为什么spring解决bean初始化时候循环依赖问题是用的三级缓存而不是二级缓存?感觉用两级就够了啊?
这个问题问到了 Spring 循环依赖处理的核心设计细节,非常关键!

简单说,二级缓存无法解决 “循环依赖 + AOP 代理” 的组合场景,会导致最终注入的是原始 Bean 而非代理对象,而三级缓存正是为了动态生成代理对象、保证注入一致性而设计的。

1. 先明确三级缓存的核心职责

要理解区别,首先得清楚 Spring 三级缓存分别存什么,这是后续分析的基础:

缓存级别 缓存名称(Spring 源码) 核心存储内容 作用
一级缓存 singletonObjects 完全初始化完成的单例 Bean 供外部直接获取使用,是最终的 “成品 Bean”
二级缓存 earlySingletonObjects 提前暴露的 “原始 Bean” 或 “代理 Bean” 避免同一 Bean 在循环依赖中被反复创建,起到 “临时占位” 作用
三级缓存 singletonFactories Bean 的创建工厂(ObjectFactory) 存储 “生成 Bean 实例的逻辑”,关键是能在需要时动态生成代理对象

2. 为什么二级缓存不够?核心矛盾:AOP 代理

如果只保留 “一级缓存(成品 Bean)+ 二级缓存(临时 Bean)”,在循环依赖 + AOP 代理的场景下会失效,具体问题如下:

场景举例:A 和 B 循环依赖,且 A 需要被 AOP 代理

假设存在依赖关系:A → B(A 依赖 B)、B → A(B 依赖 A),且 A 配置了 AOP(如事务、日志),需要生成代理对象。

若用二级缓存,流程会卡在 “代理对象不一致”:
  1. A 开始实例化(new A()),但未初始化(未注入 B),此时将原始 A 对象放入二级缓存;
  2. A 尝试注入 B,发现 B 未创建,转而开始创建 B;
  3. B 实例化后,尝试注入 A,从二级缓存中获取到原始 A 对象并注入;
  4. B 完成初始化,成为 “成品 Bean” 放入一级缓存,回到 A 的创建流程;
  5. A 继续初始化,此时需要为 A 生成 AOP 代理对象(因为配置了 AOP);
  6. 最终 A 的代理对象成为 “成品 Bean” 放入一级缓存,但 B 中已经注入了原始 A 对象,导致 B 依赖的 A 和最终一级缓存的 A 不是同一个对象,出现 “代理对象不一致” 问题。

本质问题:二级缓存只能提前暴露 “固定对象”(要么原始 Bean,要么代理 Bean),但 A 的代理对象需要在 A 自身初始化后期才能生成(依赖 A 的属性、切面配置等),无法在实例化时就确定,导致提前暴露的原始 Bean 和最终的代理 Bean 脱节。

3. 三级缓存如何解决?关键在 “延迟生成代理”

三级缓存的 ObjectFactory 本质是一个 “延迟执行的工厂”,它不直接存储 Bean 对象,而是存储 “生成 Bean 的逻辑”,核心作用是:在需要时(即被其他 Bean 依赖时)才动态生成代理对象,确保注入的对象和最终成品一致。

还是用上面的 A→B、B→A 且 A 需要 AOP 场景,三级缓存的流程如下:

  1. A 实例化(new A())后,不直接放二级缓存,而是将 () -> getEarlyBeanReference(A) 这个工厂逻辑放入三级缓存;
  2. A 注入 B 时触发 B 的创建,B 实例化后需要注入 A;
  3. B 从三级缓存获取 A 的工厂,执行工厂逻辑:判断 A 是否需要 AOP 代理,若需要则生成 A 的代理对象,将代理对象放入二级缓存,同时删除三级缓存中的工厂;
  4. B 注入 A 的代理对象,完成初始化后放入一级缓存;
  5. 回到 A 的创建流程,A 继续初始化(注入其他属性等),最终发现二级缓存中已有自己的代理对象,直接将代理对象放入一级缓存;
  6. 最终一级缓存中的 A 是代理对象,B 中注入的 A 也是同一个代理对象,一致性得到保证。

4. 总结:三级缓存的不可替代性

  • 二级缓存的局限:只能存储 “静态对象”(原始 Bean 或提前生成的代理 Bean),无法应对 “代理对象需要延迟生成” 的场景,会导致依赖注入的对象与最终成品不一致。
  • 三级缓存的价值:通过 “工厂逻辑” 实现了代理对象的 “按需生成”,在循环依赖的节点上动态判断是否需要代理、生成代理,确保所有依赖方注入的都是最终的代理对象(若需要代理),同时避免了不必要的代理对象生成(非循环依赖场景下,代理可在 Bean 初始化完成后生成)。

简单说,二级缓存能解决 “纯循环依赖”,但解决不了 “循环依赖 + AOP 代理”;三级缓存通过延迟生成代理的设计,同时覆盖了这两种场景,这也是 Spring 选择三级缓存的根本原因。
 

二级缓存 vs 三级缓存 流程对比表

以下表格以 “A→B、B→A 且 A 需 AOP 代理” 为核心场景,拆解两种缓存方案的关键步骤,直观呈现差异。

步骤 二级缓存方案(仅 singletonObjects+earlySingletonObjects) 三级缓存方案(singletonObjects+earlySingletonObjects+singletonFactories) 核心差异点
1 A 执行实例化(new A ()),生成原始 A 对象,放入 earlySingletonObjects(二级缓存) A 执行实例化(new A ()),不直接存对象,将 “A 的 ObjectFactory 工厂” 放入 singletonFactories(三级缓存) 二级存 “静态原始对象”,三级存 “动态生成逻辑(工厂)”
2 A 开始初始化,发现依赖 B,转而创建 B A 开始初始化,发现依赖 B,转而创建 B 流程一致,均触发依赖 Bean 的创建
3 B 执行实例化(new B ()),开始初始化,发现依赖 A B 执行实例化(new B ()),开始初始化,发现依赖 A 流程一致,均进入循环依赖节点
4 B 从 earlySingletonObjects 获取到原始 A 对象,注入自身 B 从三级缓存获取 A 的 ObjectFactory,执行工厂逻辑:1. 判断 A 需 AOP 代理,生成A 的代理对象2. 将 A 的代理对象放入 earlySingletonObjects(二级缓存)3. 删除三级缓存中的 A 工厂4. B 注入 A 的代理对象 二级注入 “原始 A”,三级注入 “A 的代理对象”
5 B 完成初始化,成为成品 Bean,放入 singletonObjects(一级缓存) B 完成初始化,成为成品 Bean,放入 singletonObjects(一级缓存) 流程一致,B 均正常生成
6 回到 A 的初始化流程,执行 AOP 逻辑,生成A 的代理对象 回到 A 的初始化流程,发现 earlySingletonObjects 中已有自己的代理对象 二级中 A 需 “重新生成代理”,三级中 A “已存在代理”
7 A 将代理对象放入 singletonObjects(一级缓存),覆盖原原始 A A 直接将 earlySingletonObjects 中的代理对象移入 singletonObjects(一级缓存) -
8 最终结果:一级缓存 A = 代理 A,B 中注入的 A = 原始 A → 两个 A 对象不一致 最终结果:一级缓存 A = 代理 A,B 中注入的 A = 代理 A → 两个 A 对象完全一致 二级方案出现对象不一致,三级方案正常

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