Spring AOP 代理模式:CGLIB 与 JDK 动态代理区别

张开发
2026/4/18 9:08:17 15 分钟阅读

分享文章

Spring AOP 代理模式:CGLIB 与 JDK 动态代理区别
Spring AOP 底层默认支持两种代理模式JDK 动态代理和CGLIB 动态代理。很多同学只知道 Spring 会自动选择代理方式却不清楚两者的区别、适用场景面试时被问到就卡壳甚至在项目中遇到代理失效的问题也不知道如何排查。一、什么是 Spring AOP 代理在讲区别之前我们先明确一个核心概念Spring AOP 的本质是「动态代理」简单来说就是 Spring 会在运行时动态生成一个「代理对象」这个代理对象会包裹住目标对象我们的业务对象当我们调用目标对象的方法时实际上是先调用代理对象的方法代理对象在方法执行前后插入增强逻辑如日志、限流最后再执行目标对象的核心业务逻辑。举个通俗的例子你去房产中介买房中介就是「代理对象」你目标对象的核心需求是「买房」而中介会在你买房前后帮你做很多增强操作找房源、谈价格、办过户这些增强操作不影响你买房的核心需求却能帮你省去很多麻烦——这就是代理模式的核心价值解耦增强逻辑与业务逻辑。Spring AOP 之所以支持两种代理模式是为了适配不同的场景有无接口下面我们分别拆解两种代理模式的核心原理。二、核心原理两种代理模式的核心区别本质是「生成代理对象的方式不同」底层依赖的技术也不同我们分别拆解结合简单代码示例让你一眼看懂。1. JDK 动态代理JDK 动态代理是 JDK 自带的代理方式不需要依赖任何第三方jar包核心依赖java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口其核心原理是基于接口生成代理对象代理对象会实现目标对象所实现的所有接口通过调用接口方法触发增强逻辑。1.1 核心条件JDK 动态代理有一个硬性要求目标对象必须实现至少一个接口。如果目标对象没有实现任何接口JDK 动态代理就无法生成代理对象此时 Spring 会自动切换为 CGLIB 代理。1.2 简单示例手动实现 JDK 动态代理我们通过一个简单的案例手动实现 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 JdkProxyHandler implements InvocationHandler { // 目标对象被代理的对象 private final Object target; // 构造方法注入目标对象 public JdkProxyHandler(Object target) { this.target target; } /** * 代理对象的核心方法所有代理对象的方法调用都会触发这个方法 * param proxy 代理对象本身 * param method 目标方法被调用的方法 * param args 目标方法的参数 * return 目标方法的返回值 * throws Throwable 异常抛出 */ 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. 测试 JDK 动态代理 public class JdkProxyTest { public static void main(String[] args) { // 目标对象 UserService target new UserServiceImpl(); // 生成代理对象核心Proxy.newProxyInstance UserService proxy (UserService) Proxy.newProxyInstance( target.getClass().getClassLoader(), // 类加载器与目标对象一致 target.getClass().getInterfaces(), // 目标对象实现的所有接口 new JdkProxyHandler(target) // 增强逻辑处理器 ); // 调用代理对象的方法实际触发 invoke 方法 proxy.addUser(张三); } }1.3 运行结果JDK代理 - 方法执行前记录日志用户新增操作开始 核心业务新增用户 - 张三 JDK代理 - 方法执行后记录日志用户新增操作结束1.4 总结JDK 动态代理的核心是「面向接口」代理对象和目标对象是「兄弟关系」都实现了同一个接口通过接口方法间接调用目标对象的方法增强逻辑在 InvocationHandler 的 invoke 方法中实现。2. CGLIB 动态代理CGLIBCode Generation Library是一个第三方代码生成库Spring 已经内置了 CGLIB 依赖无需额外导入其核心原理是基于继承生成代理对象代理对象会继承目标对象重写目标对象的方法在重写的方法中插入增强逻辑。2.1 核心条件CGLIB 代理没有接口要求目标对象可以有接口也可以没有接口。它通过继承目标对象实现代理因此有一个注意点目标对象的方法不能被 final 修饰如果方法被 final 修饰子类无法重写CGLIB 无法实现增强。2.2 简单示例手动实现 CGLIB 代理同样通过案例手动实现 CGLIB 代理对比 JDK 代理的区别import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; // 1. 目标对象无需实现接口 public class OrderService { public void addOrder(String orderNo) { System.out.println(核心业务新增订单 - orderNo); } // final 方法CGLIB 无法增强重写不了 public final void deleteOrder(String orderNo) { System.out.println(删除订单 - orderNo); } } // 2. 实现 MethodInterceptor增强逻辑类似 JDK 的 InvocationHandler public class CglibProxyInterceptor implements MethodInterceptor { // 目标对象 private final Object target; public CglibProxyInterceptor(Object target) { this.target target; } /** * 代理对象的核心方法所有代理对象的方法调用都会触发这个方法 * param o 代理对象本身 * param method 目标方法 * param args 目标方法参数 * param methodProxy 方法代理用于调用目标方法 * return 目标方法返回值 * throws Throwable 异常抛出 */ Override public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { // 增强逻辑方法执行前 System.out.println(CGLIB代理 - 方法执行前记录日志订单新增操作开始); // 执行目标对象的核心业务方法两种方式推荐用 methodProxy.invokeSuper Object result methodProxy.invokeSuper(o, args); // 不推荐method.invoke(target, args)会触发二次代理效率低 // 增强逻辑方法执行后 System.out.println(CGLIB代理 - 方法执行后记录日志订单新增操作结束); return result; } } // 3. 测试 CGLIB 动态代理 public class CglibProxyTest { public static void main(String[] args) { // 目标对象 OrderService target new OrderService(); // 生成代理对象核心Enhancer Enhancer enhancer new Enhancer(); enhancer.setSuperclass(target.getClass()); // 设置父类目标对象 enhancer.setCallback(new CglibProxyInterceptor(target)); // 设置增强逻辑 OrderService proxy (OrderService) enhancer.create(); // 生成代理对象 // 调用代理对象的方法可增强 proxy.addOrder(ORDER20260417001); // 调用 final 方法无法增强直接执行目标方法 proxy.deleteOrder(ORDER20260417001); } }2.3 运行结果CGLIB代理 - 方法执行前记录日志订单新增操作开始 核心业务新增订单 - ORDER20260417001 CGLIB代理 - 方法执行后记录日志订单新增操作结束 删除订单 - ORDER20260417001注意deleteOrder 方法被 final 修饰CGLIB 无法重写因此没有执行增强逻辑直接调用了目标对象的方法。2.4 核心总结CGLIB 代理的核心是「面向继承」代理对象是目标对象的「子类」通过重写目标方法实现增强无需目标对象实现接口但目标方法不能是 final 修饰。三、区别对比这是本文的核心内容也是面试高频考点。我们从「底层原理、依赖条件、性能、适用场景」等8个维度对比两种代理模式的区别用表格呈现清晰易懂方便记忆。对比维度JDK 动态代理CGLIB 动态代理底层原理基于接口通过 Proxy 类生成代理对象实现目标接口基于继承通过 Enhancer 生成代理对象继承目标对象依赖条件目标对象必须实现至少一个接口无接口要求目标对象可无接口方法限制只能增强接口中定义的方法无法增强类中独有的方法可增强目标对象的所有非 final 方法final 方法无法重写不能增强代理对象关系代理对象与目标对象是「兄弟关系」同实现一个接口代理对象与目标对象是「父子关系」代理对象是目标对象的子类性能表现生成代理对象速度快执行速度稍慢需要通过反射调用接口方法生成代理对象速度慢需要生成子类字节码执行速度快直接调用子类方法无需反射依赖jar包JDK 自带无需额外导入需要 CGLIB 依赖Spring 已内置无需手动导入Spring 优先级首选如果目标对象有接口默认使用 JDK 动态代理兜底如果目标对象无接口自动切换为 CGLIB 代理也可手动指定强制使用 CGLIB适用场景目标对象有接口如 Service 层接口、Dao 层接口适合大多数企业级项目场景目标对象无接口如普通工具类、需要增强类中独有的非 final 方法补充说明Spring Boot 2.x 版本后默认情况下如果目标对象有接口使用 JDK 动态代理如果目标对象无接口使用 CGLIB 代理。如果想强制使用 CGLIB 代理只需在配置文件中添加一行配置spring.aop.proxy-target-classtrue。四、Spring AOP 代理选择源码解析很多同学好奇Spring 是如何自动选择 JDK 还是 CGLIB 代理的我们通过 Spring 核心源码片段简单解析其选择逻辑不用深入看懂每一行代码重点理解核心判断逻辑即可。// Spring AOP 代理选择的核心类DefaultAopProxyFactory public class DefaultAopProxyFactory implements AopProxyFactory, Serializable { Override public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { // 核心判断逻辑 // 1. config.isOptimize()是否开启优化默认false // 2. config.isProxyTargetClass()是否强制使用 CGLIB 代理默认false // 3. hasNoUserSuppliedProxyInterfaces(config)目标对象是否没有实现任何接口 if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) { Class? targetClass config.getTargetClass(); if (targetClass null) { throw new AopConfigException(TargetSource cannot determine target class); } // 如果目标对象是接口还是使用 JDK 动态代理 if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) { return new JdkDynamicAopProxy(config); } // 否则使用 CGLIB 代理 return new ObjenesisCglibAopProxy(config); } else { // 目标对象有接口使用 JDK 动态代理 return new JdkDynamicAopProxy(config); } } // 判断目标对象是否没有实现任何接口除了 Spring 内部的接口 private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) { Class?[] interfaces config.getProxiedInterfaces(); return interfaces null || interfaces.length 0 || (interfaces.length 1 SpringProxy.class.isAssignableFrom(interfaces[0])); } }核心逻辑总结1. 如果配置了proxy-target-classtrue强制使用 CGLIB无论目标对象是否有接口都使用 CGLIB 代理除非目标对象是接口2. 如果没有强制配置判断目标对象是否有接口有接口 → JDK 代理无接口 → CGLIB 代理3. 即使目标对象有接口如果开启了优化isOptimizetrue也会使用 CGLIB 代理优化的核心是提升执行速度。五、代理失效的3种高频场景及解决方案在实际项目中经常会遇到「AOP 增强不生效」的问题本质都是「代理对象未被调用」结合两种代理模式的特点我们总结3种高频失效场景给出解决方案帮你快速排错。场景1目标方法被 final 修饰CGLIB 代理失效❌ 问题现象使用 CGLIB 代理时final 修饰的方法增强逻辑如日志、限流不生效✅ 原因CGLIB 代理基于继承final 方法无法被子类重写因此无法插入增强逻辑✅ 解决方案移除目标方法的 final 修饰符确保方法可被重写。场景2目标对象自己调用自己的方法代理失效❌ 问题现象在 Service 类中方法 A 调用方法 B方法 B 添加了 AOP 增强注解如 DS、Log但增强逻辑不生效✅ 原因方法 A 直接调用方法 B是「目标对象内部调用」没有经过代理对象因此无法触发增强逻辑无论是 JDK 还是 CGLIB 代理都会失效✅ 解决方案1. 方式1通过 Spring 上下文获取代理对象再调用方法 B推荐2. 方式2在 Service 类中注入自身代理对象用 Autowired 注入避免直接 this 调用3. 方式3使用 AopContext.currentProxy() 获取当前代理对象再调用方法 B。// 错误示例内部调用代理失效 Service public class OrderService { public void methodA() { // this 是目标对象不是代理对象methodB 的增强不生效 this.methodB(); } Log // AOP 增强注解 public void methodB() { System.out.println(核心业务方法); } } // 正确示例通过代理对象调用 Service public class OrderService { Autowired private OrderService orderService; // 注入自身代理对象 public void methodA() { // 调用代理对象的 methodB增强生效 orderService.methodB(); } Log public void methodB() { System.out.println(核心业务方法); } }场景3目标对象未被 Spring 管理代理失效❌ 问题现象手动 new 出来的目标对象添加了 AOP 增强注解但增强逻辑不生效✅ 原因Spring AOP 只对 Spring 容器管理的 Bean 生成代理对象手动 new 的对象不属于 Spring 管理无法生成代理✅ 解决方案通过 Autowired、Resource 等方式注入目标对象不要手动 new。七、文末小结Spring AOP 的两种代理模式是 AOP 核心底层知识也是企业面试的高频考点总结下来核心要点1. JDK 动态代理面向接口无额外依赖生成快、执行慢适合有接口的场景2. CGLIB 动态代理面向继承无接口要求生成慢、执行快适合无接口或需要增强类独有方法的场景3. Spring 会自动选择代理模式也可通过配置强制使用 CGLIB4. 代理失效的核心原因是「未调用代理对象」重点关注 final 方法、内部调用、Spring 管理这3个场景。掌握这两种代理模式的区别和适用场景不仅能帮你快速排查项目中的 AOP 问题还能在面试中脱颖而出。其实不用死记硬背理解底层原理接口 vs 继承很多区别自然就能记住。如果你在项目中遇到代理相关的问题或者有其他疑问欢迎在评论区留言交流一起避坑、一起进步别忘了点赞在看收藏三连关注我解锁更多 Spring AOP 实战干货下期再见❤️

更多文章