Java动态代理是如何实现的?JDK Proxy 和 CGLib 有什么区别?

目录

一、Java动态代理的实现

1、使用JDK Proxy实现动态代理

2、使用CGLib实现动态代理

二、JDK Proxy 与 CGLib 的区别

三、Spring中的动态代理

四、Lombok代理原理

总结


前言

本文深入探讨了Java动态代理的实现机制,分别介绍了使用JDK Proxy和CGLib两种不同方式来实现动态代理。

文章进一步对比了JDK Proxy与CGLib的主要区别,JDK Proxy主要依赖于java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口,它允许在运行时动态创建实现了一组接口的代理类实例,但仅限于代理那些已实现接口的类。相比之下,CGLib通过在运行时生成目标类的子类来实现代理,从而克服了JDK Proxy只能代理接口的限制。包括代理对象的类型、性能差异、使用场景以及依赖问题。尽管两者都可以实现动态代理,但选择哪一种方式取决于具体需求:如果目标对象已经实现了接口,则JDK Proxy是一个简单直接的选择;若需要代理没有实现接口的普通类,则CGLib是更合适的选择。

文中还探讨了Spring框架中动态代理的应用,Spring可以根据情况自动选择使用JDK Proxy或CGLib来实现代理,为开发者提供了更高层次的抽象和便利。同时,也简要介绍了Lombok库是如何利用代理原理来减少样板代码并提高开发效率的。

一、Java动态代理的实现

Java动态代理主要依赖于java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口来实现。其基本思想是,在运行时动态创建一个实现了一组接口的代理类的实例,然后将对该实例的所有方法调用转发给一个处理器(即实现了InvocationHandler接口的类的实例)。

Java动态代理是Java高级编程中的一项强大功能,它允许开发者在运行时创建代理实例来控制对其他对象的访问。这种技术广泛应用于AOP(面向切面编程)、RPC(远程过程调用)框架、事务管理等领域。本文旨在深入探讨Java动态代理的实现机制,并比较JDK Proxy和CGLib两种实现方式的异同。

动态代理的常用实现方式是反射。反射机制是指程序在运行期间可以访问、检测和修改其本身状态或行为的一种能力,使用反射我们可以调用任意一个类对象,以及类对象中包含的属性及方法。

动态代理可以通过 CGLib 来实现,而 CGLib 是基于 ASM(一个 Java 字节码操作框架)而非反射实现的。

简单来说,动态代理是一种行为方式,而反射或 ASM 只是它的一种实现手段而已。

1、使用JDK Proxy实现动态代理

JDK动态代理只能代理实现了接口的类。

  • 定义接口:首先定义一个或多个接口,以及它们的实现类。
  • 创建InvocationHandler:实现InvocationHandler接口,重写invoke方法。在这里可以插入自定义的逻辑,比如日志记录、权限检查等。
  • 生成代理对象:通过调用Proxy.newProxyInstance方法,传入类加载器、接口数组以及InvocationHandler实例,来动态生成代理对象。
public interface Subject {void doSomething();}public class RealSubject implements Subject {@Overridepublic void doSomething() {System.out.println("Doing something...");}}public class MyInvocationHandler implements InvocationHandler {private Object target;public MyInvocationHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// Before adviceSystem.out.println("Before method call");Object result = method.invoke(target, args);// After adviceSystem.out.println("After method call");return result;}}Subject subject = (Subject) Proxy.newProxyInstance(RealSubject.class.getClassLoader(),new Class[]{Subject.class},new MyInvocationHandler(new RealSubject()));subject.doSomething();

2、使用CGLib实现动态代理

与JDK Proxy不同,CGLib能够代理未实现接口的类。它通过在运行时动态生成被代理类的子类,来拦截对父类方法的调用。

