Spring为什么要三级缓存?二级不行吗?
Spring采用三级缓存(singletonFactories
、earlySingletonObjects
、singletonObjects
)主要是为了解决循环依赖中代理对象(如AOP)的创建和缓存问题。若仅用二级缓存,在某些场景下会导致代理对象重复生成或依赖注入不一致。以下是具体分析:
1. 三级缓存的作用
第一级缓存(
singletonObjects
):存储完全初始化好的单例Bean,可直接使用。第二级缓存(
earlySingletonObjects
):存储提前暴露的Bean的早期引用(未完成属性注入和初始化),用于解决循环依赖。第三级缓存(
singletonFactories
):存储Bean的工厂对象(ObjectFactory
),用于在需要时生成早期引用,尤其是处理代理对象的创建。
2. 二级缓存的问题
假设只有两级缓存(例如去掉earlySingletonObjects
,仅保留singletonFactories
和singletonObjects
):
代理对象可能重复生成:
当Bean需要被代理(如AOP)时,每次从singletonFactories
获取早期引用都会调用工厂方法生成新对象。若多个Bean依赖该对象,可能导致代理对象被多次创建,破坏单例。无法保证代理对象的一致性:
若A依赖B,B依赖A,且A需要代理,则B注入的A必须是最终的代理对象。二级缓存无法在第一次生成代理后缓存该对象,导致后续注入时可能拿到不一致的实例。
3. 三级缓存的优势
代理对象仅生成一次:
当通过singletonFactories
首次获取Bean的早期引用时,若需要代理,工厂会生成代理对象,并立即将其存入earlySingletonObjects
。后续依赖直接从此缓存获取,避免重复调用工厂方法。保证依赖注入的一致性:
所有依赖方都从earlySingletonObjects
获取同一个早期引用(可能是代理对象),确保最终注入的Bean与完全初始化的Bean一致。
4. 典型场景分析
以AOP代理为例:
创建Bean A:
实例化A后,将A的工厂(能生成A的代理)放入
singletonFactories
。填充属性时发现依赖B,开始创建B。
创建Bean B:
实例化B后,填充属性时发现依赖A,从
singletonFactories
获取A的工厂,生成A的代理对象,存入earlySingletonObjects
。B完成初始化,放入
singletonObjects
。
回到A的初始化:
从
earlySingletonObjects
获取A的代理对象(而非重新生成)。完成A的属性注入和初始化,最终将代理对象放入
singletonObjects
。
若仅有二级缓存,步骤2中每次获取A都会调用工厂生成新代理,导致B和A持有的代理对象不一致。
5. 结论
二级缓存无法解决代理对象的一致性问题,而三级缓存通过
earlySingletonObjects
隔离工厂的调用,确保代理对象只生成一次,并保证所有依赖方拿到相同的引用。设计本质:三级缓存通过分层隔离,平衡了“提前暴露引用”和“延迟生成代理”的需求,是Spring解决循环依赖与AOP代理协同工作的关键机制。