在 Spring 框架的学习过程中,Bean 的循环依赖问题是一个绕不开的重点和难点。当两个或多个 Bean 之间相互依赖时,若处理不当,很容易导致程序陷入死循环或抛出异常。Spring 通过精妙的三级缓存机制,成功解决了大部分场景下的循环依赖问题。本文将基于学习笔记,详细解析三级缓存的工作原理及循环依赖的解决流程,并结合代码示例帮助理解。
一、三级缓存的构成:各司其职的缓存体系
Spring 为解决循环依赖问题,设计了三级缓存体系,这三个缓存分别承担着不同的职责,共同协作完成 Bean 的创建和依赖注入过程:
- 一级缓存(singletonObjects):存放完全初始化好的 Bean。这些 Bean 已经完成了属性注入、初始化等所有流程,是可以直接使用的成熟 Bean。一级缓存是最终的 Bean 存储容器,应用中获取到的 Bean 大多来自这里。在代码中可表示为:
// 一级缓存:存放完全初始化的单例Bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
- 二级缓存(earlySingletonObjects):存放原始的 Bean 对象(尚未填充属性)。这里的 Bean 虽然已经实例化,但还未进行属性注入和初始化操作,处于一种 “半成品” 状态。二级缓存的主要作用是在循环依赖时,为其他 Bean 提供一个提前暴露的 Bean 引用,避免重复创建对象。代码表示如下:
// 二级缓存:存放提前暴露的原始Bean(未完成属性注入)
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
- 三级缓存(singletonFactories):存放 Bean 的工厂对象,用于创建原始 Bean 对象。工厂对象可以理解为一个创建 Bean 的 “蓝图”,当需要获取 Bean 时,通过工厂可以生成原始的 Bean 实例。三级缓存的存在是为了应对 Bean 在实例化后需要进行 AOP 代理的情况,确保暴露给其他 Bean 的是代理后的对象。代码如下:
// 三级缓存:存放Bean的工厂对象
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
这三级缓存层层递进,既保证了 Bean 的正常创建流程,又巧妙地解决了循环依赖问题。
二、循环依赖解决流程:以 A 依赖 B,B 依赖 A 为例
让我们通过一个具体的循环依赖场景,详细解析三级缓存是如何工作的。首先定义两个相互依赖的 Bean:
// BeanA类,依赖BeanB
public class BeanA {
private BeanB beanB;
// setter方法用于属性注入
public void setBeanB(BeanB beanB) {
this.beanB = beanB;
}
}
// BeanB类,依赖BeanA
public class BeanB {
private BeanA beanA;
// setter方法用于属性注入
public void setBeanA(BeanA beanA) {
this.beanA = beanA;
}
}
接下来模拟循环依赖的解决流程:
步骤 1:创建 A 的实例
- 首先,Spring 容器开始实例化 Bean A,此时的 A 只是一个刚被创建出来的对象,还未进行属性填充,处于 “裸对象” 状态。
// 实例化BeanA
BeanA beanA = new BeanA();
- 紧接着,Spring 将创建 A 的工厂对象放入三级缓存(singletonFactories)中。这个工厂对象可以在需要的时候生成 A 的原始实例,这一步是解决循环依赖的关键,通过提前暴露 Bean 的工厂,为后续其他 Bean 依赖 A 提供了可能。
// 将BeanA的工厂放入三级缓存
singletonFactories.put("beanA", () -> beanA);
- 随后,Spring 开始为 A 填充属性,在这个过程中发现 A 依赖于 Bean B,此时 A 的创建过程暂停,转而开始创建 Bean B。
步骤 2:创建 B 的实例
- 按照同样的流程,Spring 先实例化 Bean B,此时的 B 也处于未填充属性的状态。
// 实例化BeanB
BeanB beanB = new BeanB();
- 然后,将创建 B 的工厂对象放入三级缓存(singletonFactories)中。
// 将BeanB的工厂放入三级缓存
singletonFactories.put("beanB", () -> beanB);
- 接下来,Spring 开始为 B 填充属性,发现 B 依赖于 Bean A。
- 此时,Spring 会去缓存中查找 Bean A,首先检查一级缓存(singletonObjects),发现 A 还未完全初始化,不在一级缓存中;接着检查二级缓存(earlySingletonObjects),也没有 A 的信息;最后检查三级缓存(singletonFactories),找到了创建 A 的工厂对象。
// 查找BeanA的过程
Object beanAFromCache = singletonObjects.get("beanA");
if (beanAFromCache == null) {
beanAFromCache = earlySingletonObjects.get("beanA");
if (beanAFromCache == null) {
ObjectFactory<?> factory = singletonFactories.get("beanA");
if (factory != null) {
beanAFromCache = factory.getObject();
// 将A的原始对象放入二级缓存
earlySingletonObjects.put("beanA", beanAFromCache);
// 删除三级缓存中A的工厂
singletonFactories.remove("beanA");
}
}
}
- 有了 A 的原始对象后,B 的属性填充工作顺利完成,B 成为一个完全初始化好的 Bean,被放入一级缓存(singletonObjects)中。
// 为BeanB填充属性
beanB.setBeanA((BeanA) beanAFromCache);
// 将完全初始化的BeanB放入一级缓存
singletonObjects.put("beanB", beanB);
// 从二级缓存移除BeanB(若有)
earlySingletonObjects.remove("beanB");
// 从三级缓存移除BeanB的工厂
singletonFactories.remove("beanB");
步骤 3:完成 A 的创建
- 当 B 创建完成后,Spring 回到 A 的属性填充过程,此时需要的 B 已经存在于一级缓存中,顺利完成 A 的属性填充。
// 从一级缓存获取BeanB并为BeanA填充属性
BeanB beanBFromCache = (BeanB) singletonObjects.get("beanB");
beanA.setBeanB(beanBFromCache);
- A 的属性填充完成后,进入初始化阶段,完成所有初始化操作后,A 成为一个完全初始化好的 Bean。
- 最后,Spring 将完全初始化好的 A 放入一级缓存(singletonObjects)中,并删除二级缓存(earlySingletonObjects)中 A 的原始对象。
// 将完全初始化的BeanA放入一级缓存
singletonObjects.put("beanA", beanA);
// 从二级缓存移除BeanA
earlySingletonObjects.remove("beanA");
至此,A 和 B 都成功创建完成,循环依赖问题得到解决。
在这个过程中,三级缓存各司其职,一级缓存存放最终可用的 Bean,二级缓存存放提前暴露的原始 Bean,三级缓存存放 Bean 的工厂对象,三者协同工作,让循环依赖的 Bean 能够有序地完成创建。
三、特殊处理:无法解决构造器循环依赖
虽然三级缓存能够解决大多数循环依赖问题,但有一种情况是它无法处理的,那就是构造器循环依赖。
构造器注入发生在 Bean 的实例化阶段,我们来看一个构造器循环依赖的例子:
// 构造器注入导致循环依赖的BeanA
public class BeanA {
private BeanB beanB;
// 构造器注入BeanB
public BeanA(BeanB beanB) {
this.beanB = beanB;
}
}
// 构造器注入导致循环依赖的BeanB
public class BeanB {
private BeanA beanA;
// 构造器注入BeanA
public BeanB(BeanA beanA) {
this.beanA = beanA;
}
}
当创建 Bean A 时,在调用 A 的构造器时就需要 Bean B;而创建 Bean B 时,在调用 B 的构造器时又需要 Bean A。此时,A 和 B 都还没有被实例化,更谈不上将它们的工厂对象放入三级缓存了,所以三级缓存无法发挥作用,Spring 容器会直接抛出BeanCurrentlyInCreationException异常。
这是因为构造器注入的依赖关系在 Bean 实例化之前就需要满足,而三级缓存的工作机制是在 Bean 实例化之后才开始发挥作用的,无法覆盖到构造器注入这个阶段。因此,在实际开发中,应尽量避免使用构造器注入产生循环依赖,可以采用 setter 注入等方式来替代。
四、总结与思考
三级缓存机制是 Spring 框架中一个非常精妙的设计,它通过提前暴露 Bean 的工厂对象,结合三级缓存的协作,成功解决了单例 Bean 之间的循环依赖问题。理解这一机制,不仅有助于我们更好地掌握 Spring 容器的工作原理,也能在实际开发中避免因循环依赖而导致的问题。
需要注意的是,三级缓存只针对单例 Bean 有效,原型 Bean 由于每次获取都会创建新的实例,无法通过缓存来解决循环依赖问题,所以原型 Bean 的循环依赖会直接抛出异常。
通过对三级缓存解决循环依赖问题的学习,结合代码示例的分析,我们可以更深刻地体会到 Spring 框架设计的严谨性和灵活性。在后续的学习中,我们可以进一步调试 Spring 源码,观察三级缓存中数据的变化过程,让知识真正内化为自己的技能。
如果这篇文章对大家有帮助可以点赞关注,你的支持就是我的动力😊!
2304

被折叠的 条评论
为什么被折叠?



