如果是Proxy Design Pattern,JDK's Dynamic Proxy和第三方动态代码生成API(例如CGLib)之间有什么区别?
使用这两种方法之间的区别是什么时候应该优先选择另一种方法?
答案 0 :(得分:157)
JDK动态代理只能通过接口代理(因此您的目标类需要实现一个接口,然后由代理类实现)。
CGLIB(和javassist)可以通过子类创建代理。在这种情况下,代理成为目标类的子类。不需要接口。
所以Java动态代理可以代理:public class Foo implements iFoo
CGLIB可以在其中代理:public class Foo
编辑:
我应该提一下,因为javassist和CGLIB通过子类化使用代理,这就是你在使用依赖于它的框架时不能声明最终方法或使类最终的原因。这将阻止这些库允许子类化并覆盖您的方法。
答案 1 :(得分:47)
功能差异
JDK代理允许在子类化java.lang.reflect.Proxy
时实现任何接口集。然后,任何接口方法,加上Object::hashCode
,Object::equals
和Object::toString
都会转发到InvocationHandler
。
cglib允许您在子类化任何非final类时实现任何接口集。此外,可以任选地覆盖方法,即不需要截取所有非抽象方法。此外,有不同的方法来实现方法。它还提供了一个InvocationHandler
类(在不同的包中),但它也允许通过使用更高级的拦截器来调用超级方法,例如MethodInterceptor
。此外,cglib可以通过FixedValue
之类的专门拦截来提高性能。我曾写过a summary of different interceptors for cglib。
效果差异
只使用一个拦截调度程序InvocationHandler
,JDK代理实现得相当天真。这需要对实现进行虚拟方法调度,而实现并不总是内联。 Cglib允许创建专门的字节代码,有时可以提高性能。以下是使用18个存根方法实现接口的一些比较:
cglib JDK proxy
creation 804.000 (1.899) 973.650 (1.624)
invocation 0.002 (0.000) 0.005 (0.000)
以毫秒为单位记录时间,括号中标准偏差。您可以在Byte Buddy's tutorial中找到有关基准测试的更多详细信息,其中Byte Buddy是cglib的更现代的替代品。另请注意,cglib不再处于活动开发阶段。
答案 2 :(得分:20)
动态代理:使用 JDK Reflection API 在运行时动态实现接口。
示例: Spring使用事务的动态代理,如下所示:
生成的代理位于bean之上。它为bean添加了跨国行为。这里代理使用JDK Reflection API在运行时动态生成。
当一个应用程序停止时,代理将被销毁,我们只在文件系统上有接口和bean。
在上面的例子中我们有接口。但在大多数接口的实现并不是最好的。所以bean没有实现接口,在这种情况下我们使用继承:
为了生成这样的代理,Spring使用名为 CGLib 的第三方库。
CGLib( C ode G 能力 Lib rary)建立在ASM之上,这主要用于生成代理扩展bean并在代理方法中添加bean行为。
答案 3 :(得分:3)
Spring AOP使用JDK动态代理或CGLIB来创建 给定目标对象的代理。 (JDK动态代理是首选 只要你有选择)。
如果要代理的目标对象实现至少一个接口 然后将使用JDK动态代理。所有接口 由目标类型实现的代理将被代理。如果是目标对象 没有实现任何接口,那么将创建一个CGLIB代理。
如果要强制使用CGLIB代理(例如,代理 为目标对象定义的每个方法,而不仅仅是那些实现的方法 通过它的接口)你可以这样做。但是,有一些问题要解决 考虑:
无法建议最终方法,因为它们无法覆盖。
您需要在类路径上使用CGLIB 2二进制文件,而动态 代理可与JDK一起使用。 Spring会自动警告你 当它需要CGLIB并且找不到CGLIB库类时 类路径。
您的代理对象的构造函数将被调用两次。这是一个 CGLIB代理模型的自然结果,即子类 为每个代理对象生成。对于每个代理实例,两个 创建对象:实际代理对象和实例 实现建议的子类。没有表现出这种行为 使用JDK代理时。通常,调用构造函数 代理类型两次,不是问题,因为通常只有 任务发生,并没有实现真正的逻辑 构造