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

08 Spring 循环依赖怎么解决的?你背了三级缓存,但面试官其实想听这句

面过一个不错的候选人,Spring 源码看得挺多。问到循环依赖,他说:

“Spring 用三级缓存解决循环依赖。singletonObjects 存成品,earlySingletonObjects 存半成品,singletonFactories 存 ObjectFactory,用完后会删掉。”

对——完全对。市面上能说出这些的人已经不多了。

但我是面试官嘛,我又问了一句:“那为什么需要三级?为什么不是两级?”

他卡住了。

这道题的陷阱就在这里——几乎所有材料都讲了 Spring 三级缓存怎么用。几乎没有人讲为什么一定是三级,而不是两级、不是四级。

如果你到这里也卡住——来,我把这个“为什么”给你拆开。


先别管三级缓存——先搞清楚 Spring 里 beans 是怎么造的

假设 Spring 要创建一个 bean A,同时 A 依赖 B。

Spring 的流程大概是这样的:

① 反射创建 A 的实例(newInstance)——此时 A 还是空壳 ② 给 A 填充属性(populateBean)——发现 A 依赖了 B ③ Spring 去创建 B——同理:反射创建 B → 填充 B 的属性 → 发现 B 又依赖了 A 这时候 A 还是个空壳(还没完成 ②),但它的引用已经存在了 ④ 把空壳 A 的引用交给 B ⑤ B 建造完成(完整对象) ⑥ 回到 A——把完整的 B 注入给 A,A 建造完成

这就是循环依赖的基本流程。关键是第 ③~④ 步——Spring 把一个还没建造完全的 A 的半成品暴露给了 B

这个半成品怎么暴露?三级缓存。


三级缓存的真实威力

第一级:singletonObjects —— 存放完全成品的 bean

最普通的 HashMap。一个 bean 从创建到属性填充到初始化(init-method/@PostConstruct)全部完成后,才放进这个池。任何地方 getBean 就能拿到完整的对象。

第二级:earlySingletonObjects —— 存放半成品的 bean

一个 bean 刚反射创建出来(空壳),还没填充属性——但它已经有引用了。Spring 把它的引用放进二级缓存——让其他正在创建的 bean 能拿到这个半成品。

第三级:singletonFactories —— 存放产生半成品的工厂

这里不直接存 bean 实例——存的是一个 ObjectFactory(一个 lambda 表达式)。当 Spring 需要“从工厂里把半成品取出来”,这个 lambda 就执行——调用getEarlyBeanReference()方法,把你刚创建的空壳 A 包装一下(可能经过 AOP 代理),再返回给调用方。

为什么不是直接存半成品而要存一个工厂?因为 AOP 代理的存在。

如果你的 A 被 AOP 切面了(比如@Transactional@Async),Spring 需要创建一个代理类对象——这个代理对象就是半成品 A。但谁去创建这个代理——不是 Spring 初始化,而是SmartInstantiationAwareBeanPostProcessorgetEarlyBeanReference方法在第三级缓存的 lambda 里调用的。

如果没有第三级缓存——bean 刚反射创建后,Spring 还不知道它要被代理——直接把空壳塞进第二级缓存。后面 AOP 想要代理它——晚了。


如果只有两级缓存会怎样?

我们设想来一个只有 singletonObjects(成品)和 earlySingletonObjects(半成品)的体系:

① 反射创建 A 的空壳 → 立刻放进 earlySingletonObjects ② 填充 A → 需要 B → 创建 B ③ B 填充属性 → 依赖 A → 从 earlySingletonObjects 拿到空壳 A ④ B 完成 → 放进 singletonObjects ⑤ 回到 A,继续填充 → A 完成 → 从 earlySingletonObjects 挪到 singletonObjects

看起来没啥问题是吧?是——如果你没有 AOP。

假设 A 类的某个方法加了@Transactional。Spring 需要用 CGLIB 为它创建一个代理类——代码在AbstractAutoProxyCreator.getEarlyBeanReference()里。

你这时候调用getEarlyBeanReference(),它发现刚创建的空壳 A 需要被代理——但它应该把代理后的 A 放进第二级缓存,而不是把原始的空壳 A 放进第二级缓存。

这就是第三级缓存存在的全部意义。第三级缓存里的 ObjectFactory 在你需要半成品时才会执行——在它执行之前,你可以先把原始的空壳 A 跟传进来的 BeanPostProcessor 联合起来决定需不需要生成代理对象。

原始流程: 反射空壳 A → 立即放第二级缓存 → B 拿到原始空壳 → AOP 后 — 晚了 正确流程: 反射空壳 A → 放第三级缓存(ObjectFactory) → B 拿半成品时 — ObjectFactory 执行 → 在 lambda 里调 getEarlyBeanReference() → 如果有 AOP 就生成代理 → 返回代理对象给 B → 同时存入第二级缓存

面试最好的答法

第一档:说出三个缓存的名字——不及格。说明背过。

