Spring的三级缓存机制主要用于解决单例Bean的循环依赖问题。其核心在于提前暴露Bean的引用,允许未完全初始化的对象被其他Bean引用。以下是三级缓存的详细说明及其解决循环依赖的原理:
三级缓存结构
-
一级缓存(
singletonObjects
)- 存储完全初始化的单例Bean。
- 当Bean完成实例化、属性注入和初始化后,最终存储于此。
- 其他Bean通过此缓存获取完全可用的Bean实例。
-
二级缓存(
earlySingletonObjects
)- 存储未完全初始化的Bean(仅实例化,未完成属性注入和初始化)。
- 用于解决循环依赖时,提前暴露Bean的早期引用。
-
三级缓存(
singletonFactories
)- 存储Bean的
ObjectFactory
(工厂对象)。 - 当需要提前暴露 Bean 时,Spring 会将 ObjectFactory 放入此缓存,而不是直接暴露尚未初始化的 Bean 实例。
- 当Bean实例化后(构造方法调用后),将生成Bean的工厂存入此缓存。
- 工厂的作用是按需生成早期引用,可能包含AOP代理逻辑。
- 存储Bean的
解决循环依赖的流程
以BeanA
依赖BeanB
,BeanB
依赖BeanA
为例:
-
创建
BeanA
- 实例化
BeanA
(调用构造方法),得到一个原始对象。 - 将
BeanA
的ObjectFactory
存入三级缓存 (singletonFactories
)。 - 开始属性注入,发现需要
BeanB
。
- 实例化
-
创建
BeanB
- 实例化
BeanB
,同样将ObjectFactory
存入三级缓存。 - 开始属性注入,发现需要
BeanA
。
- 实例化
-
获取
BeanA
的早期引用- 从三级缓存 中找到
BeanA
的ObjectFactory
,调用其getObject()
方法。- 若
BeanA
需要AOP代理,工厂会生成代理对象;否则返回原始对象。
- 若
- 将生成的早期引用存入二级缓存 (
earlySingletonObjects
),并移除三级缓存中的工厂。 BeanB
成功注入BeanA
的早期引用,继续完成属性注入和初始化。
- 从三级缓存 中找到
-
完成
BeanB
的创建BeanB
完成后,存入一级缓存 (singletonObjects
)。
-
回到
BeanA
的创建BeanA
注入BeanB
(此时已存在于一缓)。- 完成
BeanA
的属性注入和初始化。 - 检查二级缓存是否存在
BeanA
的早期引用:- 如果存在,可能合并代理逻辑,最终将完整对象存入一缓,并清理二、三缓存。
为什么需要三级缓存?
-
分离职责
- 一级缓存存放成品,二级缓存存放半成品,三级缓存存放生成半成品的工厂。
- 工厂的延迟执行:确保代理逻辑在需要时才执行(如存在AOP时),避免重复创建代理对象。
-
解决代理对象的循环依赖
- 若Bean需要AOP代理,三级缓存的工厂能生成代理对象,而二级缓存直接存储对象。如果只有二级缓存,无法处理代理对象的生成时机问题,可能导致注入不一致。
局限性
-
仅支持单例Bean
- 原型(prototype)Bean每次创建新对象,无法通过缓存提前暴露引用,循环依赖会直接报错。
-
构造器注入无法解决
- 若循环依赖通过构造器注入,Bean在实例化前无法暴露引用,导致创建失败。
-
需要Spring管理
- 若Bean通过
new
创建或非Spring上下文管理,三级缓存机制失效。
- 若Bean通过
总结
Spring通过三级缓存的协同工作,在Bean实例化后立即暴露其ObjectFactory
,使得循环依赖的Bean能通过工厂获取早期引用(可能是代理对象)。这一机制巧妙地平衡了对象创建顺序与依赖注入的需求,解决了单例Bean的循环依赖问题,同时确保AOP代理的正确性。