Spring AOP 与动态代理 ⭐⭐⭐⭐⭐
面试频率:★★★★★
工作频率:★★★★★
⚡30 秒速记(复习只看这里)
🟢 一句话
AOP 就是在不修改原业务代码的情况下,通过代理对象在方法执行前后增加额外逻辑。
Spring AOP 核心:
原始对象
↓
Spring创建代理对象
↓
调用代理对象
↓
执行额外逻辑
↓
调用原始方法
常见应用:
@Transactional
@Async
日志
权限
监控
🟡 Spring AOP 核心原理
Spring Bean
↓
BeanPostProcessor
↓
判断是否需要AOP
↓
创建代理对象
↓
Spring容器保存代理对象
↓
@Autowired拿到代理对象
🔴 @Transactional 为什么能生效?
因为:
调用代理对象
↓
事务拦截器
↓
开启事务
↓
调用原始方法
↓
成功 → commit
异常 → rollback
所以:
真正实现事务的是代理对象,不是 @Transactional 注解自己。
📚 一、什么是 AOP?
AOP:
Aspect Oriented Programming
中文:
面向切面编程。
不用死背英文。
简单理解:
在不修改业务代码的情况下,在方法执行前后增加额外功能。
例如:
原来:
public void createOrder() {
saveOrder();
}
现在需要:
记录日志。
最简单的方法:
public void createOrder() {
System.out.println("开始");
saveOrder();
System.out.println("结束");
}
这样确实可以。
但是如果:
100个Service
1000个方法
全部需要:
日志
事务
权限
监控
每个方法都写一遍:
非常麻烦。
所以使用 AOP:
业务代码:
public void createOrder() {
saveOrder();
}
额外功能:
日志
事务
权限
监控
统一放到外面处理。
二、AOP 核心思想 ⭐⭐⭐⭐⭐
假设:
OrderService
原来:
调用者
↓
OrderService
↓
createOrder()
使用 AOP:
调用者
↓
代理对象
↓
额外逻辑
↓
OrderService
↓
createOrder()
↓
额外逻辑
例如事务:
调用者
↓
代理对象
↓
开启事务
↓
createOrder()
↓
提交事务
所以:
AOP 的核心就是代理。
三、什么是动态代理? ⭐⭐⭐⭐⭐
假设原始对象:
OrderService
Spring 会给它生成:
OrderService代理对象
代理对象内部最终会调用:
真正的OrderService
所以:
orderService.createOrder();
看起来:
只是调用一个普通方法。
实际上可能是:
调用代理对象
↓
执行额外逻辑
↓
调用真正的createOrder()
↓
执行额外逻辑
例如:
OrderService代理对象
↓
开启事务
↓
OrderService.createOrder()
↓
提交事务
四、@Transactional 为什么能工作? ⭐⭐⭐⭐⭐
例如:
@Service
public class OrderService {
@Transactional
public void createOrder() {
saveOrder();
deductStock();
}
}
我们只写了:
createOrder();
但是 Spring 可以做到:
开启事务
↓
createOrder()
↓
成功
↓
commit
发生异常:
开启事务
↓
createOrder()
↓
异常
↓
rollback
原因:
Spring 创建了代理对象。
原始对象:
OrderService
Spring最终提供:
OrderService代理对象
调用:
orderService.createOrder();
实际流程:
调用代理对象
↓
事务拦截器
↓
开启事务
↓
调用原始对象
↓
createOrder()
↓
成功?
↙ ↘
是 否
↓ ↓
commit rollback
所以:
@Transactional
本身只是告诉Spring:
这个方法需要事务。
真正执行:
开启事务
提交事务
回滚事务
的是:
Spring 的事务拦截器 / 代理机制。
五、AOP 和 Bean 生命周期的关系 ⭐⭐⭐⭐⭐
你前面学过:
实例化
↓
属性填充
↓
Aware
↓
BeanPostProcessor
↓
初始化
↓
BeanPostProcessor
↓
Bean完成
Spring AOP:
和:
BeanPostProcessor
密切相关。
可以先这样理解:
new OrderService()
↓
原始Bean
↓
Bean生命周期
↓
BeanPostProcessor处理
↓
发现需要AOP
↓
创建代理对象
↓
最终Spring Bean
所以:
@Autowired
private OrderService orderService;
拿到的:
可能并不是最开始:
new OrderService()
得到的原始对象。
而可能是:
OrderService代理对象
六、AOP 和三级缓存的关系
你前面学过循环依赖:
三级缓存
singletonFactories
保存:
获取Bean提前引用的ObjectFactory
为什么不能简单地:
直接把原始 Bean 给别人?
因为:
Bean 最终可能需要:
AOP代理
例如:
@Transactional
最终:
原始A
↓
A的代理对象
如果循环依赖的时候:
B拿到原始A
但是其他地方:
拿到A的代理对象
就可能出现引用不一致。
所以:
三级缓存的 ObjectFactory:
可以在需要提前获取 Bean 引用时:
获取提前引用
↓
必要时考虑代理处理
↓
返回合适的提前引用
这也是:
三级缓存和 AOP 关系密切的原因之一。
七、Spring 两种动态代理 ⭐⭐⭐⭐⭐
Spring AOP 常见:
JDK动态代理
CGLIB代理
八、JDK 动态代理
JDK 动态代理:
主要基于:
接口
例如:
public interface OrderService {
void createOrder();
}
实现:
public class OrderServiceImpl
implements OrderService {
@Override
public void createOrder() {
}
}
Spring 可以:
OrderService接口
↑
|
代理对象
|
↓
OrderServiceImpl
简单理解:
JDK 动态代理主要通过接口生成代理对象。
九、CGLIB 动态代理
如果:
没有接口。
例如:
@Service
public class OrderService {
public void createOrder() {
}
}
可以通过:
CGLIB
生成:
OrderService的子类
类似:
OrderService
↑
|
OrderService代理子类
代理子类:
重写方法。
然后:
加入额外逻辑。
例如:
原来:
createOrder()
代理之后可以理解:
createOrder() {
开启事务();
super.createOrder();
提交事务();
}
当然实际源码比这个复杂很多。
这里只需要理解原理。
十、JDK 动态代理和 CGLIB 区别 ⭐⭐⭐⭐⭐
JDK 动态代理
主要基于:
接口
代理:
接口
CGLIB
主要基于:
继承
生成:
目标类的子类
速记:
JDK
↓
接口
CGLIB
↓
继承 / 子类
十一、为什么 final 方法不好代理?
因为:
CGLIB 通过:
继承目标类
↓
重写方法
实现代理。
但是:
public final void save() {
}
final 方法:
不能被子类重写。
所以:
CGLIB 无法通过重写这个方法来进行正常代理增强。
同理:
private void save() {
}
private 方法:
子类也不能重写。
所以:
不能像普通可覆盖方法一样进行代理增强。
十二、为什么 private 方法的 @Transactional 通常不生效? ⭐⭐⭐⭐⭐
例如:
@Transactional
private void saveOrder() {
}
事务依赖:
Spring AOP代理
代理需要:
拦截方法调用。
但是:
private方法
不能像普通可代理方法一样被代理拦截。
所以:
@Transactional
private void saveOrder()
通常不会按你预期产生事务增强。
简单记:
@Transactional
↓
依赖代理
↓
private方法无法正常被代理增强
↓
事务不生效
十三、最经典的坑:自己调用自己 ⭐⭐⭐⭐⭐
例如:
@Service
public class OrderService {
public void createOrder() {
save();
}
@Transactional
public void save() {
// 保存订单
}
}
看起来:
createOrder()
↓
save()
↓
@Transactional
↓
开启事务
但是:
可能不生效。
为什么?
因为:
save();
实际上相当于:
this.save();
属于:
对象内部调用。
正常事务调用:
外部调用
↓
代理对象
↓
事务拦截器
↓
save()
↓
原始对象
但是内部调用:
外部
↓
代理对象
↓
createOrder()
↓
原始对象
↓
this.save()
这里:
this.save()
直接调用当前对象的方法。
没有重新经过:
代理对象
所以:
事务拦截器没有执行
最终:
@Transactional
可能不生效
十四、自调用为什么绕过代理?
假设:
Spring 给:
OrderService
创建:
OrderServiceProxy
正常:
Controller
↓
OrderServiceProxy.save()
↓
事务
↓
OrderService.save()
但是:
public void createOrder() {
this.save();
}
流程:
Controller
↓
OrderServiceProxy.createOrder()
↓
OrderService.createOrder()
↓
this.save()
↓
OrderService.save()
注意:
最后:
OrderService.save()
没有经过:
OrderServiceProxy
所以:
代理无法拦截。
十五、@Async 也是类似原理
例如:
@Async
public void sendEmail() {
}
Spring:
同样可以通过代理机制:
调用代理对象
↓
拦截@Async
↓
把任务提交线程池
↓
异步线程执行
所以:
@Transactional
@Async
AOP日志
虽然功能完全不同:
但底层都大量使用:
Spring代理机制
十六、AOP 常见使用场景
1. 事务
@Transactional
2. 日志
例如:
方法开始
↓
记录参数
↓
执行方法
↓
记录耗时
3. 权限
例如:
请求方法
↓
检查用户权限
↓
有权限
↓
执行方法
4. 接口监控
例如:
开始时间
↓
执行方法
↓
结束时间
↓
计算耗时
5. @Async
方法调用
↓
代理拦截
↓
提交线程池
↓
异步执行
十七、几个 AOP 常见概念
面试可能会问:
切面 Aspect
切点 Pointcut
通知 Advice
连接点 JoinPoint
不用一开始背得特别复杂。
Aspect(切面)
可以理解:
一整套 AOP 逻辑。
例如:
日志切面
负责:
所有日志相关增强。
Pointcut(切点)
表示:
哪些方法需要被增强。
例如:
com.xxx.service下面所有方法
Advice(通知)
表示:
具体增加什么逻辑。
例如:
方法执行前记录日志
方法执行后记录日志
JoinPoint(连接点)
可以简单理解:
可以被 AOP 增强的方法执行位置。
速记:
Aspect
↓
我要做一套什么增强
Pointcut
↓
我要增强哪些方法
Advice
↓
我要增加什么代码
十八、AOP 完整流程 ⭐⭐⭐⭐⭐
例如:
@Transactional
public void createOrder() {
}
可以理解成:
Spring启动
↓
创建OrderService
↓
实例化原始Bean
↓
属性填充
↓
初始化
↓
BeanPostProcessor处理
↓
发现需要AOP增强
↓
创建代理对象
↓
Spring容器管理代理对象
↓
@Autowired注入代理对象
↓
调用createOrder()
↓
代理拦截
↓
开启事务
↓
调用原始createOrder()
↓
是否异常?
↙ ↘
没有 有
↓ ↓
commit rollback
十九、和之前知识串起来 ⭐⭐⭐⭐⭐
现在 Spring 这一条线:
Spring Bean生命周期
↓
实例化
↓
属性填充
↓
初始化
↓
BeanPostProcessor
↓
AOP
↓
创建代理对象
↓
@Transactional / @Async
循环依赖:
Bean实例化
↓
提前暴露
↓
三级缓存
↓
必要时考虑AOP代理
↓
解决部分循环依赖
所以:
Bean生命周期
循环依赖
三级缓存
AOP
@Transactional
这些知识:
其实全部是连在一起的。
🎤 面试回答(30秒)
Spring AOP 是面向切面编程,核心思想是在不修改业务代码的情况下,通过代理对象给方法增加事务、日志、权限等额外功能。
Spring AOP 主要使用 JDK 动态代理和 CGLIB 代理。JDK 动态代理主要基于接口,而 CGLIB 通过生成目标类的子类实现代理。
像 @Transactional 就是典型的 AOP 应用,Spring 创建代理对象,在调用目标方法之前开启事务,方法成功后提交,异常时根据事务规则进行回滚。
🎯 面试追问
Q1:什么是 AOP?
答:
在不修改原业务代码的情况下,通过代理机制在方法执行前后增加额外功能。
Q2:Spring AOP 怎么实现?
答:
主要通过:
动态代理
常见:
JDK动态代理
CGLIB
Q3:JDK 和 CGLIB 有什么区别?
答:
JDK动态代理
↓
主要基于接口
CGLIB
↓
通过继承目标类生成代理子类
Q4:@Transactional 为什么能实现事务?
答:
Spring 会通过 AOP 创建代理对象。
调用事务方法时:
代理对象
↓
开启事务
↓
调用原始方法
↓
提交 / 回滚
Q5:@Transactional 注解自己会开启事务吗?
答:
不会。
注解主要是:
事务配置标记
真正处理事务的是:
Spring事务拦截器
+
代理机制
Q6:为什么 private 方法的 @Transactional 通常不生效?
答:
因为 Spring 声明式事务依赖 AOP 代理,而 private 方法不能像普通可代理方法一样被代理拦截增强。
Q7:为什么同一个类里面调用 @Transactional 方法可能失效?
答:
因为:
this.save();
属于对象内部调用。
没有经过:
Spring代理对象
所以事务拦截器没有执行。
Q8:BeanPostProcessor 和 AOP 什么关系?
答:
Spring AOP 的代理创建和 BeanPostProcessor 机制密切相关,Bean 创建过程中可以通过后置处理器判断 Bean 是否需要增强,并创建代理对象。
Q9:AOP 有哪些使用场景?
答:
事务
日志
权限
监控
异步
⚠️ 工作踩坑
1. 同类自调用导致事务失效
例如:
public void createOrder() {
save();
}
@Transactional
public void save() {
}
这里:
this.save()
没有经过代理。
所以:
事务可能不生效。
2. private 方法加 @Transactional
例如:
@Transactional
private void save() {
}
不要指望它通过普通 Spring AOP 代理正常产生事务增强。
3. 自己 new Service
例如:
OrderService service =
new OrderService();
这个对象:
不是 Spring 容器提供的代理 Bean。
所以:
@Transactional
@Async
AOP
等 Spring 代理能力可能不会生效。
正确:
让 Spring 管理:
@Autowired
private OrderService orderService;
4. 看到 @Transactional 就认为一定有事务
错误。
应该想到:
@Transactional
↓
是否Spring Bean?
↓
是否经过代理?
↓
方法是否可被正常代理?
↓
调用是否绕过代理?
❓ 自测
-
什么是 AOP?
-
AOP 解决什么问题?
-
Spring AOP 的核心实现原理是什么?
-
什么是动态代理?
-
JDK 动态代理和 CGLIB 有什么区别?
-
@Transactional 为什么可以实现事务?
-
@Transactional 注解自己会开启事务吗?
-
Spring Bean 为什么可能不是原始对象?
-
BeanPostProcessor 和 AOP 有什么关系?
-
AOP 和 Bean 生命周期有什么关系?
-
为什么 private 方法的 @Transactional 通常不生效?
-
为什么 final 方法会影响 CGLIB 代理?
-
什么叫 Spring AOP 自调用问题?
-
为什么 this.save() 可能导致事务失效?
-
@Async 和 AOP 有什么关系?
-
AOP 常见使用场景有哪些?
-
Aspect、Pointcut、Advice 分别是什么意思?
-
三级缓存为什么需要考虑 AOP 代理?