0源码基础学习Spring源码系列(二)——Spring如何解决循环依赖
字数 1610 2025-08-11 17:40:26
Spring循环依赖解决机制详解
一、循环依赖概述
循环依赖是指两个或多个Bean相互依赖,形成闭环的情况。例如:
- A依赖B
- B依赖C
- C依赖A
如果不解决循环依赖问题,会导致无限递归调用,最终导致OOM错误。
1.1 Spring支持的循环依赖类型
Spring只能解决以下情况的循环依赖:
- 单例Bean:必须是单例模式的Bean
- 属性或setter注入:通过属性注入或setter方法注入的依赖
- 非构造器注入:不能是通过构造器注入的依赖
二、三级缓存机制
Spring通过三级缓存解决循环依赖问题:
2.1 三级缓存定义
// 一级缓存:存放完全初始化好的Bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 二级缓存:存放提前曝光的原始Bean对象
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
// 三级缓存:存放Bean工厂对象
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
2.2 三级缓存的作用
-
singletonObjects(一级缓存)
- 存放完全初始化好的Bean
- 从该缓存中取出的Bean可以直接使用
-
earlySingletonObjects(二级缓存)
- 存放提前曝光的单例对象
- 存放原始的Bean对象(尚未填充属性)
-
singletonFactories(三级缓存)
- 存放单例对象工厂
- 用于解决循环依赖
三、解决循环依赖的流程
以A依赖B,B依赖A为例:
- 尝试从缓存中加载A,发现A不存在
- 实例化A(半成品)
- 将实例化完成的A放入第三级缓存中
- 装配属性B
- 尝试从缓存中加载B,发现B不存在
- 实例化B
- 将实例化完成的B放入第三级缓存中
- 装配属性A
- 尝试从缓存中加载A,发现A存在于三级缓存中
- 将A从三级缓存中移除
- 放入二级缓存中
- 将A赋值给B
- B装配属性完成
- 初始化B,并将B从三级缓存中移除,放入一级缓存
- 返回第4步,此时A的属性也装配完成
- 初始化A,并将A放入一级缓存
四、代理对象与循环依赖
4.1 代理对象的问题
当存在代理对象时,简单的两级缓存无法解决问题:
- 代理对象是在源对象初始化完成后才创建的
- 如果B依赖的是A的代理对象,而A还在创建过程中,直接返回原始A对象会导致问题
4.2 Spring的解决方案
Spring通过ObjectFactory和getEarlyBeanReference方法解决代理对象问题:
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
// 向三级缓存中添加ObjectFactory
this.singletonFactories.put(beanName, singletonFactory);
// ...
}
当其他Bean需要依赖当前Bean时,会调用ObjectFactory的getObject()方法,最终调用:
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
// 如果需要代理,则返回代理对象
return wrapIfNecessary(bean, beanName, null);
}
4.3 二级缓存的作用
二级缓存的主要作用是避免多次创建代理对象:
- 每次调用
getObject()方法创建代理对象时,都会产生新的代理对象 - 二级缓存保存第一次创建的代理对象,后续直接从二级缓存获取
- 保证单例模式下代理对象的唯一性
五、关键代码分析
5.1 获取单例Bean的核心方法
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 1. 从一级缓存中获取
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
// 2. 从二级缓存中获取
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) {
// 3. 从三级缓存中获取ObjectFactory
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 调用getObject()方法获取Bean
singletonObject = singletonFactory.getObject();
// 放入二级缓存
this.earlySingletonObjects.put(beanName, singletonObject);
// 从三级缓存中移除
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
5.2 提前曝光Bean工厂
boolean earlySingletonExposure = (mbd.isSingleton() &&
this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
六、常见问题解答
6.1 为什么需要三级缓存而不是两级?
- 普通Bean:两级缓存确实可以解决问题
- 代理Bean:需要三级缓存来延迟代理对象的创建
- 三级缓存保存的是
ObjectFactory,可以按需创建代理对象 - 二级缓存保存第一次创建的代理对象,保证唯一性
- 三级缓存保存的是
6.2 代理对象何时被初始化?
- 代理对象内部持有目标对象的引用
- 当原始对象初始化完成后,代理对象也相当于完成了初始化
- 方法调用会委托给原始对象执行
6.3 什么时候返回代理对象?
在Bean初始化完成后,会检查是否需要返回代理对象:
if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
// ...
}
}
- 如果Bean没有被后置处理器修改,则返回二级缓存中的代理对象
- 否则返回原始对象(可能会抛出异常)
七、总结
Spring通过三级缓存机制优雅地解决了循环依赖问题:
-
三级缓存分工明确:
- 一级缓存:完整Bean
- 二级缓存:原始Bean或代理Bean
- 三级缓存:Bean工厂
-
代理对象处理:
- 通过
ObjectFactory延迟创建代理对象 - 保证代理对象的单例性
- 通过
-
设计思想:
- 优先保证普通Bean的创建流程
- 特殊处理循环依赖和代理对象的情况
- 通过缓存机制提高性能
理解Spring的循环依赖解决机制,有助于我们更好地设计Bean之间的依赖关系,避免潜在的循环依赖问题。