Spring的三级缓存是解决单例Bean循环依赖的核心机制。循环依赖指两个或多个Bean相互依赖(如A依赖B,B依赖A),若不处理会导致实例化过程陷入无限循环。三级缓存通过"提前暴露Bean的早期引用",让依赖在Bean完全初始化前即可被访问,从而打破循环。
一、三级缓存的定义与作用
Spring在DefaultSingletonBeanRegistry
中定义了三级缓存,均为Map
结构,存储单例Bean的不同状态:
缓存级别 | 变量名 | 类型 | 作用 |
---|---|---|---|
一级缓存 | singletonObjects |
Map<String, Object> |
存储完全初始化完成的单例Bean(最终可用的Bean)。 |
二级缓存 | earlySingletonObjects |
Map<String, Object> |
存储提前暴露的未完全初始化的Bean(仅实例化未初始化,用于临时引用)。 |
三级缓存 | singletonFactories |
Map<String, ObjectFactory<?>> |
存储Bean的工厂对象,用于在需要时生成Bean的早期引用(支持AOP代理)。 |
二、循环依赖的解决过程(以A依赖B,B依赖A为例)
假设场景:A
和B
都是单例Bean,A
的字段依赖B
,B
的字段依赖A
(字段注入,非构造器注入)。流程如下:
步骤1:实例化A,暴露工厂到三级缓存
- 容器启动时,首先尝试获取
A
(getBean(A)
)。 - 发现
A
不在一级缓存中,进入创建流程:-
实例化A :调用
A
的构造器,生成A
的原始对象(未设置属性,未执行初始化方法)。 -
暴露工厂到三级缓存 :创建
ObjectFactory
(工厂对象),用于在需要时生成A
的早期引用(若A
需要AOP代理,工厂会生成代理对象;否则直接返回原始对象)。java// 简化逻辑:添加到三级缓存 singletonFactories.put("A", () -> getEarlyBeanReference("A", beanDefinition, rawA));
-
此时
A
的状态:仅实例化,未初始化(属性未填充),存在于三级缓存的工厂中。
-
步骤2:A填充属性时依赖B,触发B的创建
A
进入"属性填充"阶段,发现依赖B
,于是调用getBean(B)
。- 容器尝试获取
B
,发现B
不在一级缓存中,进入创建流程:- 实例化B :调用
B
的构造器,生成B
的原始对象。 - 暴露工厂到三级缓存 :同
A
,将B
的工厂放入三级缓存。 - 此时
B
的状态:仅实例化,未初始化,存在于三级缓存。
- 实例化B :调用
步骤3:B填充属性时依赖A,从三级缓存获取A的早期引用
B
进入"属性填充"阶段,发现依赖A
,调用getBean(A)
。- 容器查找
A
:- 一级缓存(
singletonObjects
):无(A
未完全初始化)。 - 二级缓存(
earlySingletonObjects
):无。 - 三级缓存(
singletonFactories
):存在A
的工厂。
- 一级缓存(
- 获取A的早期引用 :
- 调用
A
的工厂对象,生成A
的早期引用(若A
需要代理,则此处生成代理对象;否则为原始对象)。 - 将
A
的早期引用从三级缓存移到二级缓存(earlySingletonObjects.put("A", earlyA)
),并删除三级缓存中的工厂(避免重复生成)。
- 调用
- 注入A到B :将
A
的早期引用(earlyA
)注入到B
的属性中。
步骤4:B完成初始化,放入一级缓存
-
B
的属性填充完成,进入"初始化"阶段(执行@PostConstruct
、afterPropertiesSet()
等)。 -
B
完全初始化后,从二级缓存(若有)或直接放入一级缓存:javasingletonObjects.put("B", b); // B成为可用Bean earlySingletonObjects.remove("B"); // 清除二级缓存 singletonFactories.remove("B"); // 清除三级缓存
步骤5:A获取B的引用,完成初始化
-
A
的属性填充阶段继续:此时B
已在一级缓存中,直接从singletonObjects
获取B
,注入到A
的属性中。 -
A
进入"初始化"阶段(执行初始化方法)。 -
A
完全初始化后,放入一级缓存:javasingletonObjects.put("A", a); // A成为可用Bean earlySingletonObjects.remove("A"); // 清除二级缓存
三、关键细节:为什么需要三级缓存?
核心原因是支持AOP代理:
- 若Bean需要被AOP代理(如被
@Transactional
注解),最终注入给依赖的应该是代理对象,而非原始对象。 - 三级缓存的
ObjectFactory
的作用是:在需要时(如被其他Bean依赖时)才生成代理对象,而非在实例化后立即生成。 - 若只有二级缓存(直接存储早期引用),无法动态生成代理对象(实例化时还不确定是否需要代理,代理逻辑可能依赖其他Bean)。
四、无法解决的循环依赖:构造器注入
三级缓存仅能解决字段注入 或setter注入 的循环依赖,无法解决构造器注入的循环依赖。原因是:
- 构造器注入在实例化阶段 就需要依赖对象(调用构造器时必须传入参数),而三级缓存是在实例化后才暴露早期引用。
- 例如:
A
的构造器依赖B
,B
的构造器依赖A
,此时A
和B
在实例化时就需要对方,而缓存中尚无任何引用,导致循环依赖无法解决(抛出BeanCurrentlyInCreationException
)。
总结
三级缓存通过"提前暴露Bean的工厂→按需生成早期引用→逐步完成初始化"的流程,打破了单例Bean的循环依赖。其中:
- 三级缓存(
singletonFactories
)负责延迟生成早期引用(支持AOP代理)。 - 二级缓存(
earlySingletonObjects
)负责临时存储早期引用,避免重复生成。 - 一级缓存(
singletonObjects
)存储最终可用的完全初始化Bean。
这一机制既保证了循环依赖的解决,又兼顾了AOP代理等复杂场景的正确性。