  • 定义类:直接定义一个类,而非接口及其实现。
  • 创建MethodInterceptor:实现MethodInterceptor接口,重写intercept方法,在该方法中添加自定义逻辑。
  • 生成代理对象:通过使用CGLib提供的Enhancer类,设置父类和回调,从而创建代理对象。
public class RealSubject {public void doSomething() {System.out.println("Doing something...");}}Enhancer enhancer = new Enhancer();enhancer.setSuperclass(RealSubject.class);enhancer.setCallback(new MethodInterceptor() {@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {// Before adviceSystem.out.println("Before method call");Object result = proxy.invokeSuper(obj, args);// After adviceSystem.out.println("After method call");return result;}});RealSubject subject = (RealSubject) enhancer.create();subject.doSomething();

二、JDK Proxy 与 CGLib 的区别

尽管JDK Proxy和CGLib都可用于实现Java动态代理,但它们之间存在一些关键差异:

  1. 代理对象类型:JDK Proxy只能代理实现了接口的类;而CGLib可以直接代理普通类。
  2. 性能:CGLib在运行时生成代理类的子类,通常认为其性能略优于JDK Proxy。但在大多数场景下,这种性能差异不大。
  3. 使用场景:如果目标对象已经实现了接口,使用JDK Proxy是一个简单直接的选择。如果需要代理没有实现接口的类,则必须使用CGLib。
  4. 依赖:JDK Proxy无需额外依赖,因为它是Java核心库的一部分;而CGLib需要添加CGLib库作为项目依赖。
  5. JDK Proxy 是 Java 语言自带的功能,无需通过加载第三方类实现;
  6. Java 对 JDK Proxy 提供了稳定的支持,并且会持续的升级和更新 JDK Proxy,例如 Java 8 版本中的 JDK Proxy 性能相比于之前版本提升了很多;
  7. JDK Proxy 是通过拦截器加反射的方式实现的;
  8. JDK Proxy 只能代理继承接口的类;
  9. JDK Proxy 实现和调用起来比较简单;
  10. CGLib 是第三方提供的工具,基于 ASM 实现的,性能比较高;
  11. CGLib 无需通过接口来实现,它是通过实现子类的方式来完成调用的。

三、Spring中的动态代理

动态代理的常见使用场景有 RPC 框架的封装、AOP(面向切面编程)的实现、JDBC 的连接等。

Spring 框架中同时使用了两种动态代理 JDK Proxy 和 CGLib,当 Bean 实现了接口时,Spring 就会使用 JDK Proxy,在没有实现接口时就会使用 CGLib,我们也可以在配置中指定强制使用 CGLib,只需要在 Spring 配置中添加即可。

四、Lombok代理原理

Lombok是一个工具类可以解决又重复的代码,如 Setter、Getter、toString、equals 和 hashCode 等等,向这种方法都可以使用 Lombok 注解来完成。 需要在 IDE 中安装 Lombok 插件,如下图所示:

Lombok 的实现和反射没有任何关系,Lombok 是在编译期就为我们生成了对应的字节码其实 Lombok 是基于 Java 1.6 实现的 JSR 269: Pluggable Annotation Processing API 来实现的,也就是通过编译期自定义注解处理器来实现的,它的执行步骤如下:

从流程图中可以看出,在编译期阶段,当 Java 源码被抽象成语法树(AST)之后,Lombok 会根据自己的注解处理器动态修改 AST,增加新的代码(节点),在这一切执行之后就生成了最终的字节码(.class)文件,这就是 Lombok 的执行原理。

总结

Java动态代理是Java高级编程中的一个强大工具,它提供了一种灵活的方式来增强方法调用。通过对比JDK Proxy和CGLib,可以看出每种方法各有优势和适用场景。选择合适的动态代理实现方式,可以帮助开发者编写更加灵活和高效的代码。在实际开发中,根据具体需求选择最适合的代理方式,是提升项目质量和维护性的关键。

如果本文对你有帮助 欢迎 关注、点赞、收藏、评论,博主才有动力持续记录遇到的问题!!!

博主v:XiaoMing_Java

作者简介:嗨,大家好,我是小明(小明Java问道之路)互联网大厂后端研发专家,2022博客之星TOP3 / 博客专家 / CSDN后端内容合伙人、InfoQ(极客时间)签约作者、阿里云签约博主、全网 6 万粉丝博主。


文末获取联系 精彩专栏推荐订阅收藏

专栏系列(点击解锁)

学习路线(点击解锁)

知识定位

Redis从入门到精通与实战

Redis从入门到精通与实战

围绕原理源码讲解Redis面试知识点与实战

MySQL从入门到精通

MySQL从入门到精通

全面讲解MySQL知识与企业级MySQL实战

计算机底层原理

深入理解计算机系统CSAPP

以深入理解计算机系统为基石,构件计算机体系和计算机思维

Linux内核源码解析

围绕Linux内核讲解计算机底层原理与并发

数据结构与企业题库精讲

数据结构与企业题库精讲

结合工作经验深入浅出,适合各层次,笔试面试算法题精讲

互联网架构分析与实战

企业系统架构分析实践与落地

行业最前沿视角,专注于技术架构升级路线、架构实践

互联网企业防资损实践

互联网金融公司的防资损方法论、代码与实践

Java全栈白宝书

精通Java8与函数式编程

本专栏以实战为基础,逐步深入Java8以及未来的编程模式

深入理解JVM

详细介绍内存区域、字节码、方法底层,类加载和GC等知识

深入理解高并发编程

深入Liunx内核、汇编、C++全方位理解并发编程

Spring源码分析

Spring核心七IOC/AOP等源码分析

MyBatis源码分析

MyBatis核心源码分析

Java核心技术

只讲Java核心技术