JDK动态代理和CGLib有什么区别?


147

如果使用代理设计模式,那么JDK的动态代理和第三方动态代码生成API(例如CGLib)有什么区别

使用这两种方法之间的区别是什么?何时应该优先选择另一种方法?


3
在此处获取代码:< gist.github.com/ksauzz/1563486 >。在cglib中,您可以创建类代理和接口代理。Spring默认使用CGlib,而AspectJ使用Java代理。也请阅读:jnb.ociweb.com/jnb/jnbNov2005.html ;)
Subhadeep Ray 2013年

Answers:


185

JDK动态代理只能按接口进行代理(因此您的目标类需要实现一个接口,然后该接口也可以由代理类实现)。

CGLIB(和javassist)可以通过子类化创建代理。在这种情况下,代理将成为目标类的子类。无需接口。

因此,Java Dynamic代理可以代理:public class Foo implements iFooCGLIB可以代理:public class Foo

编辑:

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


谢谢..!!但是,如果您能给我一个示例代码(或链接)来说明某人在某些情况下的用法,将很有帮助。
KDjava

1
请注意,JDK代理实际上在为IFoo代理,而不是为任何Foo代理。这是一个非常重要的区别。而且,cglib代理是完整的子类-充分利用这一优势!使用过滤器仅代理您关心的方法并直接使用生成的类。
lscoughlin 2012年

9
还应该注意,CGLib子类的创建需要对超类有足够的了解,以便能够使用正确的args调用正确的构造函数。与不关心构造函数的基于接口的代理不同。这使得使用CGLib代理比使用JDK代理具有更少的“自动”能力。另一个区别是“堆栈”成本。JDK代理每次调用总是会产生额外的堆栈帧,而CGLib可能不会花费任何额外的堆栈帧。应用越复杂,相关性就变得越来越重要(因为堆栈越大,消耗的内存线程就越多)。

1
cglib无法代理最终方法,但不会引发异常gist.github.com/mhewedy/7345403cfa52e6f47563f8a204ec0e80
Muhammad Hewedy

是的,CGLIB只是忽略了最终方法。
yashjain12yj

56

功能上的差异

  • JDK代理允许在子类化时实现任何接口集Object。任何界面的方法,加Object::hashCodeObject::equals并且Object::toString然后被转发到一个InvocationHandler。此外,实现了标准库接口java.lang.reflect.Proxy

  • cglib允许您在继承任何非最终类的同时实现任何接口集。同样,方法可以可选地被覆盖,即并非所有非抽象方法都需要被拦截。此外,有多种实现方法的方式。它还提供了一个InvocationHandler类(在不同的包中),但是它也允许通过使用更高级的拦截器(例如a)来调用超级方法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不再处于主动开发中。


2
考虑到cglib的性能优势,为什么spring文档比JgProxy更支持JDK代理?docs.spring.io/spring/docs/2.5.x/reference/…–
P4ndaman

2
Cglib是一个外部依赖项,目前不受支持。依赖第三方软件始终是一场赌博,因此,在尽可能少的人依赖它的情况下,它是最好的选择。
拉斐尔·温特豪德

在您的博客中,您说:“但是,在对InvocationHandler#invoke方法附带的代理对象上调用方法时,应格外小心。对该方法的所有调用将使用相同的InvocationHandler进行分派,因此可能导致无限循环。” 你什么意思?
Koray Tugay

如果在代理对象上调用方法,则任何调用都会通过我们的调用处理程序进行路由。如果有任何调用处理程序调用将委托委托给该对象,则发生上述递归。
拉斐尔·温特豪德

嗨,拉斐尔(Rafael),与您的答案无关的消息,我要向您询问5年前的修改。由于cglib显然仍在2019年进行提交,并且自述文件中未显示任何令人震惊的进展,因此我已从标记摘录中删除了您的声明。如果有任何需要提及的内容,请随时改进标签的描述/摘录。
心教堂

28

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

示例: Spring使用动态代理进行事务处理,如下所示:

在此处输入图片说明

生成的代理位于bean之上。它将跨国行为添加到bean中。在这里,代理使用JDK Reflection API在运行时动态生成。

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


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

在此处输入图片说明

为了生成此类代理,Spring使用了一个名为CGLib的第三方库。

CGLIB(ç ODE ģ eneration 郭宝宏)是建立在顶部ASM,这是主要用于在生成代理延伸豆和在代理方法增加豆的行为。

JDK动态代理和CGLib的示例

春季参考


5

从Spring文档

Spring AOP使用JDK动态代理或CGLIB创建给定目标对象的代理。(只要有选择,首选JDK动态代理)。

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

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

不能建议使用final方法,因为它们不能被覆盖。

您将在类路径上需要CGLIB 2二进制文件,而JDK提供了动态代理。当需要CGLIB并且在类路径中找不到CGLIB库类时,Spring会自动向您发出警告。

代理对象的构造函数将被调用两次。这是CGLIB代理模型的自然结果,其中为每个代理对象生成一个子类。对于每个代理实例,都会创建两个对象:实际的代理对象和实现建议的子类的实例。使用JDK代理时,不会出现此现象。通常,两次调用代理类型的构造函数不是问题,因为通常仅发生分配,并且构造函数中未实现任何实际逻辑。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.