当前位置: 首页 > news >正文

Spring 源码系列(9): 循环依赖终极拷问:为什么用三级缓存而不是二级

引子

A 依赖 B,B 又依赖 A——这么写,Spring 为什么没崩?很多人的答案止步于"因为三级缓存"。但面试官要的是:三级缓存各自存什么?为什么二级不够、非得三级?

本篇把循环依赖的"前因后果 + 源码证据 + 反证"一次性讲透。这是整个 IoC 阶段最值得收藏的一篇。

💡一句话结论
三级缓存解决的是"在还没初始化完时,如何把一个可被他人引用的早期对象(可能是 AOP 代理)安全暴露出去"。二级缓存只能存"已决定的早期对象",无法延迟决定要不要生成代理;三级缓存用ObjectFactory把"是否生成代理"延迟到真正被依赖引用时才发生。这就是二级不够、必须三级的根本原因。

前置知识

  1. Spring只解决"单例 + 字段/setter 注入"的循环依赖;构造器注入、原型(prototype)作用域不解决
  2. "提前暴露"动作发生在doCreateBean:实例化后addSingletonFactory() -> getEarlyBeanReference放进三级缓存(见第 6 篇)。
  3. 循环依赖的产生源于 DI 阶段的getBean(依赖)递归(见第 8 篇)。

一、三级缓存到底存什么

都在DefaultSingletonBeanRegistry里,三个 Map:

缓存字段存的内容写入时机读取时机
一级singletonObjects成品 Bean(完全初始化完成)addSingleton(创建完成)getSingleton先查它
二级earlySingletonObjects早期 Bean(已实例化、未初始化,可能是代理)getSingleton从三级取对象后转入有人提前引用时
三级singletonFactoriesObjectFactory(能产出早期引用)addSingletonFactory(实例化后)一级、二级都未命中时
// DefaultSingletonBeanRegistryprivatefinalMap<String,Object>singletonObjects=newConcurrentHashMap<>(256);// 一级privatefinalMap<String,Object>earlySingletonObjects=newHashMap<>(16);// 二级privatefinalMap<String,ObjectFactory<?>>singletonFactories=newHashMap<>(16);// 三级

二、getSingleton:查缓存的顺序

