JDK 动态代理和 CGLIB 动态代理对比

04-10 1240阅读

JDK动态代理和CGLIB动态代理对比

JDK动态代理和CGLIB动态代理是Java中两种常见的动态代理方式,它们都用于在运行时动态地创建代理对象。以下是它们的主要对比点:

JDK 动态代理和 CGLIB 动态代理对比
(图片来源网络,侵删)

1. 代理的对象不同:

  • JDK动态代理:只能代理实现了接口的类,代理对象是实现了目标对象所有接口的代理类。
  • CGLIB动态代理:可以代理没有实现接口的类,通过继承目标类生成子类的方式来创建代理对象。

    2. 实现机制不同:

    • JDK动态代理:使用java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口来创建代理对象,工作通过反射机制完成。
    • CGLIB动态代理:使用底层的字节码技术,通过Enhancer类和MethodInterceptor接口来创建代理对象,工作通过字节码增强技术完成。

      3. 性能差异:

      • JDK动态代理:因为它基于反射机制,所以在调用代理方法时性能上不如CGLIB。
      • CGLIB动态代理:通常认为其性能要比JDK动态代理更好,因为它通过直接操作字节码生成新的类,避免了使用反射的开销。

        4. 使用场景差异:

        • JDK动态代理:适用于接口驱动的代理场景,在不涉及具体类,只关心接口定义时非常适用。
        • CGLIB动态代理:在需要代理没有实现接口的类,或者需要通过继承来提供增强功能的场景更适用。

          5. 依赖差异:

          • JDK动态代理:不需要添加任何额外依赖,因为它是基于JDK自带的API。
          • CGLIB动态代理:需要添加CGLIB库的依赖。

            6. 可扩展性和复杂性:

            • JDK动态代理:使用较为简单,只需要实现InvocationHandler接口。
            • CGLIB动态代理:提供了更多的控制,包括方法拦截、方法回调等,但相对来说使用起来更复杂。

              7. 第三方框架支持:

              • JDK动态代理和CGLIB动态代理都被广泛地应用在各种Java框架中,例如Spring。Spring可以根据情况选择使用JDK动态代理还是CGLIB动态代理。默认情况下,Spring会优先使用JDK动态代理,如果要代理的对象没有实现接口,则会使用CGLIB动态代理。

                在实际使用中,选择哪种代理方式通常取决于具体的应用场景和需求。如果目标对象已经实现了接口,那么JDK动态代理是一个简单而有效的选择。如果目标对象没有实现接口或者有特定的继承结构要求,CGLIB可能是更好的选择。

                JDK动态代理与CGLIB动态代理在性能方面的差异

                1. 实现方式的差异:

                  • JDK动态代理是基于接口的代理,它使用反射机制在运行时创建代理对象,并且每次通过代理对象调用方法都会通过反射进行。反射在性能上相对较慢,因为它需要在运行时解析类的字节码,查找对应的方法,并进行安全性检查等。
                  • CGLIB动态代理是通过扩展被代理类的方式工作,它使用ASM字节码操作框架直接操作类的字节码生成新的子类。由于它是直接修改字节码来创建子类,这种方式避免了反射调用,因此在调用方法时可以比反射更快。
                  • 优化程度的差异:

                    • CGLIB可以对生成的字节码进行一些优化,例如内联,这能进一步提高代理对象的方法调用速度。
                    • JDK动态代理的优化受限于Java反射API提供的功能,因此优化的空间较小。

                然而,关于性能的讨论并非那么简单,因为现代JVM对于反射的优化已经相当成熟,包括但不限于方法调用的内联、反射调用的优化等。所以在现代Java版本中,JDK动态代理的性能和CGLIB之间的差距已经不像过去那么大了。

                Spring等框架优先使用JDK动态代理,原因包括:

                1. 兼容性:

                  • JDK动态代理是Java核心API的一部分,不需要任何额外的依赖,因此在所有Java环境中都能保证良好的兼容性。
                  • 简洁性:

                    • 使用JDK动态代理可以让代理逻辑与业务逻辑清晰地分离。只要类实现了接口,就可以使用JDK动态代理,这符合Java编程的接口导向设计原则。
                    • 足够快:

                      • 对于大多数企业应用来说,JDK动态代理提供的性能已经足够快,特别是在优化过的JVM上,性能不再是选择代理方式的主要考虑点。
                      • 设计选择:

                        • Spring框架鼓励使用接口编程,这也是为什么它倾向于使用JDK动态代理。当一个类实现接口时,Spring默认会用JDK动态代理去创建代理类。
                        • 避免类加载问题:

                          • CGLIB需要为目标对象创建一个子类,这可能在某些复杂的类加载环境下导致问题,特别是在OSGi等模块化编程环境中。

                综上所述,尽管CGLIB在某些情况下可能提供更好的性能,但JDK动态代理由于其简洁性、兼容性和设计上的考虑,往往是Spring等框架的首选。然而,当代理非接口实现类时,Spring将退回到使用CGLIB。

                关于生成类的字节码

                JDK动态代理

                当使用JDK动态代理时,Java运行时会动态生成代理类的字节码,并直接加载到JVM中。这个过程是在内存中完成的,通常不会输出为磁盘上的字节码文件(即.class文件)。JDK动态代理使用java.lang.reflect.Proxy类的newProxyInstance方法来在运行时动态创建代理对象。这个方法会根据传入的接口信息,使用反射机制生成一个实现了指定接口的代理类,并将其加载到JVM中。

                CGLIB动态代理

                CGLIB(Code Generation Library)也会在运行时生成新的类的字节码,但它是通过操作底层字节码(使用第三方库如ASM)来创建新的类。与JDK动态代理类似,CGLIB通常会将生成的字节码直接加载到JVM中,而不是写入到文件系统。CGLIB是通过其Enhancer类来生成新的代理对象,这些对象是原始类的子类。

                在特定的调试场景下,开发者可能需要将这些动态生成的类的字节码写入文件系统,以便进行分析。例如,CGLIB提供了一个调试功能,允许开发者将生成的类的字节码保存为.class文件。但是,在标准的运行时环境中,这些动态生成的类通常只存在于内存中,而不会持久化到磁盘上。

                因此,虽然两种技术在技术上都涉及到字节码的生成,但它们通常不会直接生成磁盘上的文件,除非特别配置为调试目的。在标准的应用程序执行过程中,这些动态生成的类都是在内存中创建和使用的。

VPS购买请点击我

文章版权声明:除非注明,否则均为主机测评原创文章,转载或复制请以超链接形式并注明出处。

目录[+]