JDK动态代理只能通过接口来代理(所以你的目标类需要实现一个接口,这个接口也要被代理类实现)。
CGLIB(和javassist)可以通过子类创建代理。在这种情况下,代理变成了目标类的子类。不需要接口。
因此,Java动态代理可以代理: public class Foo implements iFoo
,而CGLIB可以代理:public class Foo
编辑:
我应该提到,因为javassist和CGLIB使用子类代理,这就是当使用依赖于此的框架时,不能声明final方法或使类final的原因。这会阻止这些库允许子类化您的类并覆盖您的方法。
功能差异
JDK代理允许实现任何接口集,同时以Object
为超类。然后,任何接口方法加上Object::hashCode
、Object::equals
和Object::toString
将被转发到一个InvocationHandler
中。此外,还实现了标准库接口java.lang.reflect.Proxy
。
cglib允许你实现任何接口集,同时以任何非final类为超类。另外,方法可以选择性地重写,即不必拦截所有非抽象方法。此外,有不同的方法实现方式。它还提供了一个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's tutorial中找到有关基准测试的更多详细信息,Byte Buddy是Cglib的一种现代替代方案。另外,请注意,Cglib已不再处于活跃开发状态。
动态代理:使用JDK反射API在运行时动态实现接口。
示例:Spring在事务中使用动态代理如下:
生成的代理层位于bean之上。它为bean添加了事务行为。此处代理是使用JDK Reflection API在运行时动态生成的。
当应用程序停止后,代理将被销毁,文件系统上只有接口和bean。
在上述示例中,我们有一个接口。但在大多数情况下,接口的实现并不是最好的。因此,bean没有实现接口,在这种情况下我们使用继承:
为了生成这样的代理,Spring使用了一个第三方库叫做CGLib。
CGLib(Code Generation Library)基于ASM构建,主要用于生成代理并扩展bean,将bean行为添加到代理方法中。
Spring AOP使用JDK动态代理或CGLIB为给定目标对象创建代理(如果可以选择,优先选择JDK动态代理)。
如果要代理的目标对象至少实现了一个接口,则会使用JDK动态代理。所有由目标类型实现的接口都将被代理。如果目标对象未实现任何接口,则将创建CGLIB代理。
如果您想强制使用CGLIB代理(例如,代理目标对象定义的每个方法,而不仅仅是其接口实现的方法),则可以这样做。但是,需要考虑一些问题:
无法通知final方法,因为它们无法被覆盖。
您需要将CGLIB 2二进制文件放在类路径上,而JDK动态代理可在JDK中使用。当Spring需要CGLIB而类路径上没有找到CGLIB库类时,Spring会自动发出警告。
代理对象的构造函数将被调用两次。这是CGLIB代理模型的自然结果,其中为每个代理对象生成子类。对于每个代理实例,将创建两个对象:实际的代理对象和实现建议的子类的实例。使用JDK代理时不会出现这种行为。通常,调用代理类型的构造函数两次不是问题,因为通常只进行赋值而没有在构造函数中实现任何真正的逻辑。