1、问题背景
什么是循环依赖?
循环依赖指的是两个或多个 Bean 互相依赖,如 BeanA 依赖 BeanB,BeanB 又依赖 BeanA。在传统的依赖注入过程中,这种相互依赖会导致死循环或栈溢出,Spring 需要一套机制来优雅解决此问题。
过程示例图:
如何解决循环依赖问题?
Spring 循环依赖的解决方案核心:三级缓存机制
Spring 使用三级缓存缓存正在创建的单例 Bean,利用**“提前曝光”**的方式断开循环依赖链,解决循环依赖的问题
2、三级缓存解决方案(DefaultSingletonBeanRegistry)
主要解决方案是在:package org.springframework.beans.factory.support.DefaultSingletonBeanRegistry
类,其中getSingleton
里面包含有三级缓存依赖的解决方案
2.1、三级缓存结构
变量名 | 作用总结 |
---|---|
singletonObjects | 一级缓存,完全初始化好的 Bean |
earlySingletonObjects | 二级缓存,提前曝光的半成品 Bean |
singletonFactories | 三级缓存,创建半成品 Bean 的工厂 |
registeredSingletons | 已注册单例 Bean 名称集合 |
singletonsCurrentlyInCreation | 正在创建中的 Bean 名称集合 |
inCreationCheckExclusions | 不参与创建检查的 Bean 名称集合 |
suppressedExceptions | 被抑制异常集合 |
singletonsCurrentlyInDestruction | 是否正在销毁单例 Bean |
disposableBeans | 需要销毁的 Bean 实例集合 |
containedBeanMap | Bean 包含关系 |
dependentBeanMap | Bean 的依赖者关系 |
dependenciesForBeanMap | Bean 依赖关系 |
Spring 中的三级缓存主要用于单例 Bean 的生命周期管理,特别是在循环依赖时,它通过不同阶段暴露 Bean 实例来确保依赖注入的顺利完成。缓存的内容如下:
-
一级缓存 (singletonObjects):存储完全初始化的单例 Bean。该缓存中存储的是已经完成初始化的 Bean 对象,包括所有的属性注入和生命周期回调方法(如 @PostConstruct)都已完成。
-
二级缓存 (earlySingletonObjects):存储实例化但尚未完成所有初始化步骤的 Bean 引用。也就是 Bean 已经完成了实例化,并完成了依赖注入(如字段注入),但可能还没有执行初始化方法(如 @PostConstruct)。二级缓存用于解决循环依赖问题,当需要注入的 Bean 还没有完全初始化时,可以先返回该 Bean 的早期引用。
-
三级缓存 (singletonFactories):存储的是创建 Bean 的工厂(ObjectFactory),而不是直接存储 Bean 实例。这些工厂延迟返回真正的 Bean 实例,直到它们完成初始化。三级缓存用于处理 循环依赖 的核心,当一个 Bean 处于创建过程中,另一个 Bean 依赖它时,可以从三级缓存中获取一个工厂对象,该工厂会在 Bean 完全初始化后,生成一个完整的 Bean 实例。
2.2、三级缓存解决代码详解
@Nullable
public Object getSingleton(String beanName) {
return this.getSingleton(beanName, true);
}
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 先从一级缓存(完全初始化的Bean)中查找
Object singletonObject = this.singletonObjects.get(beanName);
// 如果一级缓存没找到,并且该Bean正处于创建中(避免重复创建)
if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
// 尝试从二级缓存(早期曝光的半成品)中查找
singletonObject = this.earlySingletonObjects.get(beanName);
// 如果二级缓存也没有,并且允许获取早期引用
if (singletonObject == null && allowEarlyReference) {
// 加锁,保证下面操作的线程安全
synchronized(this.singletonObjects) {
// 再次检查一级缓存(防止其他线程已初始化完成)
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
// 再次从二级缓存检查
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
// 从三级缓存获取ObjectFactory,创建早期半成品Bean
ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 通过工厂创建Bean的早期引用
singletonObject = singletonFactory.getObject();
// 放入二级缓存
this.earlySingletonObjects.put(beanName, singletonObject);
// 从三级缓存移除工厂,避免重复创建
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
}
总结
singletonObject
加锁的目的:
1、代码使用了经典的双重检查锁定模式
// 第一次检查(无锁)
if (singletonObject == null && allowEarlyReference) {
synchronized(this.singletonObjects) {
// 第二次检查(有锁)
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
// ...
}
}
}
2、 不加锁的核心问题
最关键的线程安全问题是:
// 无锁情况下,多个线程可能同时执行:
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject(); // 可能被多次调用
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
这会导致同一个ObjectFactory被多次调用,创建多个实例,违反单例约束。
2.3、执行流程
-
一级缓存 (singletonObjects):
检查一级缓存,获取完全初始化的 Bean。
如果一级缓存中存在该对象,则直接返回。 -
二级缓存 (earlySingletonObjects):
如果一级缓存中没有找到该对象,且该对象正在创建过程中(this.singletonsCurrentlyInCreation),则检查二级缓存。
如果二级缓存中存在早期暴露的 Bean,则直接返回。 -
三级缓存 (singletonFactories):
如果二级缓存中也没有,并且允许提前引用(allowEarlyReference 为 true),则从三级缓存中获取工厂对象(ObjectFactory)。
调用 ObjectFactory.getObject() 方法生成 Bean 实例。
将生成的实例放入二级缓存,同时从三级缓存中移除对应的工厂。
3、扩展
📍 定位核心类和方法
Spring 解决循环依赖的核心代码在:
- 类:
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry
- 关键方法:
- getSingleton(String beanName):获取单例
- addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory):添加早期工厂
- addSingleton(String beanName, Object singletonObject):添加单例对象