2026 年 4 月 9 日,北京
一、开篇引入

Java 动态代理是 Java 开发中一个极为重要的技术知识点。无论是在日常业务开发中实现 AOP(Aspect Oriented Programming,面向切面编程),还是在 Spring 框架、MyBatis 等主流框架的底层源码中,动态代理的身影无处不在。可以说,不理解动态代理,就很难真正看懂 Spring 事务管理、MyBatis Mapper 代理等核心机制。
许多学习者在接触动态代理时常遇到几个痛点:只会用 Proxy.newProxyInstance() 写 Demo,但说不清它底层是怎么工作的;分不清 JDK 动态代理和 CGLIB 的区别,面试时一被问到就答不上来;甚至对“静态代理”和“动态代理”的概念都容易混淆。

本文将由天工 AI 助手从零开始梳理,从痛点切入,依次讲解:为什么需要动态代理、核心概念与关联概念的区别、代码实战示例、底层原理浅析,最后汇总高频面试题。全文条理清晰、由浅入深,兼顾技术科普与实用性,目标是让读者真正搞懂动态代理。
📌 本文为“Java 动态代理从入门到精通”系列第一篇。
二、痛点切入:为什么需要动态代理?
传统做法:静态代理
假设我们有一个用户服务接口和它的实现类:
// 接口 public interface UserService { void createUser(String name); void deleteUser(Long id); } // 实现类 public class UserServiceImpl implements UserService { @Override public void createUser(String name) { System.out.println("创建用户:" + name); } @Override public void deleteUser(Long id) { System.out.println("删除用户,ID:" + id); } }
现在要为每个方法添加日志记录功能。如果用静态代理的方式,需要手动编写一个代理类:
public class UserServiceStaticProxy implements UserService { private final UserService target; public UserServiceStaticProxy(UserService target) { this.target = target; } @Override public void createUser(String name) { System.out.println("[日志] 调用 createUser,参数:" + name); target.createUser(name); System.out.println("[日志] createUser 执行完成"); } @Override public void deleteUser(Long id) { System.out.println("[日志] 调用 deleteUser,参数:" + id); target.deleteUser(id); System.out.println("[日志] deleteUser 执行完成"); } }
静态代理的核心痛点:
代理类爆炸:每新增一个接口,都需要手写一个对应的代理类-15。
横切逻辑重复:日志、事务等增强代码在每个代理方法中反复出现,维护成本高。
扩展性差:接口新增方法时,代理类必须同步修改-。
更好的方案:动态代理
动态代理在运行时由 JVM 自动生成代理类,无需手动编写。一个通用的 InvocationHandler 可以处理所有接口的代理需求,大幅减少重复代码-11。
三、核心概念讲解:JDK 动态代理
标准定义
JDK 动态代理是 Java 标准库(位于 java.lang.reflect 包)提供的一种在运行时动态生成代理类的机制,它要求目标类必须实现至少一个接口-22。
核心组件:
| 组件 | 作用 |
|---|---|
Proxy 类 | 提供 newProxyInstance() 静态方法,动态创建代理实例 |
InvocationHandler 接口 | 定义 invoke() 方法,所有代理方法调用都会被转发到此方法中处理 |
生活化类比
可以把动态代理想象成一个“万能中介”——你只要告诉它你要代理什么类型的事务(接口),它就能自动生成一个全能的代理对象,替你完成日志、校验等增强工作。而静态代理就像是“专属经纪人”,每签一个明星(接口)都要专门配一个经纪人,极其低效-。
核心价值
JDK 动态代理解决了静态代理“代码重复、扩展性差”的根本问题,让开发者只需关注增强逻辑的实现,代理对象的生成交由框架自动完成-6。
四、关联概念讲解:CGLIB 动态代理
标准定义
CGLIB(Code Generation Library,代码生成库) 是一个基于 ASM 字节码框架的开源项目,它通过在运行时动态生成目标类的子类来实现代理,可以代理没有实现任何接口的普通类-52。
核心组件
| 组件 | 作用 |
|---|---|
Enhancer 类 | 配置目标类和回调,生成代理子类 |
MethodInterceptor 接口 | 定义 intercept() 方法,拦截代理方法调用 |
简单示例说明运行机制
假设有一个没有实现任何接口的普通类 OrderService:
public class OrderService { public void createOrder() { System.out.println("创建订单"); } }
CGLIB 会动态生成一个名为 OrderService$$EnhancerByCGLIB$$xxx 的子类,该子类重写 createOrder() 方法,在重写的方法中调用 MethodInterceptor.intercept() 来执行增强逻辑-55。
五、概念关系与区别总结
JDK 动态代理和 CGLIB 动态代理的关系可以理解为:它们是动态代理的两种不同实现方式——JDK 动态代理基于接口(原生支持),CGLIB 基于继承(补充方案)。
一句话总结:JDK 动态代理靠接口,CGLIB 动态代理靠继承。
对比表
| 对比维度 | JDK 动态代理 | CGLIB 动态代理 |
|---|---|---|
| 代理方式 | 基于接口 | 基于继承(生成子类) |
| 要求 | 目标类必须实现接口 | 无需接口,但类不能是 final |
| 底层技术 | 反射 + Proxy | ASM 字节码增强 |
| 限制 | 只能代理接口中定义的方法 | 无法代理 final 类、final 方法 |
| 依赖 | Java 标准库,无需额外依赖 | 需要引入 CGLIB 库 |
| 典型场景 | Spring AOP 中代理有接口的 Bean | Spring AOP 中代理无接口的 Bean |
-21-22
六、代码示例演示
6.1 JDK 动态代理完整示例
// 1. 定义接口 public interface UserService { void createUser(String name); } // 2. 实现类 public class UserServiceImpl implements UserService { @Override public void createUser(String name) { System.out.println("创建用户:" + name); } } // 3. 自定义 InvocationHandler import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; 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("[前置日志] 调用方法:" + method.getName()); // 核心:反射调用目标方法 Object result = method.invoke(target, args); // 后置增强 System.out.println("[后置日志] 方法执行完成"); return result; } } // 4. 生成代理对象并使用 public class Demo { public static void main(String[] args) { UserService target = new UserServiceImpl(); UserService proxy = (UserService) Proxy.newProxyInstance( target.getClass().getClassLoader(), // 类加载器 target.getClass().getInterfaces(), // 接口数组 new LogInvocationHandler(target) // 调用处理器 ); proxy.createUser("张三"); } }
执行流程:
Proxy.newProxyInstance()在内存中动态生成代理类的字节码代理类被加载到 JVM,并通过反射实例化
调用
proxy.createUser()时,实际转发到LogInvocationHandler.invoke()在
invoke()中完成增强逻辑 + 反射调用目标方法
6.2 静态代理 vs 动态代理效果对比
| 对比项 | 静态代理 | JDK 动态代理 |
|---|---|---|
| 代理类数量 | N 个接口需要 N 个代理类 | 1 个 InvocationHandler 复用 |
| 新增接口时的改动 | 需要新增代理类 | 无需改动任何代理代码 |
| 代码复用性 | 差 | 好 |
| 运行时性能 | 编译期确定,略优 | 反射调用有轻微开销 |
-15
七、底层原理浅析
JDK 动态代理的底层原理
JDK 动态代理的底层本质是 动态生成字节码 + 反射机制 的结合-。Proxy.newProxyInstance() 内部核心步骤为:
拼凑生成字节码:根据传入的接口数组,在内存中动态拼装一个 Java 类的字节码。这个类会实现所有指定接口,每个方法的实现体中都会调用
InvocationHandler.invoke()-2。类加载:将生成的字节码加载进 JVM,得到代理类的
Class<?>对象-2。反射实例化:通过反射调用代理类的构造函数,传入
InvocationHandler实例,生成代理对象-2。
生成的代理类名称形如 jdk.proxy1.$Proxy0,在调试或报错日志中看到这类名称时,就能快速定位到动态代理相关的问题-2。
CGLIB 动态代理的底层原理
CGLIB 底层依赖 ASM 字节码框架,它直接在内存中生成目标类的子类字节码,而不是依赖 Java 反射-52。CGLIB 采用了 FastClass 机制——通过生成方法索引表,以类似数组下标访问的方式直接调用方法,避免了反射调用的性能开销--55。
💡 底层技术栈定位:JDK 动态代理依赖于 Java 反射(属于 JVM 层面的 API),而 CGLIB 依赖于字节码生成技术(属于更底层的类文件操作)。理解这两者的差异,有助于后续深入学习 Spring AOP 等框架的源码。
八、高频面试题与参考答案
面试题 1:静态代理和动态代理有什么区别?
参考答案(踩分点:创建时机 + 灵活性 + 代码量):
创建时机不同:静态代理的代理类在编译期手动编写,编译后存在
.class文件;动态代理的代理类在运行期通过反射/字节码技术动态生成,无物理.class文件。灵活性不同:静态代理与目标类一一对应,新增接口需同步新增代理类;动态代理一个
InvocationHandler可处理多个接口,灵活性高。代码量不同:静态代理存在大量重复代码,动态代理大大减少了手写代理类的工作量。
-37-15
面试题 2:JDK 动态代理和 CGLIB 动态代理有什么区别?(星标🔥)
参考答案(踩分点:代理方式 + 使用要求 + 底层技术 + 限制条件):
代理方式不同:JDK 动态代理基于接口,代理类与目标类实现相同接口;CGLIB 基于继承,动态生成目标类的子类。
使用要求不同:JDK 要求目标类必须实现至少一个接口;CGLIB 无此要求,但目标类不能是
final类。底层技术不同:JDK 基于 Java 反射机制 +
Proxy类;CGLIB 基于 ASM 字节码框架,通过生成子类实现。限制条件不同:JDK 只能代理接口中定义的方法;CGLIB 无法代理
final类和final方法。
-37-21
面试题 3:为什么 JDK 动态代理只能代理接口?
参考答案(踩分点:Java 单继承限制 + 代理类已继承 Proxy):
JDK 动态代理生成的代理类已经继承了 java.lang.reflect.Proxy 类。Java 是单继承语言,一个类无法同时继承两个父类,因此无法再通过继承方式代理目标类,只能通过实现接口的方式来代理-。
面试题 4:介绍一下动态代理在 Spring AOP 中的实际应用场景?(星标🔥)
参考答案(踩分点:Spring 默认策略 + 三大应用场景):
Spring AOP 的底层实现核心就是动态代理。默认策略是:目标类有接口时使用 JDK 动态代理,无接口时使用 CGLIB 动态代理(可通过配置强制使用 CGLIB)。
实际应用场景包括:
声明式事务管理:通过动态代理在业务方法执行前自动开启事务,执行后提交事务,异常时回滚事务-37。
统一日志记录:在方法执行前后记录入参、耗时等信息,无需在每个方法中重复编写-37。
权限校验拦截:在业务方法执行前拦截请求,校验用户权限。
-37
面试题 5:JDK 动态代理和 CGLIB 哪个性能更好?
参考答案(踩分点:JDK 版本差异 + 优化趋势):
性能表现随 JDK 版本变化。JDK 1.8 及以后版本,Java 对反射机制进行了优化,JDK 动态代理性能优于或接近 CGLIB;JDK 1.8 以下版本,CGLIB 由于采用 FastClass 机制直接调用方法,性能略优于 JDK 动态代理-37-21。
九、结尾总结
核心知识点回顾
| 知识点 | 要点 |
|---|---|
| 静态代理的痛点 | 代理类爆炸、代码重复、扩展性差 |
| JDK 动态代理 | 基于接口,依赖 Proxy + InvocationHandler,Java 原生支持 |
| CGLIB 动态代理 | 基于继承,依赖 Enhancer + MethodInterceptor,需引入第三方库 |
| 核心区别 | JDK 靠接口,CGLIB 靠继承;JDK 只能代理接口方法,CGLIB 不能代理 final 类 |
| 底层原理 | JDK:动态生成字节码 + 反射;CGLIB:ASM 字节码增强 + FastClass 索引调用 |
| 框架应用 | Spring AOP 默认策略:有接口用 JDK,无接口用 CGLIB |
重点与易错点
⚠️ 易错点 1:误以为 JDK 动态代理可以代理任何类。❌ 错!必须要有接口。
⚠️ 易错点 2:混淆 InvocationHandler 中的 proxy 参数和 target 对象。proxy 是代理实例本身,不能在其上再次调用代理方法(否则无限递归);业务增强应通过持有 target 对象完成。
⚠️ 易错点 3:CGLIB 不能代理 final 方法和 final 类,否则运行时会抛出异常。
预告
下一篇将继续深入:Spring AOP 中 JDK 动态代理与 CGLIB 的源码级分析,以及如何根据业务场景选择合适的代理方式。敬请关注!
本文由天工 AI 助手整理撰写,数据来源于公开技术文档与行业实践。欢迎留言讨论!