Spring 在处理 循环依赖 时使用了 三级缓存机制,这是 Singleton Bean 创建过程中为了解决循环依赖的一种策略。通过三级缓存,Spring 可以提前暴露未完全初始化的对象,避免循环依赖导致的无限递归错误。
1. 三级缓存的结构
Spring IOC 容器的 三级缓存分为以下三个部分:
-
一级缓存(singletonObjects)
- 存储的是完全初始化完成的单例对象。
- 类型:
ConcurrentHashMap<String, Object>
- 用于直接获取初始化完成的 bean。
-
二级缓存(earlySingletonObjects)
- 存储的是早期暴露的单例对象(已实例化但尚未完全初始化的 bean)。
- 类型:
ConcurrentHashMap<String, Object>
- 用于解决部分场景下的循环依赖问题,避免不必要的代理创建。
-
三级缓存(singletonFactories)
- 存储的是ObjectFactory(对象工厂),用于生成 bean 的代理或延迟引用。
- 类型:
ConcurrentHashMap<String, ObjectFactory<?>>
- 用于在需要时生成早期对象的引用,并放入二级缓存。
2. 三级缓存的工作流程
当遇到循环依赖时,Spring 会按照以下步骤来创建和管理 bean:
-
检查一级缓存:
- 从
singletonObjects
中查找,如果找到,说明这个 bean 已完全初始化,直接返回。
- 从
-
检查二级缓存:
- 如果一级缓存中没有,则从
earlySingletonObjects
(二级缓存)中查找。 - 如果找到,说明这个 bean 还没完全初始化,但可以提供引用,直接返回。
- 如果一级缓存中没有,则从
-
检查三级缓存:
- 如果一级和二级缓存都没有,从
singletonFactories
(三级缓存)中获取一个对象工厂,调用工厂方法创建早期引用,并放入二级缓存。
- 如果一级和二级缓存都没有,从
-
放入一级缓存:
- 完成 bean 的初始化后,将最终的对象存入
singletonObjects
(一级缓存),并从二级、三级缓存中移除。
- 完成 bean 的初始化后,将最终的对象存入
3. 示例:如何解决循环依赖
假设存在以下两个类:A
和 B
,它们互相依赖。
class A {
private B b;
public A(B b) {
this.b = b;
}
}
class B {
private A a;
public void setA(A a) {
this.a = a;
}
}
在 Spring 容器中创建这两个对象时,会出现循环依赖:
- Spring 尝试实例化
A
,但A
的构造器依赖B
。 - 容器开始创建
B
,但B
的setA()
方法又依赖A
。 - 为了解决这个循环依赖问题,Spring 将未完全初始化的
A
的引用提前暴露到二级缓存中。 - 在创建
B
时,B
可以通过二级缓存获取A
的引用,完成注入。 - 最终,
A
和B
都完成初始化,并分别存入一级缓存。
4. 代码中的核心逻辑
Spring 的核心实现位于 DefaultSingletonBeanRegistry
类中。
- 三级缓存的使用逻辑大致如下:
// 从一级缓存中获取 bean
Object singletonObject = singletonObjects.get(beanName);
if (singletonObject == null) {
// 如果一级缓存没有,则尝试从二级缓存获取
singletonObject = earlySingletonObjects.get(beanName);
if (singletonObject == null) {
// 如果二级缓存也没有,则从三级缓存的 ObjectFactory 中获取
ObjectFactory<?> singletonFactory = singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
// 提前暴露到二级缓存中
earlySingletonObjects.put(beanName, singletonObject);
singletonFactories.remove(beanName);
}
}
}
return singletonObject;
5. 为什么需要三级缓存?
- 三级缓存的作用: 提前暴露一个未完全初始化的对象(或其代理)引用,避免循环依赖导致的死锁问题。
- 为什么不直接使用二级缓存?
- 二级缓存只能存储已经创建出的对象引用,但某些代理对象需要延迟创建。三级缓存通过 对象工厂(ObjectFactory) 延迟创建这些代理,灵活性更高。
6. 适用场景与限制
-
适用于 Setter 注入:
- Spring 通过三级缓存可以处理 Setter 注入 场景中的循环依赖。
-
不适用于构造注入:
- 如果两个 bean 通过构造器相互依赖,Spring 无法通过三级缓存解决,因为构造器必须一次性注入所有依赖。
7. 总结
- 一级缓存: 存储完全初始化的对象。
- 二级缓存: 存储早期暴露的对象引用,用于解决循环依赖。
- 三级缓存: 存储对象工厂,用于延迟创建代理对象,并转移到二级缓存中。
三级缓存机制帮助 Spring 处理 Setter 注入 引发的循环依赖问题,但对于 构造器注入 的循环依赖无能为力。