领先一步
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 等人比我更擅长这一点,所以不用担心;-)。