📌 本文已由AI火花助手全程辅助资料搜集与结构梳理
在Spring AOP、MyBatis、RPC框架中,有一个反复出现的底层技术——Java动态代理。很多开发者会用,却说不清它“为什么能动态”、和静态代理到底差在哪、面试被问到“JDK和CGLIB的区别”时只会背答案。本文将带你把这些问题一次性讲透。

一、痛点切入:为什么需要动态代理?
先看一个常见场景:你有一个UserService,需要给它的createUser方法加上日志记录。

不用代理的话,你可能会这样写:
public class UserService { public void createUser(String username) { System.out.println("【日志】开始执行createUser"); // 侵入式 // 核心业务逻辑 System.out.println("【日志】结束执行createUser"); } }
问题显而易见:日志代码和业务代码混在一起,每个方法都要手动加一遍,改起来更是灾难。
静态代理的做法是手动编写一个代理类,实现和目标对象相同的接口,在代理类中包裹目标方法并添加增强逻辑。但如果你有10个Service,就要写10个代理类,代码量翻倍。而且接口一旦新增方法,所有代理类都要跟着改,维护成本爆炸。
动态代理要解决的就是这个问题:不写代理类,在运行时自动生成——你只需要定义一套增强逻辑,它就能为任意对象动态生成代理。
二、核心概念讲解:什么是Java动态代理?
定义:Java动态代理是一种在程序运行时通过反射机制动态生成代理类的技术。代理对象会拦截对目标对象的方法调用,在调用前后插入额外逻辑(如日志、事务、权限校验),而无需修改目标对象的源代码-5。
一句话理解:静态代理是你亲手写一个代理类;动态代理是JVM帮你生成一个代理类。
生活化类比:旅行社就是你的“代理”。你(目标对象)只需要告诉旅行社你想去哪,剩下的一切——订票、办签证、安排住宿——都由旅行社(代理对象)帮你完成。重点是,你不用自己去学怎么做这些事,旅行社就是那个在中间帮你“增强”旅行体验的中间人-10。
三、关联概念讲解:JDK动态代理 vs CGLIB动态代理
Java动态代理主要有两种实现方式:
3.1 JDK动态代理
定义:基于Java标准库
java.lang.reflect包实现,要求目标类必须实现至少一个接口。核心三剑客:
Proxy(工具类,生成代理对象)、InvocationHandler(定义增强逻辑)、Method(反射调用目标方法)-10。特点:Java原生支持,无需额外依赖,轻量级-39。
3.2 CGLIB动态代理
定义:Code Generation Library,通过ASM字节码生成框架动态创建目标类的子类作为代理类,不要求目标类实现接口。
核心组件:
Enhancer(创建代理类)、MethodInterceptor(拦截方法调用)-39。特点:功能更强大,但无法代理
final类或final方法-3。
四、概念关系与区别总结
JDK和CGLIB的关系,用一句话概括:
JDK动态代理是“接口代理”思想的标准实现,CGLIB是“继承代理”思想的字节码落地。
| 对比维度 | JDK动态代理 | CGLIB动态代理 |
|---|---|---|
| 代理方式 | 基于接口 | 基于继承(生成子类) |
| 目标要求 | 必须实现接口 | 无需接口,但类/方法不能是final |
| 底层技术 | 反射 + Proxy | ASM字节码增强 |
| 依赖 | JDK原生,无需额外依赖 | 需引入CGLIB库(Spring已内置) |
| 代理对象生成速度 | 较快 | 较慢(需生成字节码) |
| 方法调用效率 | JDK 8之前略慢,JDK 9+差距已显著缩小 | 较高(直接调用) |
| 典型应用 | Spring AOP默认对接口代理 | 代理无接口的类、Hibernate懒加载 |
-3-39
面试考点提示:JDK 8及更高版本中,两种代理的性能差距已显著缩小,千万不要再背“CGLIB一定比JDK快”这种过时的结论了-37。
五、代码示例演示
5.1 JDK动态代理完整示例
// 1. 定义接口 public interface UserService { void createUser(String username); } // 2. 目标类(必须实现接口) public class UserServiceImpl implements UserService { @Override public void createUser(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代理】方法执行前:记录日志"); Object result = method.invoke(target, args); // 反射调用目标方法 System.out.println("【JDK代理】方法执行后:记录结束"); 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) // 增强逻辑处理器 ); proxy.createUser("张三"); } }
执行结果:
【JDK代理】方法执行前:记录日志 业务:创建用户张三 【JDK代理】方法执行后:记录结束
5.2 CGLIB动态代理示例
// 1. 目标类(无需接口,但不能是final) public class ProductService { public void addProduct(String name) { System.out.println("业务:添加商品" + name); } } // 2. 自定义MethodInterceptor public class CglibMethodInterceptor implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("【CGLIB代理】方法执行前:权限校验"); Object result = proxy.invokeSuper(obj, args); // 调用父类(目标类)方法 System.out.println("【CGLIB代理】方法执行后:记录操作"); return result; } } // 3. 使用代理 public class CglibMain { public static void main(String[] args) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(ProductService.class); // 设置父类 enhancer.setCallback(new CglibMethodInterceptor()); // 设置拦截器 ProductService proxy = (ProductService) enhancer.create(); proxy.addProduct("手机"); } }
-37
六、底层原理支撑
JDK动态代理的本质:Proxy.newProxyInstance()方法在运行时动态拼接生成字节码,创建一个新的类(通常命名为$Proxy0),这个类实现了传入的所有接口。生成的代理类会持有InvocationHandler的引用,接口中的每个方法都被重写,方法体内调用InvocationHandler.invoke(),再由invoke()通过反射调用真正的目标方法-47。
💡 核心逻辑:动态生成字节码 + 反射调用 = JDK动态代理
CGLIB动态代理的本质:底层依赖ASM字节码操作框架,在运行时动态生成目标类的子类。这个子类会重写所有非final方法,在重写的方法中通过MethodInterceptor拦截调用,再调用父类(目标类)的原始方法--39。
💡 核心逻辑:ASM生成字节码 + 继承重写 + 方法拦截 = CGLIB
依赖的Java基础:
反射机制:JDK动态代理通过
Method.invoke()调用目标方法-1类加载机制:动态生成的代理类字节码需通过类加载器加载到JVM中
字节码技术:CGLIB依赖ASM直接操作字节码,性能更高
面试进阶方向:理解Proxy类的generateProxyClass()如何生成字节码、CGLIB的FastClass机制如何避免反射调用——这些都是源码级进阶的核心切入点。
七、高频面试题与参考答案
面试题1:什么是Java动态代理?和静态代理有什么区别?
参考答案:动态代理是在程序运行时,通过反射机制动态生成代理类并创建代理对象的技术,不需要像静态代理那样手动编写代理类。区别在于:静态代理在编译期确定代理关系,需要为每个目标类单独写代理类,代码冗余、维护成本高;动态代理在运行期生成代理类,一套增强逻辑可复用于多个目标对象,真正做到了“一次编写,处处生效”。动态代理是Spring AOP实现的核心基础-5-55。
面试题2:JDK动态代理和CGLIB动态代理有什么区别?各有什么优缺点?
参考答案:JDK动态代理基于接口,要求目标类必须实现接口,通过Proxy.newProxyInstance()生成代理对象,底层用反射调用目标方法。优点是Java原生、无需额外依赖、轻量级;缺点是不能代理没有接口的类。CGLIB动态代理基于继承,通过ASM字节码技术生成目标类的子类作为代理,无需接口,但不能代理final类或final方法。性能方面,JDK 8之前CGLIB调用效率更高,但JDK 9+两者差距已显著缩小;生成代理对象时CGLIB更慢(需生成字节码),JDK更快。Spring AOP默认优先使用JDK代理,当目标类无接口时自动切换为CGLIB-3-6-37。
面试题3:Spring AOP默认使用哪种动态代理?如何强制使用CGLIB?
参考答案:Spring AOP默认优先使用JDK动态代理——如果目标类实现了接口,就使用JDK代理;如果目标类没有实现任何接口,则自动降级使用CGLIB。强制使用CGLIB有两种方式:①在Spring Boot配置文件中设置spring.aop.proxy-target-class=true;②在XML配置中设置<aop:aspectj-autoproxy proxy-target-class="true"/>。强制使用CGLIB的场景包括:需要代理没有接口的类,或者需要代理类内部的非public方法调用-56-64。
面试题4:JDK动态代理为什么只能代理有接口的类?
参考答案:因为JDK动态代理生成的代理类(如$Proxy0)会继承java.lang.reflect.Proxy类,而Java是单继承的,代理类已经继承了Proxy,不能再继承其他类。JDK动态代理只能通过实现接口的方式来扩展目标类的功能。如果目标类没有实现接口,生成的代理类就无法与目标类建立委托关系。这也是CGLIB通过继承方式实现代理的价值所在-3。
面试题5:动态代理在实际项目中有哪些应用场景?
参考答案:① Spring AOP:通过动态代理实现方法拦截,在目标方法前后织入日志、事务、权限校验等横切逻辑;② MyBatis:Mapper接口的动态代理,开发者只需定义接口,MyBatis在运行时动态生成实现类执行SQL;③ RPC框架:如Dubbo,通过动态代理屏蔽网络通信细节,让远程调用像本地调用一样;④ 拦截器/过滤器:实现请求的预处理和后处理;⑤ 缓存代理:为方法调用添加缓存逻辑,提升性能-41-10。
八、结尾总结
本文围绕Java动态代理这一AOP的核心底层技术,从痛点引入到概念解析,从代码实战到原理剖析,再到面试考点,形成了完整的学习链路。
核心回顾:
| 知识点 | 一句话总结 |
|---|---|
| 什么是动态代理 | 运行时自动生成代理对象,无需手写代理类 |
| JDK vs CGLIB | JDK基于接口+反射,CGLIB基于继承+字节码 |
| 核心原理 | 动态生成字节码 + 反射(JDK)/ ASM(CGLIB) |
| Spring选择策略 | 有接口用JDK,无接口自动切CGLIB |
| 应用场景 | AOP、MyBatis、RPC、拦截器、缓存代理 |
易错点提醒:
❌ 不要再背“CGLIB一定比JDK快”——JDK 9+性能差距已显著缩小
❌ 不要把JDK和CGLIB当成对立面——Spring AOP是两者结合使用
✅ 面试时重点说清楚“动态”的本质:运行时生成字节码,而非编译期手写
🔗 关联阅读预告
下一篇文章将深入Spring AOP的源码实现,剖析DefaultAopProxyFactory是如何根据目标类特征智能选择JDK还是CGLIB代理策略的,以及JdkDynamicAopProxy的invoke()方法中完整的拦截器链执行逻辑。
