在 Java 技术栈中,动态代理是 AOP(Aspect-Oriented Programming,面向切面编程)框架、RPC 框架和 MyBatis 等中间件的核心底层支撑,堪称“框架工程师的必修课”-20。今天,助手抖音AI小助手就带大家从“只会用”到“懂原理”,彻底打通动态代理的知识链路。
一、痛点切入:为什么需要代理模式?

很多开发者在工作中都会遇到这样的需求:给 Service 层的业务方法统一加日志,或者给支付接口加权限校验,却又不想在每个方法里重复编写相同的代码-2。
传统做法无非两种:

方式一:在类中直接嵌入逻辑。若多个类都需要日志功能,每个类都要重复编写相同的日志代码,一旦格式要改,所有类都得同步修改,维护成本极高-47。
方式二:通过组合引入功能类。目标类需要持有事务管理器或日志服务的引用,方法调用前后主动调用,这会让目标类被迫知晓大量实现细节,破坏了单一职责原则,耦合度剧增-47。
静态代理正是解决上述问题的第一步。代理类与目标类实现同一接口,在代理类的方法调用前后嵌入附加逻辑,客户端直接与代理对象交互-38。来看一个极简示例:
// 1. 抽象主题:业务接口 public interface UserService { void addUser(String username); } // 2. 真实主题:核心业务(只干活,不写日志) public class UserServiceImpl implements UserService { @Override public void addUser(String username) { System.out.println("数据库新增用户:" + username); } } // 3. 静态代理类:为目标类附加日志功能 public class UserServiceProxy implements UserService { private final UserService target; // 持有目标对象引用 public UserServiceProxy(UserService target) { this.target = target; } @Override public void addUser(String username) { System.out.println("〖日志〗开始执行addUser,参数:" + username); // 前置增强 target.addUser(username); // 调用核心业务 System.out.println("〖日志〗addUser执行完毕"); // 后置增强 } } // 客户端调用 UserService userService = new UserServiceProxy(new UserServiceImpl()); userService.addUser("张三");
静态代理虽有“不入侵原代码”的优点,但问题也显而易见:
代理类爆炸:每个目标类都需要手动编写一个对应的代理类,数量会随着业务规模急剧膨胀-;
扩展性差:一旦接口新增方法,所有代理类都得同步修改,违背开闭原则-3;
代码冗余:日志、权限这类增强逻辑在每个代理类中重复出现,毫无复用性可言。
由此,动态代理应运而生——核心目标就是“运行时生成代理类,一套增强逻辑为无数个目标对象服务”-38。
二、核心概念讲解:JDK动态代理
定义
JDK动态代理是 Java 原生提供的动态代理技术。它的核心思想是:在运行时动态生成一个实现指定接口的代理类,并将对该代理类的方法调用统一转发给 InvocationHandler(调用处理器)的 invoke() 方法,由开发者在该方法中自由插入增强逻辑-11。
核心关键词拆解
动态:代理类不是在编译期写死的,而是在运行时由 JVM 通过字节码生成技术创建-38。
接口驱动:JDK 动态代理要求目标类必须实现至少一个接口,代理类会实现这些接口-11。
InvocationHandler:开发者只需实现该接口的 invoke() 方法,所有代理对象的方法调用最终都会“汇集”到这里-3。
Proxy.newProxyInstance():这是创建代理对象的工厂方法,接受三个参数(类加载器、接口数组、InvocationHandler实例)并返回代理对象-3。
生活化类比
JDK 动态代理就像一个 “万能接线员” :你只需要告诉接线员“我要联系哪些部门(接口)”,接线员就会自动帮你接通并记录通话(增强逻辑),而你完全不用关心每个部门是不是都配了一个专属秘书。一个接线员可以为无数个部门提供服务——这正是动态代理“一套逻辑服务多个目标”的核心优势。
作用与价值
JDK 动态代理实现了横切关注点与业务逻辑的解耦,让开发者可以在不修改业务代码的前提下统一添加日志、事务、权限校验等增强逻辑,同时解决了静态代理代理类爆炸的扩展性问题-3。
三、关联概念讲解:CGLIB动态代理
定义
CGLIB(Code Generation Library,代码生成库) 是基于 ASM 字节码操作框架实现的另一种动态代理技术。它的实现方式是通过动态生成目标类的子类作为代理类,并重写父类的非 final 方法来植入增强逻辑,因此不要求目标类实现任何接口-19-22。
与 JDK 动态代理的关系
JDK 动态代理和 CGLIB 是两种并行且互补的动态代理实现方式:
JDK 动态代理:基于接口,轻量原生,无需引入第三方库;
CGLIB:基于继承,可代理无接口的普通类,但需引入 cglib 库(Spring Core 已内置),且无法代理 final 类或 final 方法-19-24。
一句话记忆:JDK 动态代理是“接口派”,CGLIB 是“子类派”-20。
四、概念关系与区别总结
| 对比维度 | JDK 动态代理 | CGLIB 动态代理 |
|---|---|---|
| 实现方式 | 接口代理(实现目标接口) | 子类代理(继承目标类)-19 |
| 是否依赖接口 | 必须实现接口 | 不需要接口-19 |
| 能否代理 final 类/方法 | ❌ 不涉及 | ❌ 无法代理(继承受限)-19 |
| 底层技术 | 反射 + Proxy | ASM 字节码生成-19 |
| 第三方依赖 | 无需(Java 原生) | 需引入 cglib 库-19 |
| 创建代理开销 | 较小 | 较大(需生成字节码)-19 |
| 方法调用性能 | JDK 8 前略慢,8+ 已大幅优化 | 执行效率更高-24 |
逻辑关系总结:JDK 动态代理是 Java 官方原生提供的“接口驱动型”代理方案,CGLIB 则是社区提供的“继承驱动型”补充方案。两者共同解决了静态代理的扩展性问题,但各有所长、互为补充-20。
五、代码示例演示
JDK 动态代理完整示例
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; // 1. 定义业务接口(JDK动态代理必须有接口) public interface UserService { void addUser(String username); } // 2. 目标类:实现接口 public class UserServiceImpl implements UserService { @Override public void addUser(String username) { System.out.println("【核心业务】数据库新增用户:" + username); } } // 3. 实现 InvocationHandler,编写增强逻辑 public class LogInvocationHandler implements InvocationHandler { private final Object target; // 持有目标对象引用 public LogInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 前置增强:日志记录 System.out.println("〖JDK动态代理〗方法 " + method.getName() + " 开始执行,参数:" + args[0]); // 核心:反射调用目标方法 Object result = method.invoke(target, args); // 后置增强 System.out.println("〖JDK动态代理〗方法 " + method.getName() + " 执行完毕"); return result; } } // 4. 创建代理对象并调用 public class Main { public static void main(String[] args) { UserService target = new UserServiceImpl(); UserService proxy = (UserService) Proxy.newProxyInstance( target.getClass().getClassLoader(), // 类加载器 target.getClass().getInterfaces(), // 目标类实现的接口数组 new LogInvocationHandler(target) // InvocationHandler实例 ); proxy.addUser("李四"); } }
执行流程:
调用
proxy.addUser("李四")时,JVM 将调用转发给LogInvocationHandler.invoke()invoke()中先执行前置增强(打印日志),再通过反射method.invoke(target, args)调用目标类的真正业务方法目标方法执行完毕后,
invoke()继续执行后置增强,最后返回结果-24
关键点:invoke() 中的 target 必须是非 null 的真实对象实例,不能是代理对象自身,否则会导致无限递归-13。
六、底层原理与技术支撑
JDK 动态代理的实现高度依赖两个底层技术:
1. Java 反射机制(Reflection)
代理类的创建依赖
Proxy.newProxyInstance(),该方法的底层通过反射动态生成代理类的字节码;目标方法的实际调用依赖
Method.invoke(),这也是反射 API 的核心能力-47。
2. 字节码生成技术
当调用 Proxy.newProxyInstance() 时,JVM 会在内存中动态生成一个代理类字节码(类名通常为 $Proxy0)并加载到 JVM 中。这个代理类会:
继承
java.lang.reflect.Proxy类(因此无法再继承其他类,这也是 JDK 动态代理无法代理普通类的根本原因)-11-13;实现目标对象的所有接口;
将所有接口方法的调用统一转发给
InvocationHandler.invoke()-16。
CGLIB 的底层原理则有所不同:它基于 ASM 字节码操作框架,在运行时动态生成目标类的子类作为代理类,通过重写父类方法来植入增强逻辑-19。
七、高频面试题与参考答案
面试题1:JDK 动态代理和 CGLIB 有什么区别?
参考答案(踩分点) :
实现原理不同:JDK 基于接口代理,通过
Proxy.newProxyInstance()生成实现指定接口的代理类;CGLIB 基于继承代理,通过 ASM 字节码技术生成目标类的子类作为代理类。依赖条件不同:JDK 要求目标类必须实现接口;CGLIB 不依赖接口,但要求目标类和目标方法不能是
final。性能差异:JDK 动态代理创建代理对象较快(反射生成类),但方法调用通过反射执行,有额外开销;CGLIB 创建代理对象较慢(需生成字节码),但方法调用直接操作,执行效率更高。JDK 8+ 后性能差距已显著缩小。
第三方依赖:JDK 是 Java 原生支持,无需额外依赖;CGLIB 需要引入 cglib 库(Spring Core 已内置)。
适用场景:JDK 适合接口驱动的轻量场景;CGLIB 适合无接口类的代理需求-19-22。
面试题2:Spring AOP 默认使用哪种动态代理?
参考答案:
Spring Framework 5.x:默认优先使用 JDK 动态代理。若目标类没有实现任何接口,则自动切换为 CGLIB--32。
Spring Boot 2.x 及更高版本:默认已改为 CGLIB 代理-32。
可通过配置强制指定:
spring.aop.proxy-target-class=true强制使用 CGLIB;false则强制使用 JDK 动态代理(前提是目标类有接口)。
面试题3:JDK 动态代理为什么只能代理接口?
参考答案:
因为 JDK 动态代理生成的代理类(如 $Proxy0)会继承 java.lang.reflect.Proxy 类。Java 是单继承的,一个类只能有一个父类,因此生成的代理类无法再继承目标类。只能通过实现接口的方式,让代理类与目标类具有相同的方法签名,从而实现对目标方法的拦截和转发。如果目标类没有实现任何接口,Proxy.newProxyInstance() 会直接抛出 IllegalArgumentException-13-11。
面试题4:动态代理中的“动态”体现在哪里?
参考答案:
“动态”体现在两个层面:
代理类生成时机:代理类不是在编译期手动编写,而是在运行时由 JVM 通过字节码生成技术动态创建-38;
代理关系的灵活性:同一套增强逻辑(一个
InvocationHandler实现)可以为任意多个实现了接口的目标对象提供代理服务,无需为每个目标类单独编写代理类,从根本上解决了静态代理的扩展性问题-37。
八、结尾总结
核心知识点回顾
| 知识点 | 要点 |
|---|---|
| 代理模式本质 | 通过代理对象控制对目标对象的访问,在方法调用前后植入附加逻辑-38 |
| 静态代理痛点 | 代理类爆炸、扩展性差、代码冗余- |
| JDK 动态代理 | 基于接口 + 反射,依赖 Proxy 和 InvocationHandler-11 |
| CGLIB 动态代理 | 基于继承 + ASM 字节码,可代理无接口类-19 |
| 底层原理 | 反射 + 字节码生成 + 类加载机制-16 |
| Spring AOP 选择 | Spring Boot 2.x+ 默认 CGLIB;传统 Spring 优先 JDK,无接口时降级 CGLIB-32 |
重点与易错点
⚠️ 易错点1:JDK 动态代理的目标类必须有接口,否则
Proxy.newProxyInstance()直接报错;⚠️ 易错点2:CGLIB 无法代理 final 类和 final 方法,因为 Java 的继承机制不支持;
⚠️ 易错点3:
InvocationHandler.invoke()中不要递归调用代理对象自身的方法,否则会无限循环-13;⚠️ 易错点4:混淆 Spring Framework 和 Spring Boot 的默认代理策略——前者优先 JDK,后者默认 CGLIB。
下篇预告
下一篇我们将深入 Spring AOP 的源码实现,剖析 AnnotationAwareAspectJAutoProxyCreator 的代理创建全流程,以及 MethodInterceptor 调用链的完整执行模型,带大家从“会用 AOP”进阶到“看懂 AOP 源码”-29。敬请期待!
扫一扫微信交流