Spring 通过 三级缓存 + 提前暴露对象引用 的方式解决单例 Bean 的循环依赖问题。以下是详细流程与原理分析:
一、循环依赖场景
假设存在两个 Bean 互相依赖:
@Component
public class A {
@Autowired
private B b;
}
@Component
public class B {
@Autowired
private A a;
}
二、三级缓存机制
Spring 容器通过以下三个缓存解决循环依赖:
缓存名称 | 存储内容 | 作用 |
---|---|---|
singletonObjects | 完全初始化好的单例 Bean | 存放最终可用的 Bean |
earlySingletonObjects | 早期引用(未完成属性注入的 Bean) | 避免重复创建早期对象 |
singletonFactories | Bean 的 ObjectFactory(工厂对象) | 生成早期引用(解决代理对象问题) |
三、解决流程(以 A → B → A 为例)
-
创建 Bean A
- 实例化 A(调用构造器),生成原始对象
A@123
。 - 将 A 的 ObjectFactory(用于生成早期引用)放入
singletonFactories
。
- 实例化 A(调用构造器),生成原始对象
-
注入 A 的依赖
- Spring 发现 A 依赖 B,开始创建 Bean B。
-
创建 Bean B
- 实例化 B(调用构造器),生成原始对象
B@456
。 - 将 B 的 ObjectFactory 放入
singletonFactories
。
- 实例化 B(调用构造器),生成原始对象
-
注入 B 的依赖
- Spring 发现 B 依赖 A,尝试从缓存获取 A:
- Step 1:从
singletonObjects
获取 → 无。 - Step 2:从
earlySingletonObjects
获取 → 无。 - Step 3:从
singletonFactories
获取 A 的 ObjectFactory → 生成早期引用A@123
,将 A 存入earlySingletonObjects
,并从singletonFactories
移除。
- Step 1:从
- Spring 发现 B 依赖 A,尝试从缓存获取 A:
-
完成 B 的初始化
- 将早期引用
A@123
注入到 B 中 →B@456.a = A@123
。 - B 初始化完成,存入
singletonObjects
,并从earlySingletonObjects
和singletonFactories
移除。
- 将早期引用
-
完成 A 的初始化
- 将初始化后的
B@456
注入到 A 中 →A@123.b = B@456
。 - A 初始化完成,存入
singletonObjects
,并从earlySingletonObjects
和singletonFactories
移除。
- 将初始化后的
四、关键条件与限制
-
仅支持单例 Bean
- 原型(Prototype)作用域的 Bean 无法解决循环依赖,会直接抛出
BeanCurrentlyInCreationException
。
- 原型(Prototype)作用域的 Bean 无法解决循环依赖,会直接抛出
-
依赖注入方式
- 支持 Setter 注入/字段注入:因为依赖注入发生在对象实例化之后。
- 不支持构造器注入:若循环依赖的 Bean 均使用构造器注入,Spring 无法提前暴露对象引用,会导致初始化失败。
-
AOP 代理的兼容性
- 若循环依赖的 Bean 被 AOP 代理(如
@Async
、@Transactional
),Spring 通过singletonFactories
中的 ObjectFactory 生成代理对象的早期引用。
- 若循环依赖的 Bean 被 AOP 代理(如
五、解决方案与代码示例
-
强制打破循环依赖(推荐)
- 重构代码,提取公共逻辑到第三个 Bean。
@Component public class CommonService { // 公共逻辑 }
-
使用 @Lazy 延迟加载
- 在依赖注入时标记
@Lazy
,延迟实际代理对象的创建。
@Component public class A { @Autowired @Lazy // 延迟注入 B 的代理对象 private B b; }
- 在依赖注入时标记
-
改用 Setter 注入
@Component public class A { private B b; @Autowired public void setB(B b) { // Setter 注入 this.b = b; } }
六、异常处理
若无法解决循环依赖,Spring 会抛出:
BeanCurrentlyInCreationException: Error creating bean with name 'a':
Requested bean is currently in creation: Is there an unresolvable circular reference?
排查方法:
- 检查 Bean 的作用域(确保是单例)。
- 检查是否使用了构造器注入。
- 使用
@Lazy
或重新设计依赖关系。
总结
Spring 的三级缓存机制通过 提前暴露对象引用 解决了单例 Bean 的循环依赖问题,但需注意构造器注入和原型 Bean 的限制。优化代码结构、使用 @Lazy
或 Setter 注入是避免循环依赖的最佳实践。