在代理设计模式的情况下,JDK的动态代理和第三方动态代码生成API如CGLib有什么区别?
使用这两种方法有什么区别,什么时候应该更喜欢一种方法?
JDK动态代理只能通过接口代理(所以你的目标类需要实现一个接口,然后代理类也实现)。
CGLIB(和 javassist)可以通过子类化创建代理。在这种情况下,代理成为目标类的子类。不需要接口。
所以Java动态代理可以代理:public class Foo implements iFoo
CGLIB可以代理的地方:public class Foo
编辑:
我应该提一下,因为 javassist 和 CGLIB 通过子类化使用代理,这就是在使用依赖于此的框架时不能声明最终方法或使类成为最终方法的原因。这将阻止这些库允许子类化您的类并覆盖您的方法。
功能差异
JDK 代理允许在子类化时实现任何一组接口Object
。任何接口方法,加号Object::hashCode
,Object::equals
然后Object::toString
转发到一个InvocationHandler
. 此外,还实现了标准库接口java.lang.reflect.Proxy
。
cglib 允许您在继承任何非最终类的同时实现任何一组接口。此外,可以选择性地覆盖方法,即并非所有非抽象方法都需要被截取。此外,存在实现方法的不同方式。它还提供了一个InvocationHandler
类(在不同的包中),但它也允许通过使用更高级的拦截器来调用超级方法,例如MethodInterceptor
. 此外,cglib 可以通过专门的拦截来提高性能,例如FixedValue
. 我曾经为 cglib 写过不同拦截器的总结。
性能差异
JDK 代理的实现相当天真,只有一个拦截调度程序,InvocationHandler
. 这需要一个虚拟方法分派到一个不能总是内联的实现。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 的教程中找到有关基准测试的更多详细信息,其中 Byte Buddy 是 cglib 的更现代替代品。另外,请注意 cglib 不再处于积极开发中。
动态代理:使用JDK Reflection API在运行时动态实现接口。
示例: Spring 对事务使用动态代理,如下所示:
生成的代理位于 bean 之上。它为 bean 添加了跨国行为。这里代理使用 JDK 反射 API 在运行时动态生成。
当应用程序停止时,代理将被销毁,文件系统上将只有接口和 bean。
在上面的例子中,我们有接口。但是在大多数接口的实现中并不是最好的。所以 bean 没有实现接口,在这种情况下我们使用继承:
为了生成这样的代理,Spring 使用了一个名为CGLib的第三方库。
CGLib(代码生成库)建立在ASM之上,主要用于生成代理扩展 bean 并在代理方法中添加 bean 行为 。
Spring AOP 使用 JDK 动态代理或 CGLIB 为给定的目标对象创建代理。(当您有选择时,首选 JDK 动态代理)。
如果要代理的目标对象至少实现一个接口,则将使用 JDK 动态代理。目标类型实现的所有接口都将被代理。如果目标对象没有实现任何接口,那么将创建一个 CGLIB 代理。
如果您想强制使用 CGLIB 代理(例如,代理为目标对象定义的每个方法,而不仅仅是那些由其接口实现的方法),您可以这样做。但是,有一些问题需要考虑:
不能建议最终方法,因为它们不能被覆盖。
您将需要类路径中的 CGLIB 2 二进制文件,而动态代理可用于 JDK。当 Spring 需要 CGLIB 并且在类路径中找不到 CGLIB 库类时,Spring 会自动警告您。
代理对象的构造函数将被调用两次。这是 CGLIB 代理模型的自然结果,其中为每个代理对象生成一个子类。对于每个代理实例,都会创建两个对象:实际的代理对象和实现通知的子类的实例。使用 JDK 代理时不会出现此行为。通常,调用代理类型的构造函数两次不是问题,因为通常只有赋值发生,构造函数中没有实现真正的逻辑。