领先一步
VMware 提供培训和认证,助您快速进步。
了解更多在最近的一篇博客文章中,Marc Logemann 提到了代理性能的话题。在他的文章中,他请求 Spring 方面的 '伙计们' 提供一份白皮书。我不想花很多篇幅讨论代理和字节码织入机制之间精确到纳秒的差异,但我确实认为重申一下这些差异以及这场讨论是否重要是很有价值的。
换句话说,代理可以作为真实对象的替身,为这些对象应用额外的行为——无论是安全相关的行为、缓存还是性能度量。
许多现代框架使用代理来实现原本不可能实现的功能。许多对象关系映射器使用代理来实现一种行为,即在实际需要数据之前阻止数据加载(这有时称为延迟加载)。Spring 也使用代理来实现其部分功能,例如其远程处理能力、事务管理能力和 AOP 框架。
代理的替代方案是字节码织入。使用字节码织入机制时,永远不会有第二个对象(即代理)。相反,如果需要应用行为(例如事务管理或安全性),它会被“织入”现有代码中,而不是“围绕”代码。进行织入过程的一种方法是使用 Java5 的 -javaagent 标志。也有其他方法可用。
换句话说:使用代理时,你最终会得到一个位于目标对象前面的代理对象,而使用字节码织入方法时,不会有需要委托调用的代理。
请注意,我在这里不打算提供具体数字。正如 Stefan Hansel 在他在 Marc 博客上的评论中正确指出的那样,测量普通目标调用与中间有代理之间的差异的微基准测试(或任何微基准测试)并没有太大意义,因为还需要考虑其他许多因素。
如果我在我的笔记本电脑 (MacBook) 上使用普通的 JDK 动态代理(稍后会详细介绍)运行此代码,那么对 *myRealObject* 的一次方法调用需要 9 纳秒 (10-9)。对被代理对象的一次调用需要 500 纳秒(大约慢了 50 倍)。
// real object
MyInterface myRealObject;
myRealObject.doIt();
// proxied object
MyInterface myProxiedObject;
myProxiedObject.doIt();
相比之下,如果我使用字节码织入方法(在这种情况下,我使用 AspectJ 来模拟相同的设置),我的调用只增加了大约 2 纳秒。
所以总结来说,我无法美化事实:代理会给普通方法调用带来显著的开销。
在我们继续之前,先认识到这里增加的开销是**固定的**。如果 `doIt()` 方法本身需要 5 秒,被代理的调用**绝对不会**花费 50 倍的时间。不,相反,调用会花费 5 秒 + 约 500 纳秒。
我们使用代理来透明地为对象添加行为。也许是为了用安全规则装饰一个对象(管理员可以访问它,但普通用户不能),或者也许是因为我们想启用延迟加载,只在第一次访问时从数据库加载数据。另一个原因可能是为我们的对象启用透明的事务管理。
现在调用服务本身确实会涉及一定的开销(我们之前已经讨论过的开销)。然而问题是,我们为这些开销换来了什么?
**代码简化** 通过在中间放置一个代理,我们极大地简化了代码。如果我们使用 Spring 提供的 `@Transactional` 注解,我们只需要做以下事情
public class Service {
@Transactional
public void executeService() { }
}
以及
<tx:annotation-driven/>
<bean class="com.mycompany.Service"/>
另一种(编程方式的)方法将涉及显著修改客户端(调用者)或服务类本身。
**集中式事务管理** 事务管理现在由一个中心设施负责,这使得优化和事务管理方法更加一致。如果我们在服务或调用者本身实现了事务管理代码,这是不可能实现的。
public Object invoke(Object proxy, Method proxyMethod, Object[] args)
throws Throwable {
Method targetMethod = null;
if (!cachedMethodMap.containsKey(proxyMethod)) {
targetMethod = target.getClass().getMethod(proxyMethod.getName(),
proxyMethod.getParameterTypes());
cachedMethodMap.put(proxyMethod, targetMethod);
} else {
targetMethod = cachedMethodMap.get(proxyMethod);
}
Ojbect retVal = targetMethod.invoke(target, args);
return retVal;
}
public Object invoke(Object proxy, Method proxyMethod, Object[] args)
throws Throwable {
Method targetMethod = target.getClass().getMethod(proxyMethod.getName(),
proxyMethod.getParameterTypes());
Ojbect retVal = targetMethod.invoke(target, args);
return retVal;
}
换句话说,将代理的生成或创建留给知道自己在做什么的人或框架来做。幸运的是,我没有参与代理的设计,而且 Rob、Juergen、Rod 等人在这方面比我做得好得多,所以不用担心 ;-)。