Spring为什么要三级缓存?二级不行吗?

2 8~10 min

Spring采用三级缓存(singletonFactoriesearlySingletonObjectssingletonObjects)主要是为了解决循环依赖中代理对象(如AOP)的创建和缓存问题。若仅用二级缓存,在某些场景下会导致代理对象重复生成或依赖注入不一致。以下是具体分析:


1. 三级缓存的作用

  • 第一级缓存(singletonObjects:存储完全初始化好的单例Bean,可直接使用。

  • 第二级缓存(earlySingletonObjects:存储提前暴露的Bean的早期引用(未完成属性注入和初始化),用于解决循环依赖。

  • 第三级缓存(singletonFactories:存储Bean的工厂对象(ObjectFactory),用于在需要时生成早期引用,尤其是处理代理对象的创建


2. 二级缓存的问题

假设只有两级缓存(例如去掉earlySingletonObjects,仅保留singletonFactoriessingletonObjects):

  • 代理对象可能重复生成
    当Bean需要被代理(如AOP)时,每次从singletonFactories获取早期引用都会调用工厂方法生成新对象。若多个Bean依赖该对象,可能导致代理对象被多次创建,破坏单例。

  • 无法保证代理对象的一致性
    若A依赖B,B依赖A,且A需要代理,则B注入的A必须是最终的代理对象。二级缓存无法在第一次生成代理后缓存该对象,导致后续注入时可能拿到不一致的实例。


3. 三级缓存的优势

  • 代理对象仅生成一次
    当通过singletonFactories首次获取Bean的早期引用时,若需要代理,工厂会生成代理对象,并立即将其存入earlySingletonObjects。后续依赖直接从此缓存获取,避免重复调用工厂方法

  • 保证依赖注入的一致性
    所有依赖方都从earlySingletonObjects获取同一个早期引用(可能是代理对象),确保最终注入的Bean与完全初始化的Bean一致。


4. 典型场景分析

以AOP代理为例:

  1. 创建Bean A

    • 实例化A后,将A的工厂(能生成A的代理)放入singletonFactories

    • 填充属性时发现依赖B,开始创建B。

  2. 创建Bean B

    • 实例化B后,填充属性时发现依赖A,从singletonFactories获取A的工厂,生成A的代理对象,存入earlySingletonObjects

    • B完成初始化,放入singletonObjects

  3. 回到A的初始化

    • earlySingletonObjects获取A的代理对象(而非重新生成)。

    • 完成A的属性注入和初始化,最终将代理对象放入singletonObjects

若仅有二级缓存,步骤2中每次获取A都会调用工厂生成新代理,导致B和A持有的代理对象不一致。


5. 结论

  • 二级缓存无法解决代理对象的一致性问题,而三级缓存通过earlySingletonObjects隔离工厂的调用,确保代理对象只生成一次,并保证所有依赖方拿到相同的引用。

  • 设计本质:三级缓存通过分层隔离,平衡了“提前暴露引用”和“延迟生成代理”的需求,是Spring解决循环依赖与AOP代理协同工作的关键机制。