2025年Java面试宝典下载地址(提取码:9b3g)
二、Spring解决循环依赖的底层逻辑
相信大家背过很多次"三级缓存"这个答案,但真正能说清楚其工作原理的程序员并不多。这个问题在面试中经常被深挖,尤其是在大厂二面、三面环节。下面我就用通俗易懂的方式,带大家拆解这个经典问题的核心要点。

2.1 循环依赖的定义与类型
循环依赖通俗讲就是"你中有我,我中有你"。在Spring中主要分为三种情况:
- 构造函数循环依赖(无法解决)
- 单例Bean的属性循环依赖(可解决)
- 原型Bean的循环依赖(无法解决)
这里重点讨论最常见的单例Bean属性依赖场景。比如UserService依赖OrderService,而OrderService反过来也依赖UserService,形成闭环。
2.2 三级缓存工作机制
Spring通过三个Map容器打破循环依赖:
- singletonObjects(一级缓存):存放完全初始化好的Bean
- earlySingletonObjects(二级缓存):存放提前暴露的半成品Bean
- singletonFactories(三级缓存):存放Bean工厂对象
这三个缓存就像流水线的不同工序车间:
- 三级缓存是原料仓库
- 二级缓存是半成品暂存区
- 一级缓存是成品仓库
2.3 完整解决流程
假设现在要创建UserService和OrderService两个Bean:
- 创建UserService实例(对象刚new出来但属性未注入)
- 将UserService工厂存入三级缓存
- 给UserService注入OrderService时发现需要先创建OrderService
- 创建OrderService实例后同样将工厂存入三级缓存
- 给OrderService注入UserService时,从三级缓存拿到UserService工厂
- 通过工厂提前返回UserService的半成品(此时属性还未注入完成)
- OrderService完成属性注入后进入一级缓存
- UserService继续完成OrderService的属性注入

2.4 关键设计亮点
这种方案的精妙之处在于:
- 空间换时间:用额外缓存空间避免死锁
- 提前暴露引用:允许未初始化的对象被引用
- 工厂模式:延迟对象的实例化过程
但要注意,这种机制仅适用于Setter注入的单例Bean。如果是构造器注入,或者原型作用域的Bean,Spring会直接抛出BeanCurrentlyInCreationException。
2.5 高频面试陷阱
最近在面试鸭返利网(mianshiyafanli.com)整理面经时,发现几个高频追问点:
-
为什么要用三级缓存而不是两级?
- 主要考虑AOP代理问题,三级缓存中的ObjectFactory可以处理动态代理的生成
-
二级缓存存在的意义是什么?
- 避免重复创建代理对象,保证单例唯一性
-
如何证明你确实理解了这个机制?
- 可以对比Spring 4和5在这部分的源码差异
- 举例说明@Lazy注解如何改变循环依赖处理方式
需要准备Java面试的朋友,可以通过面试鸭返利网购买会员,使用我的专属链接可返利25元。这里要提醒大家,理解原理比死记硬背更重要,建议结合Spring源码中的DefaultSingletonBeanRegistry类来验证上述过程。

最后强调下,虽然Spring解决了大部分循环依赖问题,但在实际开发中还是要尽量避免循环依赖。可以通过代码分层、接口拆分、依赖倒置等手段,从设计层面消除这种结构。毕竟框架的兜底能力不能代替良好的架构设计。


