🔍 Spring 为什么需要三级缓存?
—— 一文讲清循环依赖与 AOP 的一致性难题
作者: 赫英年
关键词: Spring、三级缓存、循环依赖、AOP、代理、对象一致性
🌰 一、问题场景
@Service
public class UserService {
@Autowired
private OrderService orderService;
@Transactional // 需要事务控制 → 必须通过代理执行
public void createUserOrder() {
orderService.createOrder();
}
}
@Service
public class OrderService {
@Autowired
private UserService userService; // 循环依赖
}
- 存在循环依赖 ✅
UserService
标有@Transactional
✅- Spring 正常启动,但事务可能不生效
为什么?
🧱 二、三级缓存是哪三级?
缓存 | 作用 |
---|---|
一级:singletonObjects | 存最终成品 Bean |
二级:earlySingletonObjects | 存早期引用(原始或代理) |
三级:singletonFactories | 存 ObjectFactory ,延迟创建早期引用 |
✅ 三级缓存存的不是对象,而是一个工厂:
() -> getEarlyBeanReference(bean)
🚫 三、如果只有二级缓存(存原始对象)
假设流程如下:
- 创建
UserService
→ 实例化 → 放入二级缓存(原始对象) OrderService
依赖UserService
→ 从二级缓存拿到原始对象OrderService
持有原始UserService
UserService
继续初始化 →BeanPostProcessor
创建代理对象- 代理对象放入一级缓存
💡 什么是“代理对象”?它和“原始对象”有什么区别?
- 原始对象:就是
new UserService()
得到的实例,没有任何增强。 - 代理对象:Spring 通过 AOP(如 CGLIB 或 JDK 动态代理)生成的“包装对象”,它:
- 持有原始对象
- 在调用方法前/后自动织入逻辑,比如:
- 开启事务(
@Transactional
) - 缓存读写(
@Cacheable
) - 日志记录、权限校验等
- 开启事务(
🔑 关键:只有通过代理对象调用方法,这些增强逻辑才会生效!
⚠️ 问题爆发:对象“分裂”!
组件 | 持有实例 | 类型 | 说明 |
---|---|---|---|
OrderService | 原始 UserService | ❌ 无代理 | 调用方法 → 直接执行,不走代理 |
其他组件 | 代理 UserService | ✅ 有事务 | 调用方法 → 先开启事务,再执行 |
🔴 后果:
OrderService
调用userService.createUserOrder()
→ 调的是原始对象的方法
→ 不开启事务 → 即使抛异常,数据库操作也不会回滚!- 同一个 Bean 有两个版本 → 违反单例原则,系统行为不一致
✅ 四、Spring 的正确解:三级缓存延迟决策
Spring 的精妙设计:
- 创建
UserService
→ 实例化 - 将
ObjectFactory
放入三级缓存:() -> getEarlyBeanReference(userService)
OrderService
需要UserService
→ 调用工厂的getObject()
- 此时执行
getEarlyBeanReference()
:- 判断是否有
@Transactional
- 需要 → 创建代理
- 放入二级缓存
- 判断是否有
OrderService
持有的是代理对象UserService
后续初始化完成 → 直接使用已有代理 → 放入一级缓存
✅ 最终状态:对象唯一,代理一致
组件 | 持有实例 | 类型 |
---|---|---|
OrderService | 代理 UserService | ✅ |
其他组件 | 代理 UserService | ✅ |
🎯 无论谁调用,都走代理 → 事务生效
🌟 五、核心结论
问题 | 答案 |
---|---|
为什么不能只用二级缓存? | 会存原始对象,导致 AOP 失效 |
为什么需要三级缓存? | 通过 ObjectFactory 延迟决策,按需创建代理 |
三级缓存的本质? | 解决“提前暴露”与“AOP一致性”的矛盾 |
最终目标? | 保证整个容器中只有一个 Bean 实例(且正确) |
💡 六、一句话总结
如果没有三级缓存的“延迟代理”机制,二级缓存只能存原始对象,就会导致“一个 Bean 有两个版本(原始 + 代理)”,造成 AOP 失效。三级缓存通过 ObjectFactory 延迟决策,确保了代理的正确性和对象的唯一性。
📚 参考源码
// SmartInstantiationAwareBeanPostProcessor.java
default Object getEarlyBeanReference(Object bean, String beanName) {
return bean;
}
AOP 模块(如 AnnotationAwareAspectJAutoProxyCreator
)会重写此方法,在需要时返回代理。
✅ 适合场景: 技术短分享、团队周会、面试复盘
✅ 特点: 重点突出、逻辑清晰、概念明确