JDK动态代理与CGLib的区别是什么?

194

代理设计模式的情况下,JDK动态代理和第三方动态代码生成API(例如CGLib)有什么区别?

使用这两种方法的区别是什么,应该在什么情况下选择其中一种?


3
在这里获取代码:https://gist.github.com/ksauzz/1563486。在cglib中,您可以创建类代理和接口代理。Spring默认使用CGlib,而AspectJ使用Java代理。也可阅读此内容:http://jnb.ociweb.com/jnb/jnbNov2005.html ;) - Subhadeep Ray
3
@SubhadeepRay,我知道你的评论很旧,但我今天刚看到。很抱歉纠正你,但是你的说法“Spring默认使用CGlib,而AspectJ使用Java代理”有两个错误:(1)AspectJ根本不使用任何代理。 (2)Spring Core默认情况下对接口使用JDK代理,对类使用CGLIB代理。它还可以选择对接口使用CGLIB。由于配置类的不幸组合,Spring Boot似乎总是使用CGLIB,但是Boot不是Spring Core。 - kriegaex
4个回答

239

JDK动态代理只能通过接口来代理(所以你的目标类需要实现一个接口,这个接口也要被代理类实现)。

CGLIB(和javassist)可以通过子类创建代理。在这种情况下,代理变成了目标类的子类。不需要接口。

因此,Java动态代理可以代理: public class Foo implements iFoo,而CGLIB可以代理:public class Foo

编辑:

我应该提到,因为javassist和CGLIB使用子类代理,这就是当使用依赖于此的框架时,不能声明final方法或使类final的原因。这会阻止这些库允许子类化您的类并覆盖您的方法。


1
谢谢..!! 如果您能给我一个示例代码(或链接)以说明在某些情况下使用其中一个的用法,将会很有帮助..!!! - KDjava
2
请注意,JDK代理实际上是为IFoo创建代理,而不是任何类型的Foo。这是一个非常重要的区别。此外,cglib代理是完整的子类 - 利用它!使用过滤器仅代理您关心的方法并直接使用生成的类。 - lscoughlin
11
值得注意的是,使用CGLib创建子类需要足够了解超类,以便能够使用正确的参数调用正确的构造函数。与不关心构造函数的基于接口的代理不同,这使得与CGLib代理一起工作比JDK代理更少自动化。另一个区别在于“堆栈”成本。每次调用时,JDK代理总是会产生额外的堆栈帧,而CGLib可能不会产生任何额外的堆栈帧。这在应用程序变得越来越复杂时变得越来越重要(因为堆栈越大,线程消耗的内存就越多)。 - Ray
1
cglib 无法代理 final 方法,但不会抛出异常。 - Muhammad Hewedy
是的,CGLIB简单地忽略final方法。 - yashjain12yj

74

功能差异

  • JDK代理允许实现任何接口集,同时以Object为超类。然后,任何接口方法加上Object::hashCodeObject::equalsObject::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已不再处于活跃开发状态。


3
为什么Spring文档更喜欢使用JDK代理而不是cglib,尽管后者具有更好的性能优势? - P4ndaman
4
Cglib 是一个外部依赖库,目前不受支持。依赖第三方软件总是有风险的,因此最好尽可能少地依赖它。 - Rafael Winterhalter
在你的博客中,你说:“但是,在调用带有InvocationHandler#invoke方法的代理对象上的方法时,你应该小心。所有对此方法的调用都将使用相同的InvocationHandler进行分派,可能会导致无限循环。” 你的意思是什么? - Koray Tugay
如果您在代理对象上调用方法,则任何调用都将通过我们的调用处理程序路由。如果任何调用处理程序调用委托到对象的调用,则会发生递归。 - Rafael Winterhalter
嗨,Rafael,这条消息与你的回答无关,我是在提醒你5年前进行的编辑。由于cglib显然在2019年仍有提交,并且在其自述文件中没有任何“停滞不前”的迹象,因此我已经从标签摘要中删除了你的陈述。如果有任何相关内容需要提及,请随时改进标签描述/摘要。 - Cœur
你说得没错,目前仍在进行一些小的更改,但没有意图进一步开发cglib,例如将其适应JPMS,这就是这个评论的意思。可以说该库处于休眠状态,因为没有特定的贡献者状态的人积极使用时间来改进该库。 - Rafael Winterhalter

32

动态代理:使用JDK反射API在运行时动态实现接口。

示例:Spring在事务中使用动态代理如下:

enter image description here

生成的代理层位于bean之上。它为bean添加了事务行为。此处代理是使用JDK Reflection API在运行时动态生成的。

当应用程序停止后,代理将被销毁,文件系统上只有接口和bean。


在上述示例中,我们有一个接口。但在大多数情况下,接口的实现并不是最好的。因此,bean没有实现接口,在这种情况下我们使用继承:

enter image description here

为了生成这样的代理,Spring使用了一个第三方库叫做CGLib

CGLibCode Generation Library)基于ASM构建,主要用于生成代理并扩展bean,将bean行为添加到代理方法中。

JDK动态代理和CGLib的示例

Spring参考资料


7

来自Spring文档:

Spring AOP使用JDK动态代理或CGLIB为给定目标对象创建代理(如果可以选择,优先选择JDK动态代理)。

如果要代理的目标对象至少实现了一个接口,则会使用JDK动态代理。所有由目标类型实现的接口都将被代理。如果目标对象未实现任何接口,则将创建CGLIB代理。

如果您想强制使用CGLIB代理(例如,代理目标对象定义的每个方法,而不仅仅是其接口实现的方法),则可以这样做。但是,需要考虑一些问题:

无法通知final方法,因为它们无法被覆盖。

您需要将CGLIB 2二进制文件放在类路径上,而JDK动态代理可在JDK中使用。当Spring需要CGLIB而类路径上没有找到CGLIB库类时,Spring会自动发出警告。

代理对象的构造函数将被调用两次。这是CGLIB代理模型的自然结果,其中为每个代理对象生成子类。对于每个代理实例,将创建两个对象:实际的代理对象和实现建议的子类的实例。使用JDK代理时不会出现这种行为。通常,调用代理类型的构造函数两次不是问题,因为通常只进行赋值而没有在构造函数中实现任何真正的逻辑。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接