Spring 解决循环依赖的核心原理是 三级缓存(Three-Level Cache) 机制,结合 提前暴露未完全初始化的 Bean 实例。以下是深入分析其实现原理的关键步骤和设计思想:
一、循环依赖的典型场景
假设两个 Bean 互相依赖:
@Component
public class A {
@Autowired
private B b;
}
@Component
public class B {
@Autowired
private A a;
}
此时,Spring 需要解决 A 依赖 B → B 依赖 A 的循环依赖问题。
二、三级缓存的核心机制
Spring 通过三个缓存层解决单例(Singleton)Bean 的循环依赖:
缓存级别 | 存储内容 | 作用 |
---|---|---|
一级缓存 singletonObjects | 完全初始化好的 Bean 实例 | 最终对外暴露的 Bean |
二级缓存 earlySingletonObjects | 提前暴露的 Bean 实例(未完成属性注入和初始化) | 解决循环依赖的中间状态 |
三级缓存 singletonFactories | Bean 的工厂对象(ObjectFactory ) | 生成早期 Bean 实例(可能包含代理对象) |
三、解决循环依赖的详细流程
以下以 A → B → A 的循环依赖为例,解析 Spring 的处理过程:
-
创建 Bean A 的实例
-
调用
A
的构造函数,创建一个原始对象(尚未注入属性)。 -
将 A 的原始对象包装为
ObjectFactory
,存入 三级缓存(singletonFactories
)。addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
-
-
为 Bean A 注入属性 B
- Spring 发现
A
依赖B
,开始创建B
的实例。
- Spring 发现
-
创建 Bean B 的实例
- 调用
B
的构造函数,创建一个原始对象。 - 将 B 的原始对象包装为
ObjectFactory
,存入 三级缓存。
- 调用
-
为 Bean B 注入属性 A
- Spring 发现
B
依赖A
,尝试从缓存中获取A
:- 检查 一级缓存(
singletonObjects
):无。 - 检查 二级缓存(
earlySingletonObjects
):无。 - 从三级缓存 中取出
A
的ObjectFactory
,调用getObject()
生成早期引用(可能是原始对象或代理对象)。 - 将生成的早期引用
A
提升到 二级缓存。
- 检查 一级缓存(
- 将早期引用
A
注入到B
的属性中。 - 完成
B
的属性注入和初始化,将B
放入 一级缓存。
- Spring 发现
-
完成 Bean A 的初始化
- 将完全初始化好的
B
注入到A
中。 - 执行
A
的初始化方法(如@PostConstruct
)。 - 将
A
从 二级缓存 移动到 一级缓存。
- 将完全初始化好的
四、关键源码解析(简化版)
1. DefaultSingletonBeanRegistry
中的缓存定义
public class DefaultSingletonBeanRegistry {
// 一级缓存:完全初始化的 Bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>();
// 二级缓存:未完全初始化的早期 Bean
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>();
// 三级缓存:生成早期 Bean 的工厂
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>();
}
2. AbstractAutowireCapableBeanFactory
中的依赖注入逻辑
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, Object[] args) {
// 1. 创建 Bean 实例(未填充属性)
Object instance = createBeanInstance(beanName, mbd, args);
// 2. 将 Bean 的工厂对象添加到三级缓存(解决循环依赖的关键)
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, instance));
// 3. 属性注入(可能触发依赖 Bean 的创建)
populateBean(beanName, mbd, instance);
// 4. 初始化 Bean(调用初始化方法)
initializeBean(beanName, instance, mbd);
return instance;
}
五、解决循环依赖的条件与限制
-
必须是单例 Bean
原型(Prototype)作用域的 Bean 无法解决循环依赖,因为 Spring 不缓存原型 Bean。 -
依赖注入方式必须是字段注入或 Setter 注入
-
构造器注入无法解决循环依赖,因为构造器调用时 Bean 尚未创建完成,无法存入三级缓存。
-
示例错误场景:
@Component public class A { public A(B b) {} // 构造器注入会导致循环依赖无法解决 }
-
-
Bean 未被 AOP 代理提前增强
如果 Bean 需要通过 AOP 生成代理对象,Spring 会通过getEarlyBeanReference()
方法在三级缓存中提前生成代理对象。
六、设计思想总结
- 空间换时间
通过三级缓存暂存中间状态,避免重复创建对象。 - 提前暴露引用
允许未完全初始化的对象被其他 Bean 引用,打破循环依赖的死锁。 - 分离实例化与初始化
将 Bean 的创建过程分解为多个阶段,确保依赖链的逐步构建。