Spring AOP 默认用的是什么动态代理,两者的区别?
- 标签:后端、Spring
🏛 业务架构师
Spring AOP 默认规则:目标实现了接口 → JDK 动态代理(运行时生成接口实现类);没有接口 → CGLIB(生成目标类的子类)。Spring Boot 2.x 起默认统一 CGLIB。
两者差异:
- JDK 代理:基于接口,字节码运行时生成,只能代理接口方法;不能代理 final/private(不在接口上的);
- CGLIB:基于继承,生成子类覆写方法,不能代理 final 类与 final 方法,构造器会被调用两次(父类+子类)。无需接口,但继承带来的限制要注意。
业务视角,比选型更重要的是两个应用与两个坑:
应用一:声明式事务。@Transactional 就是代理在方法前后管理事务——业务代码只写业务规则,事务这种横切关注点统一治理。应用二:审计合规——敏感操作(改利率、调额度)由切面自动记录操作人、时间、前后值,合规要求变成一段可复用代码。
坑一:自调用失效——同类内部 this.methodB() 不走代理,methodB 上的 @Transactional 静默失效,事务没开、回滚没有——这是"注解没生效"类线上事故的头号根因,也是我 CR 的必查项。解法:拆 bean、注入自身代理、或 AopContext.currentProxy()。
坑二:代理对象的类型问题——CGLIB 代理后类型是子类,按具体类型注入或反射的代码可能出问题;private/final 方法不增强,以为增强了其实没有,比不增强更危险。
一句话:AOP 让业务代码只表达业务,但代理边界(自调用、final)必须成为团队共识,否则"看不见的失效"会以资损形式现身。
🏗 系统架构师
Spring AOP 默认规则:目标类实现了接口 → JDK 动态代理;没有接口 → CGLIB 子类代理(Spring Boot 2.x 起已默认 proxyTargetClass=true,统一 CGLIB)。
两者原理差异:
- JDK 动态代理:运行时生成实现相同接口的代理类($ProxyN),通过
Proxy.newProxyInstance+ InvocationHandler 拦截,反射调用目标。要求目标必须有接口;代理与目标是"兄弟"关系(同接口); - CGLIB:字节码技术生成目标的子类,重写非 final 方法,MethodInterceptor 拦截(fastclass 机制避免反射)。不要求接口,但不能代理 final 类/方法、private 方法;子类代理意味着调用父类方法需要特殊处理,构造器会被执行两次(真实对象 + 代理对象)。
共同的失效场景(工程核心):
- 自调用:this.method() 不走代理对象,@Transactional 失效的头号原因——解法:注入自身代理(AopContext.currentProxy)或拆 Bean;
- 只能代理 public 方法;
- final/static 方法无法织入。
性能:JDK 代理反射调用 vs CGLIB 字节码调用,现代 JVM 差距已很小,不是选型依据;选型依据是类有没有接口和 Spring 版本默认策略。理解"代理对象 ≠ 目标对象"是排查注解失效、类型转换异常(注入时拿到的实际类型)的关键。
💻 全栈开发专家
Spring AOP 动态代理二选一:目标类有接口 → JDK 动态代理;没有接口 → CGLIB。但 Spring Boot 2.x 起 spring.aop.proxy-target-class 默认 true,即默认全走 CGLIB。
两者机制差异:
| 维度 | JDK 动态代理 | CGLIB |
|---|---|---|
| 实现 | 接口 + Proxy.newProxyInstance + 反射回调 | 字节码生成目标类子类 + MethodInterceptor |
| 前提 | 必须有接口 | 类和方法不能是 final |
| 代理对象 | 实现同一接口,不是目标类类型 | 目标类的子类,可强转目标类 |
| 性能 | 早期创建快调用慢 | 生成慢、调用快(FastClass 机制避免反射) |
实战中我踩过的三个坑:
- 注入类型不匹配:JDK 代理下
@Autowired private OrderService impl;若注入具体类会失败(代理只实现了接口)——按接口类型注入; - 自调用失效:类内
this.method()绕过代理对象,@Transactional/@Cacheable全部不生效。解法:注入自身代理(@Lazyself)或拆类; - final/private/static 方法:CGLIB 无法代理(子类不能覆写),注解静默失效,没有任何报错——code review 专门盯这类。
一句话总结:代理是 AOP 的实现载体,选择上现代项目基本无脑 CGLIB(Boot 默认);真正要理解的是代理边界——凡是不经过代理对象的调用,切面逻辑都不会执行。