Spring三级缓存解决循环依赖的深度解析
一、问题背景:什么是循环依赖?
在Spring应用中,当两个或多个Bean相互依赖时,会形成循环依赖。例如:
@Service
public class A {
@Autowired
private B b;
}
@Service
public class B {
@Autowired
private A a;
}
这种场景下,Spring需要解决两个核心问题:
- 如何避免Bean创建时的死锁?
- 如何保证AOP代理对象的正确性?
二、Spring的三级缓存结构
Spring通过三个层级的缓存管理Bean的生命周期:
缓存层级 | 存储内容 | 作用 |
---|---|---|
一级缓存(singletonObjects) | 完全初始化的Bean | 直接对外提供可用的Bean |
二级缓存(earlySingletonObjects) | 提前暴露的早期Bean(未初始化) | 解决循环依赖时的临时存储 |
三级缓存(singletonFactories) | ObjectFactory 工厂对象 | 延迟生成代理对象,保证一致性 |
三、三级缓存解决循环依赖的流程
以A、B循环依赖为例,详细流程如下:
1. 创建Bean A
- 实例化A:通过构造函数创建原始对象(未注入属性、未初始化)。
- 暴露早期引用:将A的工厂对象存入三级缓存:
singletonFactories.put("a", () -> getEarlyBeanReference(bean));
- 属性填充:尝试注入B,触发Bean B的创建。
2. 创建Bean B
- 实例化B:同理,创建B的原始对象,暴露工厂到三级缓存。
- 属性填充:尝试注入A:
- 从三级缓存获取A的
ObjectFactory
,生成早期引用(若需要代理,此时生成)。 - 将A的早期引用升级到二级缓存。
- 从三级缓存获取A的
- 完成B的初始化:执行
@PostConstruct
,移入一级缓存。
3. 完成Bean A的创建
- 注入B:从一级缓存获取已初始化的B。
- 完成A的初始化:执行
@PostConstruct
,移入一级缓存,清理二级/三级缓存。
四、为什么必须使用三级缓存?
1. 处理AOP代理的矛盾
- 问题:若直接暴露原始实例,后续生成的代理对象会导致注入不一致。
- 解决方案:通过三级缓存的
ObjectFactory
,确保首次访问时生成代理,后续所有引用指向同一代理对象。
2. 避免过早代理
- 问题:若在二级缓存直接生成代理,可能错过
@PostConstruct
等初始化逻辑。 - 解决方案:三级缓存延迟代理生成,仅在真正需要时触发。
3. 保证线程安全
- 通过分层设计,确保在多线程环境下,早期引用的生成和暴露是原子操作。
五、代码示例(Spring源码片段)
protected Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
ObjectFactory<?> factory = this.singletonFactories.get(beanName);
if (factory != null) {
// 通过工厂生成早期引用(代理在此生成)
singletonObject = factory.getObject();
// 升级到二级缓存
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
六、总结
Spring三级缓存的设计巧妙地平衡了以下需求:
- 循环依赖的解决:通过提前暴露未初始化对象的引用。
- AOP代理的一致性:通过工厂模式延迟生成代理对象。
- 生命周期管理:分层缓存隔离不同阶段的Bean状态。
这种机制既保证了Bean的完整初始化,又兼容了Spring AOP等高级特性,是Spring框架高度灵活和健壮的关键设计之一。