第二档:在第一档基础上说清它们的协作流程——“singletonObjects 存成品、earlySingletonObjects 存半成品、singletonFactories 存 ObjectFactory、当需要半成品时 lambda 执行、得到早期引用后从 singletonFactories 移入 earlySingletonObjects”——通过线。面试官承认你读过 Spring 源码。

第三档:在第二档基础上,关键的一句——“第三级缓存的存在本质是因为 Spring 的 AOP 代理时机——需要在属性填充之前就知道这个 bean 需不需要被代理。如果只有两级缓存,代理对象无法被正确传递到依赖方。Spring 选择用 ObjectFactory 做延迟架构,把 AOP 的决策点和 bean 的暴露点解耦。

你这段话说完,面试官在心里已经把你的评级调到了“这个人不只是知道源码——他理解了为什么要这么设计”。


面试官最后可能追问的三个问题

  • “那构造器注入能解决循环依赖吗?”不能。构造器注入时对象连空壳都还没产生,没法暴露半成品。

  • “prototype 作用域的 bean 能用三级缓存吗?”不能。prototype bean 每次创建新对象——Spring 不缓存 prototype bean,循环依赖直接抛异常。

  • “Spring Boot 解决循环依赖有没有更好的方案?”最好的方案是重构——循环依赖本身是设计问题。@Lazy注解延迟加载是紧急方案,但不是解药。重构代码,抽离出一个新的独立类(比如用@Configuration或用@Component提取共同依赖)——从根源干掉循环

这三个追问答出来一个,面试官大概率在心里给你盖章了。


Spring 循环依赖这道经典题,你是怎么面过的?面试官在你的回答后追了哪几个问题?留言区唠唠。


本文作者:资深 Java 开发老兵,前大厂面试官。不讲八股文,只讲面试官心里在想什么。系列持续更新,关注不迷路。

如果觉得有帮助,点个关注支持一下。

我最近整理了一份《大厂面试老兵资料包》,里面有评分表、高频场景题还有今年新出的 AIGC 面试题,

都是 200 多场面试里一点点攒的,市面上没人这么整理过。

需要的朋友私信我回复「666」,我发你领取方式。

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

相关文章:

  • Rust 错误处理最佳实践清单:从库到应用的一整套约定
  • 医疗信创实战|全栈国产化改造与金仓数据库应用实践
  • 小实验-MPLS VPN Option C2(锐捷设备)
  • 2026年陕西西安装修行业实地调研:西安小户型装修怎么选,靠谱公司推荐口碑排名解析 - 小随科技
  • C# 从零搭建企业级身份账户系统
  • immutable Builder模式:高效构建大型集合的3个技巧
  • 2026 毓典奢品汇|天津黄金回收渠道对比:2026年7月卖金注意事项与正规门店测评汇总 - 奢侈品测评参考
  • pz 安全最佳实践:如何安全使用 --insecure 标志和环境变量
  • 2026中山新能源维修选择指引 阿森汽修入选本地推荐名录 - 互联网科技品牌测评
  • 海口潮玩玩具回收全解析!泡泡玛特、乐高、暴力熊闲置高价变现避坑指南 - 每日生活报
  • 2026消失模白膜烘干设备综合资质排行参考榜单 - 互联网科技品牌测评
  • MetaTube SDK Go高级功能:文本翻译、图像水印与自定义处理器
  • CVE-2026-0007 Android界面覆盖劫持提权漏洞深度解析与全维度防护方案
  • PUBG地图黑客内核驱动编程深度解析:终极实战指南
  • 2026年陕西西安装修行业实地调研:西安小户型装修怎么选,靠谱公司推荐口碑排名解析 - 子柔传媒
  • 微仓 Vwms 产品详解:专业级 SaaS WMS,中小企业仓储数字化的标准化方案
  • React 函数组件的重渲染根因分析:React DevTools Profiler 深度使用指南
  • 想了解西安豪美装修工艺/施工工艺?先看施工链条,再看落地表现 - 小随科技
  • HarmonyOS7 ArkUI 歌词滚动播放器 - 歌词同步高亮、进度控制与翻译切换实战:从状态到页面反馈
  • PUBG地图黑客安全部署指南:避免封号的10个终极技巧
  • 2026年装修避坑指南:西安本地新房装修哪家好,整装交付不踩坑 - 小随科技
  • raf polyfill 实现原理揭秘:深入理解浏览器兼容性解决方案
  • 西安欧米茄回收价格查询及各大回收平台实测排行(2026年7月最新) - 天价名表回收平台
  • 备战2026系规考试,最怕“听不懂、学不会、写不出”?深度拆解软考老金团队如何用“三位一体”教学法,帮你扫清备考路上所有障碍
  • ★★★隐身截屏Pro 新版V1.3 功能全面介绍
  • Seekr主题定制:如何打造个性化的OSINT工作环境
  • ComfyUI-LTXVideo完整指南:轻松实现AI视频生成与编辑
  • morss实用技巧:10个提升RSS阅读效率的方法
  • 亲身到店探访杭州亨得利官方名表服务中心|全新维修地址及客服热线(2026年7月更新) - 亨得利官方
  • 宏观认识大模型