Java动态代理是Java开发进阶之路上的一道必过门槛——它是Spring AOP、MyBatis、RPC框架等主流技术的核心基石,也是面试中考察频率极高的知识点。很多开发者只会用、不懂原理:调用Proxy.newProxyInstance()能跑通代码,但问及底层如何生成代理类、JDK代理与CGLIB有何本质区别时却语塞;事务注解写上去不生效也排查不出原因。本文将从“为什么要用动态代理”出发,先带你理解痛点,再深入剖析JDK动态代理的核心概念与底层原理,对比CGLIB的差异,最后给出可直接运行的代码示例与高频面试题答案,助你建立从“会用”到“懂原理”的完整知识链路。
一、痛点切入:静态代理为什么不够用?

先看一段静态代理代码:
// 接口定义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); } } // 静态代理类 —— 手动编写,每个方法都要写一遍 public class UserServiceProxy implements UserService { private UserService target; public UserServiceProxy(UserService target) { this.target = target; } @Override public void createUser(String name) { System.out.println("前置:记录日志"); target.createUser(name); System.out.println("后置:记录耗时"); } @Override public void deleteUser(Long id) { System.out.println("前置:记录日志"); target.deleteUser(id); System.out.println("后置:记录耗时"); } }
这种静态代理存在三大致命缺陷:
耦合高、代码冗余:每增加一个方法,代理类都要同步添加对应方法并重复编写增强逻辑,有多少方法就得写多少遍。
扩展性差:新增一个需要代理的接口,就要新建一个代理类,维护成本随项目规模线性增长。
灵活性低:编译期就确定了代理关系,运行期无法动态切换代理行为。
于是,动态代理应运而生——它把“生成代理类”这件事从编译期搬到了运行期,让JVM根据接口动态生成代理对象,开发者只需关注“增强逻辑怎么写”,而非“代理类怎么写”。
二、核心概念:JDK动态代理
JDK动态代理(JDK Dynamic Proxy) 是Java原生提供的一种运行时代理机制,定义在java.lang.reflect包中。其核心思想是:在运行时动态生成一个实现了指定接口的代理类,所有方法调用都被转发给一个调用处理器(InvocationHandler)。
生活化类比
把JDK动态代理想象成客服总机系统:你打电话(调用方法)给某公司,不会直接转接到具体业务员(目标对象),而是先经过总机(代理对象),总机根据你的需求转接到对应业务员,同时可以在转接前/后做记录、校验等操作。关键区别在于:这个“总机”不是提前写好代码的,而是根据业务需求“即时组建”的——你只需要告诉系统“有哪些业务线(接口)”,系统就能自动生成一个总机。
三大核心组件
| 组件 | 作用 |
|---|---|
Proxy类 | 提供newProxyInstance()静态方法,负责生成代理对象 |
InvocationHandler接口 | 定义invoke()方法,由开发者实现增强逻辑 |
Method类 | 反射调用的核心,在invoke()中通过method.invoke(target, args)执行目标方法 |
三、关联概念:CGLIB动态代理
CGLIB(Code Generation Library) 是另一种动态代理实现方式,通过继承目标类生成子类来实现代理,不需要目标类实现接口。
| 对比维度 | JDK动态代理 | CGLIB动态代理 |
|---|---|---|
| 代理方式 | 基于接口实现 | 基于继承生成子类 |
| 目标类要求 | 必须实现至少一个接口 | 无需实现接口,但类和方法不能是final |
| 底层技术 | 反射 + Proxy | ASM字节码生成框架 |
| 性能特征 | JDK 8+优化后与CGLIB差距缩小 | 生成代理类开销较大,但调用时无需反射 |
| 适用场景 | 目标类有接口时优先 | 目标类无接口或需强制使用继承 |
一句话理解二者的关系:JDK动态代理是“思想标准”,CGLIB是“落地补充”——前者定义了一种“运行时生成接口实现类”的代理思想,后者为没有接口的类提供了具体的继承式代理方案。
四、代码示例:手写一个完整的JDK动态代理
下面通过一个完整的示例,演示如何为UserService添加日志记录和方法耗时统计功能。
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; // 1. 定义接口 public interface UserService { void createUser(String name); String getUser(Long id); } // 2. 目标类(实现接口) public class UserServiceImpl implements UserService { @Override public void createUser(String name) { System.out.println("执行:创建用户 " + name); } @Override public String getUser(Long id) { System.out.println("执行:查询用户 " + id); return "User{" + id + "}"; } } // 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("[BEFORE] 方法: " + method.getName() + ", 参数: " + args); long start = System.currentTimeMillis(); // 反射调用目标方法(核心!) Object result = method.invoke(target, args); // 后置增强:记录耗时 long end = System.currentTimeMillis(); System.out.println("[AFTER] 耗时: " + (end - start) + "ms"); return result; } } // 4. 创建代理对象并测试 public class ProxyDemo { 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("张三"); System.out.println("--- 分割线 ---"); proxy.getUser(100L); } }
执行流程解析:
调用
proxy.createUser("张三"),实际上调用的是生成的$Proxy0代理类中同名方法。代理类的方法内部,将调用转发给
LogInvocationHandler.invoke()方法。invoke()中先执行前置增强,再通过反射method.invoke(target, args)调用真实目标方法。目标方法执行完毕后,回到
invoke()执行后置增强,最后返回结果。
关键理解:整个过程中,目标类的代码没有一行改动,所有的增强逻辑都集中在InvocationHandler中,这就是“无侵入式增强”的本质。
五、底层原理:代理类是怎么生成的?
JDK动态代理的底层依赖于反射机制和字节码生成技术:
Proxy.newProxyInstance()在运行时调用ProxyGenerator.generateProxyClass(),在内存中动态拼接出代理类的字节码。生成的代理类(通常命名为
$Proxy0)会继承Proxy类,并实现你传入的所有接口。代理类的每个方法内部都做了同一件事:加载对应的
Method对象,然后调用InvocationHandler.invoke()。
查看生成字节码的一种方式(设置系统属性):System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"),然后使用javap -c $Proxy0反编译观察字节码。你会发现每个方法的字节码都遵循固定模式:加载父类中的h字段(即InvocationHandler),加载方法对应的Method对象,构建参数数组,最后调用h.invoke()。
这一机制的底层支撑是Java反射——Method.invoke()让程序可以在运行时动态调用任意方法,为代理层拦截方法调用提供了基础设施。
六、高频面试题与参考答案
1. JDK动态代理和CGLIB动态代理有什么区别?
标准答案(分点回答,踩分点在“接口vs继承”和“final限制”):
| 对比项 | JDK动态代理 | CGLIB动态代理 |
|---|---|---|
| 代理方式 | 基于接口 | 基于继承生成子类 |
| 目标类要求 | 必须实现接口 | 无需接口,但类/方法不能是final |
| 底层技术 | 反射 + Proxy | ASM字节码生成 |
| 性能 | JDK 8+优化后,两者差距已缩小 | 生成代理类开销大,但调用性能好 |
| 适用场景 | Spring AOP中目标类有接口时 | 目标类无接口或需强制使用CGLIB |
一句话记忆:JDK代理接口,CGLIB代理类;final类/方法只能用JDK。
2. 静态代理和动态代理的根本区别是什么?
核心区别在于代理类的创建时机:
静态代理:代理类在编译期手动编写,编译后存在
.class文件,一个接口对应一个代理类,扩展性差。动态代理:代理类在运行期通过反射/字节码技术动态生成,无物理
.class文件,一个InvocationHandler可适配多个目标类,灵活性高。
3. 为什么@Transactional注解有时会失效?
常见原因(按频率排序):
内部调用:同一个类中的方法直接调用(
this.method()),没有经过代理对象,AOP不生效。方法不是
public:Spring事务默认只作用于public方法。类或方法被
final修饰:CGLIB无法生成子类代理。异常被吞没:事务只在未捕获异常时回滚。
4. Spring AOP中JDK动态代理和CGLIB的自动选择机制是怎样的?
Spring AOP的DefaultAopProxyFactory会根据以下规则选择代理方式:
若目标类实现了接口,默认使用JDK动态代理。
若目标类没有实现接口,则使用CGLIB。
可通过
<aop:config proxy-target-class="true"/>或@EnableAspectJAutoProxy(proxyTargetClass = true)强制使用CGLIB。
七、结尾总结
回顾全文核心要点:
| 章节 | 核心知识点 |
|---|---|
| 痛点切入 | 静态代理的三大缺陷:耦合高、扩展差、灵活性低 |
| JDK动态代理 | Proxy + InvocationHandler + Method三剑客,基于接口代理 |
| CGLIB | 基于继承生成子类,可代理无接口类,但无法代理final类/方法 |
| 核心关系 | JDK是“思想标准”,CGLIB是“落地补充” |
| 代码实现 | 在invoke()中编写前置/后置增强,通过method.invoke()执行原方法 |
| 底层原理 | 运行时字节码生成 + 反射调用,代理类统一继承Proxy |
| 面试重点 | JDK vs CGLIB的区别、事务失效原因、Spring AOP选择机制 |
易错提醒:JDK动态代理只能代理接口,不能代理普通类;CGLIB不能代理final类和方法;Spring AOP内部调用不经过代理对象。
下一篇我们将继续深入AOP的切点表达式与通知类型,敬请关注!
📌 参考资料
Oracle Java SE 8 Proxy API Documentation-
Java动态代理背后的实现原理源码解析-
JDK动态代理和CGLIB动态代理面试题解析-6
Spring AOP动态代理原理与源码剖析-27

