目录
Spring框架解决循环依赖
循环依赖是指两个或多个Bean相互依赖,形成闭环的情况(如A依赖B,B又依赖A)。Spring框架通过巧妙的设计解决了部分循环依赖问题,下面解析其实现原理。
1. 循环依赖的类型
Spring能解决的循环依赖主要是单例Bean通过setter/字段注入形成的循环依赖,以下情况无法解决:
- 构造器注入形成的循环依赖
- 原型(prototype)作用域的Bean循环依赖
- 通过
@PostConstruct
方法形成的依赖
2. 三级缓存解决机制
Spring通过三级缓存来解决循环依赖问题:
缓存级别 | 名称 | 存储内容 | 作用阶段 |
第一级缓存 | singletonObjects | 完全初始化好的单例Bean | 成品Bean,可供使用 |
第二级缓存 | earlySingletonObjects | 早期引用(原始对象或代理对象) | 解决循环依赖 |
第三级缓存 | singletonFactories | ObjectFactory(用于生成代理对象) | 创建代理对象的工厂 |
3. 解决流程
以A依赖B,B依赖A为例:
- 开始创建A
- 实例化A(调用构造器),得到一个原始对象
- 将A的ObjectFactory放入三级缓存(singletonFactories)
- 开始填充A的属性(依赖注入阶段)
- 发现A依赖B
- 从容器中查找B
- 如果B不存在,开始创建B
- 开始创建B
- 实例化B(调用构造器),得到一个原始对象
- 将B的ObjectFactory放入三级缓存
- 开始填充B的属性
- 发现B依赖A
- 从容器中查找A:
-
- 一级缓存没有(未完全初始化)
- 检查二级缓存没有
- 从三级缓存获取A的ObjectFactory并执行,得到A的早期引用(可能是代理对象)
- 将A的早期引用放入二级缓存,从三级缓存移除
- 将A的早期引用注入到B
- B完成属性填充,初始化,放入一级缓存
- 回到A的创建
- 从一级缓存获取到已完全初始化的B
- 将B注入到A
- A完成属性填充,初始化
- 将A放入一级缓存,从二级缓存移除
4. 关键源码分析
DefaultSingletonBeanRegistry
类中的核心方法:
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 1. 从一级缓存查询
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
// 2. 从二级缓存查询
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 3. 从三级缓存获取ObjectFactory
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 执行ObjectFactory获取早期引用
singletonObject = singletonFactory.getObject();
// 放入二级缓存
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
5. 为什么构造器注入无法解决循环依赖
构造器注入的循环依赖无法解决是因为:
在构造器调用时,Bean还未创建完成,无法放入三级缓存
没有提前暴露对象引用的机会
Spring的设计选择:宁愿在启动时失败,也不愿在运行时出现不可预知的行为
6. 实际开发中的建议
- 避免循环依赖:
- 重新设计组件结构
- 使用事件驱动或观察者模式解耦
- 必须使用时:
- 使用setter/字段注入而非构造器注入
- 确保是单例Bean