对于 代理设计模式,JDK 的动态代理和第三方动态代码生成 API (如 CGLib)之间有什么区别?
使用这两种方法和什么时候应该选择一种方法而不是另一种方法之间的区别是什么?
JDK Dynamic 代理只能通过接口进行代理(因此您的目标类需要实现一个接口,然后该接口也由代理类实现)。
CGLIB (和 javhelp)可以通过子类化创建代理。在此场景中,代理成为目标类的子类。不需要接口。
因此 Java 动态代理可以代理: public class Foo implements iFoo,而 CGLIB 可以代理: public class Foo
public class Foo implements iFoo
public class Foo
编辑:
我应该提到,由于 javhelp 和 CGLIB 通过子类化使用代理,这就是为什么在使用依赖于它的框架时不能声明 final 方法或使类成为 final 的原因。这将阻止这些库允许子类化您的类并重写您的方法。
功能上的差异
JDK 代理允许在子类化 Object时实现任何接口集。然后将任何接口方法 plusObject::hashCode、 Object::equals和 Object::toString转发给 InvocationHandler。此外,还实现了标准库接口 java.lang.reflect.Proxy。
Object
Object::hashCode
Object::equals
Object::toString
InvocationHandler
java.lang.reflect.Proxy
Cglib 允许您在子类化任何非 final 类时实现任何接口集。此外,可以选择重写方法,也就是说,不需要截获所有非抽象方法。此外,还有不同的方法来实现一个方法。它还提供了一个 InvocationHandler类(在一个不同的包中) ,但是它也允许使用更高级的拦截器(例如 MethodInterceptor)来调用 super 方法。此外,cglib 可以通过专门的拦截(如 FixedValue)来提高性能。我曾经写过 针对 cglib 的不同拦截器的摘要。
MethodInterceptor
FixedValue
性能差异
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 教程中找到关于基准的更多细节,其中 ByteBuddy 是 cglib 的更现代的替代品。另外,请注意,cglib 不再处于活跃的开发阶段。
动态代理: 使用 JDK 反射 API在运行时动态实现接口。
示例: Spring 对事务使用如下动态代理:
生成的代理位于 bean 之上。它增加了豆子的跨国行为。这里代理使用 JDK 反射 API 在运行时动态生成。
当应用程序停止时,代理将被销毁,文件系统上只有 interface 和 bean。
在上面的例子中,我们有接口。但是在大多数接口的实现中并不是最好的。因此 bean 不实现接口,在这种情况下我们使用继承:
为了生成这样的代理,Spring 使用一个名为 CGLib的第三方库。
CGLib (Code Generation 莉比rary)是在 ASM的基础上构建的,主要用于生成代理扩展 bean 并在代理方法中添加 bean 行为。
JDK 动态代理和 CGLib 示例
春季裁判
来自 Spring 文档 :
SpringAOP 使用 JDK 动态代理或 CGLIB 创建 一个给定目标对象的代理。(JDK 动态代理是首选 只要你有选择)。 如果要代理的目标对象实现至少一个接口 然后使用一个 JDK 动态代理。所有的接口 将被代理。如果目标对象 不实现任何接口,则将创建一个 CGLIB 代理。 如果您想强制使用 CGLIB 代理(例如,代理) 为目标对象定义的每个方法,而不仅仅是那些实现的方法 你可以这样做。然而,有一些问题 考虑: 不能建议使用 final 方法,因为它们不能被重写。 您将需要类路径上的 CGLIB2二进制文件,而动态 在 JDK 中可以使用代理。 Spring 会自动警告你 当需要 CGLIB 时,在 类路径。 代理对象的构造函数将被调用两次 CGLIB 代理模型的自然结果 为每个代理对象生成 对象的实际代理对象和 实现通知的子类。此行为不显示 通常,调用 代理类型两次,不是一个问题,因为通常只有 中没有实现任何真正的逻辑 构造函数。
SpringAOP 使用 JDK 动态代理或 CGLIB 创建 一个给定目标对象的代理。(JDK 动态代理是首选 只要你有选择)。
如果要代理的目标对象实现至少一个接口 然后使用一个 JDK 动态代理。所有的接口 将被代理。如果目标对象 不实现任何接口,则将创建一个 CGLIB 代理。
如果您想强制使用 CGLIB 代理(例如,代理) 为目标对象定义的每个方法,而不仅仅是那些实现的方法 你可以这样做。然而,有一些问题 考虑:
不能建议使用 final 方法,因为它们不能被重写。
您将需要类路径上的 CGLIB2二进制文件,而动态 在 JDK 中可以使用代理。 Spring 会自动警告你 当需要 CGLIB 时,在 类路径。
代理对象的构造函数将被调用两次 CGLIB 代理模型的自然结果 为每个代理对象生成 对象的实际代理对象和 实现通知的子类。此行为不显示 通常,调用 代理类型两次,不是一个问题,因为通常只有 中没有实现任何真正的逻辑 构造函数。