【Spring】Spring是如何解决循环依赖问题的
Spring 通过 三级缓存 和 提前暴露对象引用 的机制解决单例 Bean 的循环依赖问题。以下是详细原理和实现流程:
一、循环依赖的场景
假设存在以下相互依赖的 Bean:
@Component
public class A {
@Autowired
private B b;
}
@Component
public class B {
@Autowired
private A a;
}
此时 Spring 需要处理 A → B → A
的循环依赖。
二、Spring 的解决机制
1. 三级缓存
Spring 使用三级缓存存储不同状态的 Bean:
缓存名称 | 作用 |
---|---|
singletonObjects | 存储完全初始化后的单例 Bean(一级缓存) |
earlySingletonObjects | 存储早期暴露的 Bean(未完成属性填充和初始化,二级缓存) |
singletonFactories | 存储 Bean 的 ObjectFactory(用于生成早期引用,三级缓存) |
2. Bean 创建流程(以 A → B → A 为例)
• 步骤 1:创建 Bean A
- 实例化 A(调用构造函数,但属性未填充)。
- 将 A 的 ObjectFactory 放入 三级缓存(
singletonFactories
)。 - 开始填充 A 的属性,发现依赖 B。
• 步骤 2:创建 Bean B
- 实例化 B(调用构造函数,属性未填充)。
- 将 B 的 ObjectFactory 放入 三级缓存。
- 开始填充 B 的属性,发现依赖 A。
• 步骤 3:解决依赖 A
- 从 三级缓存 中获取 A 的 ObjectFactory,生成 A 的早期引用。
- 将 A 的早期引用放入 二级缓存(
earlySingletonObjects
),并从三级缓存中移除。 - 将 A 的早期引用注入到 B 中,完成 B 的初始化。
- 将 B 的完整实例放入 一级缓存(
singletonObjects
)。
• 步骤 4:完成 Bean A 的初始化
- 将已注入 B 的 A 实例继续初始化。
- 将 A 的完整实例从 二级缓存 移到 一级缓存。
三、核心源码解析
• 关键类:DefaultSingletonBeanRegistry
• 缓存操作:
// 从缓存获取 Bean
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName); // 一级缓存
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
singletonObject = this.earlySingletonObjects.get(beanName); // 二级缓存
if (singletonObject == null && allowEarlyReference) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); // 三级缓存
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
}
return singletonObject;
}
四、限制条件
1. 仅支持单例(Singleton)作用域的 Bean
• 原型(Prototype)作用域的 Bean 无法解决循环依赖,Spring 直接抛出 BeanCurrentlyInCreationException
。
2. 不支持构造器注入的循环依赖
• 构造器注入的循环依赖无法解决(实例化阶段就需要依赖对象,但此时依赖的 Bean 未创建)。
• 错误示例:
@Component
public class A {
private B b;
public A(B b) { this.b = b; } // 构造器注入导致循环依赖无法解决
}
3. 需要启用代理
• 若 Bean 被 AOP 代理(如通过 @Async
、@Transactional
),需确保代理对象能正确暴露到缓存中。
五、如何避免循环依赖?
-
代码设计优化:
• 避免双向依赖,改为单向依赖。
• 使用@Lazy
延迟加载(将依赖标记为惰性初始化)。
• 通过事件驱动(ApplicationEvent
)解耦 Bean。 -
依赖注入方式:
• 优先使用 Setter 注入 而非构造器注入。 -
示例:使用
@Lazy
:@Component public class A { @Autowired @Lazy // 延迟注入 B 的代理对象 private B b; }
六、总结
机制 | 说明 |
---|---|
三级缓存 | 通过缓存不同状态的 Bean 解决循环依赖 |
提前暴露引用 | 在 Bean 未完成初始化前,提前将引用暴露给其他 Bean 使用 |
单例限制 | 仅支持单例 Bean,原型 Bean 和构造器注入的循环依赖无法解决 |
最佳实践:优先通过代码设计避免循环依赖,仅在必要场景下依赖 Spring 的自动解决机制。