在日常的Spring开发中,循环依赖是一个高频出现的问题,也是面试中的核心考点。本文将从概念定义、问题表现、核心原理到源码层面,全方位解析Spring是如何通过三级缓存机制优雅地解决单例Bean的循环依赖问题。
一、什么是循环依赖?
循环依赖,指的是两个或多个Bean之间互相持有对方的引用,形成闭环依赖关系。最典型的场景是Bean A依赖Bean B,同时Bean B又依赖Bean A。
代码示例:
java
@Component
class A {
// A依赖B
@Resource
private B b;
}
@Component
class B {
// B依赖A,形成循环
@Resource
private A a;
}
在默认情况下,如果Spring不做特殊处理,项目启动时会抛出BeanCurrentlyInCreationException异常,提示存在循环依赖无法解决:
二、Spring解决循环依赖的核心:三级缓存
为了解决单例Bean的循环依赖问题,Spring设计了三级缓存机制,通过提前暴露半成品Bean的方式打破依赖闭环。
三级缓存的定义
| 缓存级别 | 缓存名称 | 作用 |
|---|---|---|
| 一级缓存 | singletonObjects |
存放完全初始化完成的单例Bean(成品对象),供业务直接使用 |
| 二级缓存 | earlySingletonObjects |
存放提前暴露的半成品Bean(已实例化但未完成属性填充和初始化) |
| 三级缓存 | singletonFactories |
存放ObjectFactory(对象工厂),这是一个函数式接口,仅在调用getObject()时才会创建Bean实例 |
三、核心源码解析(基于 Spring 5.3.x)
Spring处理Bean创建和循环依赖的核心逻辑集中在DefaultSingletonBeanRegistry类中,以下是关键源码及解析:
java
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
// 一级缓存:存放完全初始化好的单例Bean (成品)
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 三级缓存:存放Bean的工厂对象,用于创建提前暴露的Bean (半成品工厂)
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
// 二级缓存:存放提前暴露的Bean实例 (半成品,未完成属性填充和初始化)
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
// 记录当前正在创建的Bean名称,解决循环依赖的关键判断
private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16));
/**
* 核心方法:获取单例Bean(解决循环依赖的入口)
* @param beanName Bean名称
* @param allowEarlyReference 是否允许提前引用半成品Bean
* @return 单例Bean实例
*/
@Nullable
public Object getSingleton(String beanName, boolean allowEarlyReference) {
// 第一步:优先从一级缓存获取成品Bean
Object singletonObject = this.singletonObjects.get(beanName);
// 如果一级缓存没有,且当前Bean正在创建中(循环依赖的核心判断条件)
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
// 第二步:从二级缓存获取提前暴露的半成品Bean
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
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 通过工厂创建半成品Bean(提前暴露的核心操作)
singletonObject = singletonFactory.getObject();
// 将半成品Bean放入二级缓存,同时移除三级缓存(避免重复创建)
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
/**
* 将Bean工厂放入三级缓存(提前暴露Bean的关键步骤)
* 在Bean实例化后、属性填充前调用
*/
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
/**
* 将完全初始化的Bean放入一级缓存,清理二、三级缓存
* 在Bean初始化完成后调用
*/
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
this.singletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
this.singletonsCurrentlyInCreation.remove(beanName);
}
}
}
调试关键方法(建议收藏)
在实际调试Spring源码时,建议重点关注以下核心方法的调用链路:
getBean()- Bean获取的入口方法doGetBean()- 获取Bean的核心实现createBean()- 创建Bean的顶层方法doCreateBean()- 创建Bean的核心逻辑createBeanInstance()- Bean实例化(创建空对象)populateBean()- Bean属性填充(依赖注入的核心)
四、循环依赖解决完整流程(以A和B为例)
结合上文A依赖B、B依赖A的场景,我们拆解Spring解决循环依赖的完整执行流程(基于单例Bean + 字段注入):
步骤 1:Spring启动,开始创建Bean A
- 调用
getBean(A),首先将A标记为「正在创建中」(singletonsCurrentlyInCreation.add("A")); - 通过反射创建A的空实例(ctro.newInstance()),此时A的属性b为null(实例化阶段);
- 关键操作:调用
addSingletonFactory()将A的ObjectFactory放入三级缓存; - 开始为A填充属性,发现依赖B,触发
getBean(B)。
步骤 2:创建Bean B(触发循环依赖)
- 调用
getBean(B),将B标记为「正在创建中」; - 通过反射创建B的空实例(ctro.newInstance()),此时B的属性a为null;
- 调用
addSingletonFactory()将B的ObjectFactory放入三级缓存; - 开始为B填充属性,发现依赖A,再次触发
getBean(A)。
步骤 3:解决循环依赖(从缓存获取A)
- 执行
getBean(A),检查一级缓存:A未完成初始化,无成品; - 检查标记:A处于「正在创建中」,符合循环依赖条件;
- 检查二级缓存:无A的半成品实例;
- 检查三级缓存:存在A的
ObjectFactory,调用getObject()创建A的半成品实例; - 将A的半成品实例从三级缓存移至二级缓存;
- 将半成品A返回给B,完成B的属性a填充。
步骤 4:B完成初始化,反馈给A
- B完成属性填充,执行初始化方法(
init-method/@PostConstruct); - 调用
addSingleton(B),将B放入一级缓存,并清理其二、三级缓存; - 将成品B返回给A,完成A的属性b填充。
步骤 5:A完成初始化,最终入池
- A完成属性填充,执行初始化方法;
- 调用
addSingleton(A),将A放入一级缓存,清理其二、三级缓存; - 移除A的「正在创建中」标记,循环依赖问题解决。
补充说明:
加入三级缓存后的Bean创建流程可参考下图:
五、关键细节:为什么需要三级缓存?
核心原因是为了支持AOP动态代理:
- 延迟创建代理对象 :
ObjectFactory的getObject()方法中会调用getEarlyBeanReference(),该方法会判断当前Bean是否需要生成AOP代理。只有发生循环依赖时,才会提前创建代理对象; - 保证代理对象的唯一性:如果没有三级缓存,所有Bean都需要提前创建代理,破坏了Spring「初始化完成后再创建代理」的设计原则;
- 避免重复代理:三级缓存的工厂模式确保代理对象只会被创建一次,放入二级缓存后就移除三级缓存,避免重复生成。
如果仅使用二级缓存,所有Bean都必须在实例化阶段就创建代理,这会导致:
- 代理对象创建时机提前,不符合Spring的初始化生命周期
- 无循环依赖的Bean也会被提前代理,增加不必要的性能开销
总结
- Spring通过三级缓存机制解决单例Bean的循环依赖问题,核心是提前暴露半成品Bean打破依赖闭环;
- 三级缓存各司其职:一级缓存存成品、二级缓存存半成品、三级缓存存工厂(支持AOP延迟代理);
- 解决循环依赖的核心流程是:实例化Bean → 放入三级缓存 → 填充属性触发循环 → 从缓存获取半成品 → 完成初始化放入一级缓存。