protectedObjectgetSingleton(StringbeanName,booleanallowEarlyReference){ObjectsingletonObject=this.singletonObjects.get(beanName);// ① 一级if(singletonObject==null&&isSingletonCurrentlyInCreation(beanName)){singletonObject=this.earlySingletonObjects.get(beanName);// ② 二级if(singletonObject==null&&allowEarlyReference){ObjectFactory<?>singletonFactory=this.singletonFactories.get(beanName);// ③ 三级if(singletonFactory!=null){singletonObject=singletonFactory.getObject();// 调 getEarlyBeanReferencethis.earlySingletonObjects.put(beanName,singletonObject);// 转存二级this.singletonFactories.remove(beanName);// 清三级}}}returnsingletonObject;}

三、为什么必须三级?核心论证

假设只有一 + 二级缓存(即没有三级的ObjectFactory),会发生什么?

  • 实例化后必须立刻把一个"早期对象"放进二级缓存earlySingletonObjects
  • 但此时 Bean还没走initializeBean,AOP 代理还没生成。如果这里直接放"原始对象",而后面initializeBean又生成了代理——那么循环依赖方(B)手里拿的是原始对象,最终容器里却是代理,二者不是同一个引用,Spring 会抛BeanCurrentlyInCreationException
  • 那"实例化后就直接生成代理放进二级"行不行?不行——绝大多数 Bean 没有 AOP,凭空给每个 Bean 都生成代理既浪费又在语义上错误;而且代理本应在initializeBean第 ⑦ 步(postProcessAfterInitialization)才确定。

三级缓存的价值 = 延迟决策

  • 三级缓存存的是() -> getEarlyBeanReference(beanName, mbd, bean),是一个工厂/lambda,不是具体对象。
  • 只有真正发生循环依赖(B 来取 A)时,才调用这个工厂 →getEarlyBeanReference→ 此时才决定返回"原始对象"还是"代理"。
  • 若不发生循环依赖,这个工厂永远不被调用,A 正常走到initializeBean生成代理,没有任何副作用。
// 发生循环依赖、被引用时才调用protectedObjectgetEarlyBeanReference(StringbeanName,RootBeanDefinitionmbd,Objectbean){ObjectexposedObject=bean;for(SmartInstantiationAwareBeanPostProcessorbp:getBeanPostProcessors()){exposedObject=bp.getEarlyBeanReference(exposedObject,beanName);// AOP 在此可能生成代理}returnexposedObject;}

📌结论:三级缓存不是为了"性能",而是为了"延迟生成代理 + 保证引用一致性"。二级缓存是三级的"结果缓存"——getEarlyBeanReference拿到代理后存进二级,避免同一早期引用被重复生成(代理不能 new 多次)。

四、构造器循环依赖为何无解

构造器注入在createBeanInstance(实例化)阶段就要求依赖就绪,而addSingletonFactory(注册三级缓存)是在实例化之后才执行的。所以:

A 构造器要 B → getBean(B) → B 构造器要 A → getBean(A) → A 此时既不在一级(没建完),也不在二/三级(还没到 addSingletonFactory) → 只能再走创建 A → isSingletonCurrentlyInCreation(A) 已标记 → 抛 BeanCurrentlyInCreationException

字段/setter 注入能解,正是因为"实例化"先于"注入"——实例化后立刻把工厂放进三级缓存,等populateBean时 B 来取,能从三级拿到 A 的早期引用。

五、循环依赖时序图

六、常见误区

误区正解
三级缓存是为了性能否,是为了延迟生成代理 + 引用一致
二级缓存就够了不够,无法延迟决定要不要代理
构造器循环依赖能解不能,实例化早于三级缓存注册
原型 Bean 的循环依赖能解不能,原型不缓存、不提前暴露

七、面试题自测

  1. 三级缓存分别存什么?读取优先级是什么?
  2. 为什么不能只用一 + 二级缓存?
  3. 循环依赖场景中,AOP 代理是在哪一步生成的?
  4. 为什么构造器注入的循环依赖无法解决?
  5. 哪些场景 Spring 明确不解决循环依赖?

八、Debug 小技巧

  1. 构造 A↔B 字段循环依赖,在getEarlyBeanReference打断电:观察 B 注入 A 时,这里返回的是 A 的代理(若 A 有 AOP)还是原始对象。
  2. DefaultSingletonBeanRegistry.getSingleton三个get处分别打断电,看 A 创建时三个缓存的先后命中顺序。
  3. 把 A、B 改成构造器互相注入,启动观察异常栈顶端BeanCurrentlyInCreationException,确认失败发生在createBeanInstance

下篇预告

第 10 篇,IoC 收尾:FactoryBean与 Spring 事件机制。讲清 MyBatis 的 Mapper 为何是FactoryBean,以及事件如何实现组件解耦。


如果这篇对你有帮助,欢迎点赞 · 收藏 · 关注三连支持。

Spring 源码系列共 30 篇,由浅入深持续更新中。有疑问或想深挖的源码点,评论区告诉我,下篇见。

http://www.jsqmd.com/news/1257100/

相关文章:

  • 深圳汽车后市场服务GEO优化公司选型指南丨2026生成式引擎优化服务商深度测评与TOP5实力盘点 - 子柔传媒
  • 如何快速掌握网盘直链下载助手:九大网盘文件直链获取的完整指南
  • 碾压传统机器人!1X Neo仿生机械手解锁人类级灵巧,背后离不开GPU科研算力支撑
  • 红警2 重聚未来 究极整合版推荐
  • docker构建jdk11
  • 2026 年当下,娄底可靠的圆拱侧开天窗制造厂哪家权威,打破设计界限:这个侧开天窗如何颠覆你的屋顶想象? - 行业推荐【认证官】
  • ELK+FileBeat 7.14.0版本安装、部署及使用
  • AICR:AI Code Review 从 0 到 1 的真实演进路线
  • WHO各国预期寿命影响因素分析与轻量回归预测
  • 三步解锁网易云音乐NCM加密:ncmdumpGUI让你的音乐无处不在
  • Chrome全屏截图插件终极指南:一键保存完整网页的完美解决方案
  • 仅剩73家企业获授「AI-MQ可信生成认证」:解读信通院首批认证标准,含Schema一致性校验、Exactly-Once语义推演、TraceID穿透率3大硬指标
  • HarmonyOS7 TransitionEffect 实战:组件显示隐藏的过渡动画
  • AI 版权求证:用 Andersen、Getty、Bartz 等真实裁决,拆“AI 偷画“传言
  • 深圳汽车养护服务行业GEO优化公司选型指南丨2026生成式引擎优化服务商深度测评与头部榜单解析 - 企业新闻快传
  • VLA-世界模型-TVA:具身智能的递归改进引擎(4)
  • C++ vector二维数组:从内存布局到性能优化的深度解析
  • 深圳人力资源服务行业GEO优化公司选型指南丨生成式引擎优化服务商深度测评与2026本地实践 - 小随科技
  • 完全指南:5分钟掌握免费在线EPUB编辑器EPubBuilder
  • 走进 Gang of Four 设计模式:责任链模式
  • 大模型选型生死线:幻觉率>5%即不可商用?头部金融科技公司内部评估标准首次流出
  • 专业工具完全指南:高效解决Windows Edge浏览器卸载难题的PowerShell脚本方案
  • OneMore:160+功能让OneNote变身终极笔记管理神器
  • Linux基础命令的使用
  • Adobe GenP破解工具:3分钟免费激活Adobe全家桶终极指南
  • SCMP供应链管理专家报名步骤 - 众智商学院职业教育
  • 2026年7月国内正规的焊管批发厂家哪家好,镀锌方管/球墨铸铁管/焊管,焊管直销厂家哪家好 - 品牌推荐师
  • HarmonyOS7 多媒体能力怎么放进一个页面:MediaPanelSwitcher 组合式案例解析
  • 如何安全畅玩GTA5:YimMenu保护工具终极使用指南
  • VC++串口通讯模块实战:异步I/O、环形缓冲区与工业级可靠性设计