08 Spring 循环依赖怎么解决的?你背了三级缓存,但面试官其实想听这句 📅 2026/7/12 19:05:24 面过一个不错的候选人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 注入给 AA 建造完成这就是循环依赖的基本流程。关键是第 ③~④ 步——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、AsyncSpring 需要创建一个代理类对象——这个代理对象就是半成品 A。但谁去创建这个代理——不是 Spring 初始化而是SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference方法在第三级缓存的 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」我发你领取方式。