一、 Spring AOP 的底层实现原理
Spring AOP 的核心原理是 动态代理 (Dynamic Proxy)。
简单来说,当 Spring 容器启动并初始化 Bean 时,如果发现某个 Bean 被切面(Aspect)所“增强”(即配置了 AOP),Spring 不会直接返回原始对象,而是会创建一个代理对象 (Proxy Object) 返回给调用者。
执行流程如下:
- 容器启动: 解析 AOP 配置,找到所有需要被增强的 Bean。
- 创建代理: 在 Bean 初始化后(通常通过
BeanPostProcessor),Spring 会根据 Bean 的情况选择使用 JDK 动态代理 或 CGLIB 动态代理 来生成一个代理对象。 - 调用拦截: 当你从容器中获取 Bean 并调用方法时,实际上调用的是代理对象的方法。
- 执行切面逻辑: 代理对象会在目标方法执行前后,执行配置好的拦截器链(Advice,如
@Before,@After等),最后再通过反射或方法调用执行原始 Bean 的目标方法。
二、 两大代理模式详解
Spring AOP 在生成代理对象时,主要依靠以下两种技术:
1. JDK 动态代理 (JDK Dynamic Proxy)
这是 Java 语言原生支持的代理方式,位于 java.lang.reflect 包下。
- 实现机制: 利用反射机制生成一个实现代理接口的匿名类。
- 核心类:
Proxy(创建代理对象) 和InvocationHandler(处理方法调用)。 - 要求: 目标对象必须实现至少一个接口。代理对象和目标对象是“兄弟”关系,它们都实现了同一个接口。
2. CGLIB 代理 (Code Generation Library)
这是一个强大的、高性能的代码生成包,Spring 内部集成了它。
- 实现机制: 利用 ASM 开源包,在运行时动态生成目标类的子类(字节码级别操作)。
- 核心原理: 通过继承的方式。代理对象是目标对象的“子类”,它会重写(Override)目标对象的所有非
final方法,在子类中植入增强逻辑。 - 要求: 目标对象不需要实现接口。但目标类不能是
final的,目标方法也不能是final的(因为无法被继承或重写)。
三、 JDK 动态代理 vs CGLIB 的核心区别
| 维度 | JDK 动态代理 | CGLIB 动态代理 |
|---|---|---|
| 代理基础 | 基于 接口 (Interface) | 基于 继承 (Inheritance) |
| 底层技术 | Java 反射机制 (java.lang.reflect) | ASM 字节码生成框架 |
| 对目标类的要求 | 必须实现接口 | 可以不实现接口,但类不能为 final |
| 对方法的限制 | 只能代理接口中定义的方法 | 无法代理 final、static、private 方法 |
| 创建代理的速度 | 较快(直接利用 JDK 能力) | 较慢(需要生成复杂的字节码) |
| 执行代理的速度 | 早期较慢,但在 JDK 8+ 后性能大幅提升,与 CGLIB 差距缩小 | 非常快(通过 FastClass 机制直接调用,非反射) |
| Spring 的选择 | 如果目标对象实现了接口,默认用 JDK | 如果目标对象没实现接口,强制用 CGLIB |
四、 深度补充:Spring Boot 的默认行为变化
这是一个加分项,展示你对 Spring 版本演进的了解:
Spring Framework (传统 Spring):
- 策略是:如果有接口,优先用 JDK 动态代理;如果没有接口,自动切换为 CGLIB。
- 可以通过配置
proxy-target-class="true"强制全部使用 CGLIB。
Spring Boot 2.x 及以上:
- 默认策略发生了变化。Spring Boot 默认将
spring.aop.proxy-target-class设置为true。 - 这意味着:默认情况下,Spring Boot 会优先使用 CGLIB 代理,即使目标类实现了接口。(除非你显式配置为
false)。 - 原因: CGLIB 基于类代理,可以避免因类型转换(Interface vs Class)导致的
ClassCastException问题,对开发者更友好。
- 默认策略发生了变化。Spring Boot 默认将
五、 总结(一句话回答)
Spring AOP 底层利用动态代理技术。当目标类实现了接口时,默认使用 JDK 动态代理(基于反射和接口实现);当目标类没有实现接口时,使用 CGLIB(基于 ASM 字节码生成和子类继承)。在 Spring Boot 2.0 后,默认倾向于使用 CGLIB 以提供更好的稳定性和易用性。
结论先给你放前面,避免混淆 👇
❌ Spring Boot 3.5 默认不是 CGLIB✅ 默认仍然是:有接口 → JDK 动态代理;无接口 → CGLIB
这是 Spring Framework 6.x + Boot 3.x 的真实行为,在 JDK 21 下也没有变。
为什么很多人以为「Boot 3 默认是 CGLIB」?
这是一个非常典型的认知误区,原因主要有 4 个:
1️⃣ 现在大量项目「没有接口」
在 Boot 3 + DDD / 简化分层风格中:
@Service
public class OrderService {
@Transactional
public void create() {}
}➡ 没有接口 ➡ Spring 只能用 CGLIB
于是你看到的几乎全是:
OrderService$$SpringCGLIB$$0但这是「被迫选择」,不是「默认策略变了」。
2️⃣ 你用的是 @Transactional / @Async / @Cacheable
这些都走 Spring AOP 自动代理:
AnnotationAwareAspectJAutoProxyCreator而不是你手写的代理逻辑。
Spring 内部策略始终是:
if (hasInterface && !proxyTargetClass) {
JDK proxy
} else {
CGLIB
}3️⃣ 你可能在某个地方“无意中”开启了 CGLIB
下面任何一个,都会 全局强制 CGLIB:
配置文件
spring:
aop:
proxy-target-class: true启动类
@EnableAspectJAutoProxy(proxyTargetClass = true)某些 Starter(⚠️容易忽略)
- Spring Security 某些配置
- 自定义 AOP 配置类
4️⃣ 日志里看的是「目标类」,不是「接口」
JDK 动态代理:
com.sun.proxy.$Proxy123很多同学没看到这个日志,就以为是 CGLIB。
二、Spring Boot 3.5 + JDK 21 的真实默认行为
| 场景 | 代理方式 |
|---|---|
| 有接口 | JDK 动态代理(默认) |
| 无接口 | CGLIB |
设置 proxy-target-class=true | 强制 CGLIB |
✔ JDK 21 并没有改变 AOP 代理选择策略
三、Boot 3.5 + JDK 21 的 AOP「实战坑点」(重点🔥)
🚨 坑 1:@Transactional 在 final 方法上直接失效
@Transactional
public final void createOrder() {}原因(CGLIB 场景):
- CGLIB 通过 子类重写方法
final方法 ❌ 不能被重写- 事务不会生效,但不会报错
✅ 建议:
- 事务方法不要加 final
- Service 类不要设计成 final
🚨 坑 2:同类方法调用(最常见)
@Service
public class OrderService {
@Transactional
public void create() {}
public void test() {
this.create(); // ❌ 事务失效
}
}JDK / CGLIB 都一样会失效
✅ 解决方式(推荐顺序):
- 拆 Bean(最推荐)
- 使用代理调用自己:
((OrderService) AopContext.currentProxy()).create();⚠️ 需要:
@EnableAspectJAutoProxy(exposeProxy = true)🚨 坑 3:接口 + 实现类注入类型不一致(JDK 代理专属)
public interface OrderService {}
@Service
public class OrderServiceImpl implements OrderService {}@Autowired
private OrderServiceImpl orderService; // ❌JDK 代理类型是:
$ProxyXX implements OrderService❌ 无法注入实现类
✅ 正确姿势:
@Autowired
private OrderService orderService;或:
spring.aop.proxy-target-class: true🚨 坑 4:@Async / @Transactional 方法必须是 public
@Transactional
void save() {} // ❌原因:
- AOP 是通过 代理对象对外暴露的方法
protected / private无法被代理调用
✔ 这在 Boot 3 + JDK 21 仍然成立
🚨 坑 5:构造方法、@PostConstruct 里调用事务方法
@PostConstruct
public void init() {
createOrder(); // ❌
}原因:
- 此时 代理对象还没创建完成
- 调用的是原始对象
四、怎么确认你现在用的是 JDK 还是 CGLIB?
方法 1:看 Bean 实际类型(推荐)
@PostConstruct
public void check() {
System.out.println(this.getClass());
}输出:
$Proxy123→ JDK$$SpringCGLIB$$→ CGLIB
方法 2:Debug 断点
AbstractAutoProxyCreator#createProxy五、一句话终极结论(直接可背)
Spring Boot 3.5 在 JDK 21 下,AOP 默认策略仍然是: 有接口用 JDK 动态代理,无接口用 CGLIB; 并非“默认全 CGLIB”, 只是现代项目越来越少写接口。