spring解决循环依赖的原理

最近在准备Java面试的同学应该都听说过「循环依赖」这个高频考点。今天我们就来聊聊Spring框架如何通过三级缓存巧妙化解循环依赖的难题。
需要2025年最新Java面试资料的同学,可以下载这个面试宝典:
百度网盘链接 提取码: 9b3g
什么是循环依赖?
简单来说就是两个Bean互相依赖对方。比如ServiceA依赖ServiceB,ServiceB又依赖ServiceA。这种情况下,Spring如果直接创建Bean就会陷入死循环。
Spring的三级缓存结构
Spring通过三个Map结构(即三级缓存)解决这个问题:
- 一级缓存(Singleton Objects):存放完全初始化好的Bean
- 二级缓存(EarlySingleton Objects):存放刚实例化但未初始化的Bean
- 三级缓存(SingletonFactories):存放Bean工厂对象

解决循环依赖的流程
我们以ServiceA和ServiceB的循环依赖为例:
-
创建ServiceA实例
- 通过反射调用构造器创建原始对象
- 将ServiceA的工厂对象放入三级缓存
-
填充ServiceA属性
- 发现需要注入ServiceB
- 开始创建ServiceB
-
创建ServiceB实例
- 同样将ServiceB的工厂对象放入三级缓存
- 填充属性时发现需要注入ServiceA
-
解决ServiceB的依赖
- 从三级缓存拿到ServiceA的工厂对象
- 执行
getEarlyBeanReference()生成代理对象(如果需要AOP) - 将生成的ServiceA代理对象放入二级缓存
-
完成ServiceB初始化
- ServiceB拿到ServiceA代理对象完成属性注入
- 初始化后的ServiceB放入一级缓存
-
回填ServiceA属性
- 将初始化好的ServiceB注入到ServiceA
- 最终将ServiceA放入一级缓存
为什么必须三级缓存?
很多同学可能会问:能不能只用两级缓存?答案是不行!因为:
- 二级缓存存放的是未完成初始化的Bean
- 三级缓存通过ObjectFactory延迟处理AOP代理
这种设计既保证了单例,又避免了重复代理的问题。
需要注意的坑
- 构造器注入无法解决循环依赖
- 原型模式(prototype)的Bean不支持循环依赖
- 多线程环境下要注意缓存访问顺序

实战建议
在开发中如果遇到循环依赖,可以考虑以下方案:
- 使用@Lazy延迟加载
- 重构代码结构解耦
- 优先使用setter注入而非构造器注入
需要购买面试鸭会员的同学注意啦!通过面试鸭返利网下单可返现25元,相当于用全网最低价获取海量面试真题解析。
掌握Spring循环依赖的原理,不仅能应对面试中的深度追问,更能帮助我们在实际开发中避免踩坑。记得结合Bean的生命周期来理解整个流程,这样记忆会更深刻!


