Spring 通过三级缓存机制解决单例 Bean 的循环依赖问题,核心思想是提前暴露未完全初始化的 Bean 引用。以下是详细原理和流程:
一、循环依赖的场景
假设存在两个 Bean:
java
复制
@Component public class A { @Autowired private B b; } @Component public class B { @Autowired private A a; }
Spring 需要创建 A
和 B
,但两者互相依赖,形成循环依赖。
二、三级缓存的作用
Spring 用三个 Map(缓存)管理 Bean 的创建过程:
一级缓存
singletonObjects
存储完全初始化的单例 Bean,可直接使用。二级缓存
earlySingletonObjects
存储未完全初始化的 Bean(已实例化但未填充属性)。三级缓存
singletonFactories
存储 Bean 的ObjectFactory
,用于生成早期 Bean 的引用。
三、解决流程(以 A 和 B 为例)
以下是关键步骤说明:
步骤 1:创建 Bean A
调用
getBean("A")
,检查一级缓存singletonObjects
,未找到。实例化 A(调用构造函数),但此时 A 尚未填充属性
b
。将 A 的
ObjectFactory
放入三级缓存singletonFactories
。
步骤 2:填充 Bean A 的属性
尝试为 A 注入属性
b
,调用getBean("B")
。检查一级缓存
singletonObjects
,未找到 B。
步骤 3:创建 Bean B
实例化 B(调用构造函数),此时 B 尚未填充属性
a
。将 B 的
ObjectFactory
放入三级缓存singletonFactories
。
步骤 4:填充 Bean B 的属性
尝试为 B 注入属性
a
,调用getBean("A")
。检查一级缓存
singletonObjects
,未找到 A。检查三级缓存
singletonFactories
,找到 A 的ObjectFactory
,调用其getObject()
生成早期引用。将 A 的早期引用从三级缓存移到二级缓存
earlySingletonObjects
。将 A 的早期引用注入到 B 中,完成 B 的属性填充。
B 初始化完成,放入一级缓存
singletonObjects
。
步骤 5:完成 Bean A 的初始化
将 B 的实例注入到 A 中。
A 初始化完成,从二级缓存移除并放入一级缓存
singletonObjects
。
四、关键机制与限制
1. 三级缓存的必要性
三级缓存
singletonFactories
:存储生成早期 Bean 的工厂,解决代理对象(如 AOP)的循环依赖。工厂可以返回原始对象或代理对象。二级缓存
earlySingletonObjects
:避免重复调用ObjectFactory.getObject()
,保证单例唯一性。
2. 支持的范围
仅支持单例 Bean:原型(Prototype)作用域的 Bean 无法解决循环依赖。
仅支持 Setter/Field 注入:构造器注入会直接抛出
BeanCurrentlyInCreationException
。
3. 构造器注入为何失败?
java
复制
@Component public class A { private final B b; public A(B b) { this.b = b; } // 构造器注入 }
在实例化 A 时就需要 B 的实例,但此时 B 尚未创建,无法通过三级缓存解决。
五、总结
Spring 通过三级缓存提前暴露未初始化的 Bean 引用,解决了单例 Bean 的循环依赖问题。核心在于将实例化与初始化分离,允许“未完成”的 Bean 被引用,最终通过依赖注入完成闭环。