Spring 通过三级缓存机制和提前暴露未初始化 Bean的方式,自动解决了setter 注入和字段注入场景下的循环依赖问题。
一、三级缓存的核心概念
Spring 容器在创建 Bean 的过程中,使用三个缓存来管理 Bean 的生命周期:
- 单例池(一级缓存):
singletonObjects
存储已经完全初始化(属性注入完成)的单例 Bean。 - 提前暴露的单例对象(二级缓存):
singletonFactories
存储原始的 Bean 对象(未完成属性注入),但通过工厂可以获取早期引用。 - 单例工厂(三级缓存):
earlySingletonObjects
存储 Bean 的工厂对象,用于生成早期暴露的 Bean 引用。
二、自动解决循环依赖的流程
假设存在循环依赖:A 依赖 B,B 依赖 A。
1. 创建 Bean A
-
A 进入创建状态,将 A 的工厂对象放入三级缓存:
// 三级缓存:存储A的工厂 singletonFactories.put("a", () -> getEarlyBeanReference(beanName, mbd, bean));
此时 A 尚未完成属性注入,但已通过工厂暴露了早期引用。
-
注入 A 的依赖 B:
Spring 发现 B 不存在,开始创建 B。
2. 创建 Bean B
- B 进入创建状态,将 B 的工厂对象放入三级缓存。
- 注入 B 的依赖 A:
Spring 从缓存中查找 A,发现 A 在三级缓存中,通过工厂获取 A 的早期引用,并将 A 从三级缓存移至二级缓存。// 二级缓存:存储A的早期引用 earlySingletonObjects.put("a", getEarlyBeanReference(beanName, mbd, bean));
- B 完成属性注入,并将 B 放入一级缓存(单例池)。
3. 继续完成 A 的创建
- A 获取到已完成的 B,完成自身属性注入。
- A 放入一级缓存,循环依赖解决。
三、关键机制解析
1. 为什么构造器注入无法解决循环依赖?
- 构造器注入在实例化阶段就需要完成依赖注入,而此时 Bean 尚未放入缓存(包括三级缓存)。
- 若 A 的构造器依赖 B,B 的构造器依赖 A,会导致:
- 创建 A 时,因需要 B 而暂停。
- 创建 B 时,因需要 A 而暂停。
- 形成死循环,最终抛出
BeanCurrentlyInCreationException
。
2. 为什么 setter / 字段注入可以解决?
- setter / 字段注入在实例化后进行,此时 Bean 已通过三级缓存暴露早期引用。
- 当 B 需要 A 时,可以从三级缓存获取 A 的早期引用(尽管 A 尚未完成初始化),从而打破循环。
3. 三级缓存的作用
- 三级缓存(singletonFactories):
存储 Bean 的工厂对象,允许在 Bean 未完全初始化时通过getEarlyBeanReference()
方法生成早期引用。这一步可能涉及 AOP 代理的创建(如果 Bean 需要被代理)。 - 二级缓存(earlySingletonObjects):
存储早期引用的实际对象,避免重复创建代理或处理。 - 一级缓存(singletonObjects):
最终存储完全初始化的 Bean,确保用户获取到的是完整可用的实例。
四、源码验证
Spring 在DefaultSingletonBeanRegistry
类中实现了三级缓存逻辑:
// 三级缓存
private final Map<String, ObjectFactory<?>> singletonFactories = new ConcurrentHashMap<>(16);
// 二级缓存
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
// 一级缓存
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
关键方法getSingleton()
展示了缓存查询顺序:
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 1. 先从一级缓存获取
Object singletonObject = this.singletonObjects.get(beanName);
// 2. 如果Bean正在创建中
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
// 3. 从二级缓存获取
singletonObject = this.earlySingletonObjects.get(beanName);
// 4. 二级缓存没有,且允许早期引用,则从三级缓存通过工厂获取
if (singletonObject == null && allowEarlyReference) {
synchronized (this.singletonObjects) {
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 通过工厂生成早期引用
singletonObject = singletonFactory.getObject();
// 将早期引用放入二级缓存
this.earlySingletonObjects.put(beanName, singletonObject);
// 从三级缓存移除工厂
this.singletonFactories.remove(beanName);
}
}
}
}
}
return singletonObject;
}
五、总结
Spring 解决循环依赖的核心是:通过三级缓存提前暴露未初始化的 Bean,允许其他 Bean 引用这个早期实例。这一机制只适用于:
- 单例作用域的 Bean(原型作用域每次创建新实例,无法解决循环依赖)。
- setter / 字段注入(构造器注入因实例化阶段依赖未解决而失败)。
若项目中存在复杂的循环依赖,建议优先重构代码设计,而非依赖此机制。