Spring循环依赖与三级缓存:原理剖析与记忆技巧
一、循环依赖问题复现
场景描述
当两个Bean相互依赖时,Spring容器初始化会触发循环依赖问题:
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
}
@Service
public class ServiceB {
@Autowired
private ServiceA serviceA;
}
异常现象
控制台报错:Requested bean is currently in creation: Is there an unresolvable circular reference?
二、三级缓存原理剖析
缓存结构(DefaultSingletonBeanRegistry)
- 一级缓存
singletonObjects:存放完整初始化的Bean - 二级缓存
earlySingletonObjects:存放早期暴露的原始对象 - 三级缓存
singletonFactories:存放对象工厂(ObjectFactory)
Bean创建流程
-
创建ServiceA实例
- 实例化后立即将工厂对象存入三级缓存:因此第三步B实例可以从三级缓存取到A实例的工厂
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); -
属性注入ServiceB
- 触发ServiceB的创建流程
-
创建ServiceB实例
- 同样将工厂存入三级缓存
- 注入ServiceA时触发三级缓存升级:
graph TD B_填充A属性 --> 检查一级缓存 一级缓存无 --> 检查二级缓存 二级缓存无 --> 从三级缓存获取工厂 执行工厂方法 --> 原始A对象存入二级缓存 删除三级缓存记录
-
完成ServiceB初始化
- ServiceB进入一级缓存
-
继续完成ServiceA初始化
- 从二级缓存获取原始对象
- 执行后续初始化步骤
- 最终ServiceA进入一级缓存
三、三级缓存记忆技巧
形象比喻
- 一级缓存:成品仓库(完整可用的Bean)
- 二级缓存:半成品货架(已实例化未初始化的Bean)
- 三级缓存:工厂流水线(对象生产车间)
记忆口诀
一级成品直接拿
二级半成品凑合用
三级工厂保平安
循环依赖全靠它
四、设计思想总结
- 空间换时间:通过缓存中间状态避免重复创建
- 提前暴露引用:允许未完成初始化的对象被引用
- 代理对象处理:通过ObjectFactory延迟生成代理对象
- 状态隔离:三级缓存确保不同状态对象互不干扰
注意事项:构造器注入无法通过三级缓存解决循环依赖,建议优先使用属性注入方式