Spring AOP 原理
Spring AOP 是 Spring 事务、缓存、日志、权限校验等能力的基础。理解它时要抓住代理机制、自调用失效、JDK 动态代理和 CGLIB 区别,以及 AOP 和 Bean 生命周期的关系。
1. AOP 是什么
AOP(Aspect Oriented Programming)是面向切面编程。
它用于把横切逻辑从业务代码中抽离出来,例如:
- 事务控制。
- 日志记录。
- 权限校验。
- 缓存处理。
- 接口耗时统计。
核心思想:
业务方法只关心业务
公共增强逻辑由代理对象在方法前后织入2. 核心概念
| 概念 | 含义 |
|---|---|
Aspect | 切面,横切逻辑的集合 |
JoinPoint | 连接点,能被增强的位置,Spring AOP 中主要是方法执行 |
Pointcut | 切点,匹配哪些方法需要增强 |
Advice | 通知,具体增强逻辑 |
Target | 目标对象 |
Proxy | 代理对象 |
Weaving | 织入,把增强逻辑应用到目标对象 |
3. 通知类型
常见通知:
| 通知 | 执行时机 |
|---|---|
@Before | 方法执行前 |
@After | 方法执行后,不管是否异常 |
@AfterReturning | 方法正常返回后 |
@AfterThrowing | 方法抛异常后 |
@Around | 方法执行前后都能控制 |
@Around 能决定是否调用目标方法,也能修改返回值和处理异常,能力最强。
4. Spring AOP 的实现方式
Spring AOP 基于代理实现,主要有两种代理:
| 方式 | 适用条件 | 原理 |
|---|---|---|
| JDK 动态代理 | 目标类实现了接口 | 生成接口代理类 |
| CGLIB | 目标类没有接口 | 生成目标类子类 |
默认情况下:
- 有接口时通常用 JDK 动态代理。
- 没有接口时使用 CGLIB。
- 可以通过配置强制使用 CGLIB。
5. JDK 动态代理和 CGLIB 区别
| 对比点 | JDK 动态代理 | CGLIB |
|---|---|---|
| 基础 | 接口 | 继承 |
| 目标类要求 | 必须有接口 | 不能是 final 类 |
| 方法限制 | 代理接口方法 | 不能代理 final 方法 |
| 生成对象 | 接口代理对象 | 子类代理对象 |
如果类或方法是 final,CGLIB 无法通过继承覆盖方法,也就无法增强。
6. 代理对象什么时候创建
AOP 代理通常在 Bean 生命周期后期创建,核心入口和 BeanPostProcessor 有关。
简化流程:
实例化 Bean
-> 属性填充
-> 初始化前置处理
-> 初始化方法
-> 初始化后置处理
-> 判断是否需要 AOP
-> 创建代理对象
-> 容器中暴露代理对象所以从 Spring 容器中拿到的 Bean,可能不是原始对象,而是代理对象。
7. AOP 调用流程
代理对象调用方法时,流程大致是:
调用代理对象方法
-> 匹配拦截器链
-> 执行前置通知
-> 执行目标方法
-> 执行返回通知或异常通知
-> 执行后置通知
-> 返回结果事务、缓存、日志等增强都是类似的拦截器链模型。
8. 为什么自调用会失效
示例:
class UserService {
public void a() {
b();
}
@Transactional
public void b() {
// ...
}
}外部调用 a() 时,确实经过代理对象。但 a() 内部调用 b() 是 this.b(),直接调用原始对象方法,没有经过代理对象,因此 b() 上的 AOP 增强不会生效。
解决方式:
- 把
b()放到另一个 Bean 中。 - 从 Spring 容器中获取当前代理对象再调用。
- 使用
AopContext.currentProxy(),但需要开启暴露代理,不推荐滥用。
9. AOP 和事务的关系
@Transactional 本质上就是基于 AOP 实现的。
简化流程:
调用代理方法
-> 事务拦截器判断事务属性
-> 开启或加入事务
-> 执行业务方法
-> 正常返回:提交事务
-> 抛出异常:根据规则回滚所以事务失效场景通常也是 AOP 代理失效场景。
10. 常见失效场景
| 场景 | 原因 |
|---|---|
| 同类内部方法调用 | 没有经过代理对象 |
方法不是 public | Spring 事务默认主要增强 public 方法 |
类或方法是 final | CGLIB 无法覆盖 |
| 对象不是 Spring Bean | 容器没有创建代理 |
| 异常被吞掉 | 事务拦截器感知不到异常 |
| 异常类型不匹配 | 默认只回滚运行时异常和 Error |
11. 理解检查
Spring AOP 是编译期织入还是运行期织入?
Spring AOP 主要是运行期基于代理织入。AspectJ 可以支持编译期或类加载期织入。
AOP 能增强 private 方法吗?
Spring AOP 基于代理,通常不能增强 private 方法。因为 private 方法不能被代理对象以覆盖方式拦截。
为什么事务方法内部调用不生效?
因为内部调用走的是 this,没有经过代理对象。
12. 总结
Spring AOP 的核心是代理对象和拦截器链。理解它之后,事务、缓存、自调用失效、JDK 代理和 CGLIB 的区别都会变得清晰。