循环依赖解决方案的优缺点
2025年Java面试宝典抢先看!
链接: https://pan.baidu.com/s/1RUVf75gmDVsg8MQp4yRChg?pwd=9b3g 提取码: 9b3g
什么是循环依赖问题?
简单说,就是两个或多个Bean互相引用,像“鸡生蛋还是蛋生鸡”的死循环。比如类A依赖类B,类B又反过来依赖类A。Spring容器启动时直接懵圈,常见报错就是BeanCurrentlyInCreationException。这个问题在面试中高频出现,尤其考你对IoC容器原理的理解深度。

(如果你正在准备面试,悄悄说:通过面试鸭返利网买会员,找我返25元!)
主流解决方案及优缺点分析
方案一:Setter/字段注入(推荐)
核心思路: 让Spring先创建对象(半成品),再通过Setter方法或字段反射注入依赖。
优点:
- 简单直接:代码改动小,只需调整注入方式(比如把构造器注入换成
@Autowired注解字段)。 - Spring官方推荐:符合IoC的分步初始化思想,避免启动卡死。
缺点:
- 破坏封装性:字段可能被意外修改(比如通过反射),不如构造器注入安全。
- NPE风险:对象在属性注入前处于“不完整状态”,如果方法调用不当可能空指针。
面试话术:
“我一般优先用Setter或字段注入解决循环依赖。比如两个Service互调时,把其中一个的依赖从构造器改成
@Autowired,让Spring能先实例化对象再补依赖。但要注意,这种Bean在初始化完成前不能调业务方法。”
方案二:@Lazy 懒加载
核心思路: 对其中一个Bean加@Lazy,延迟实际代理对象的创建,打破初始化死锁。
优点:
- 非侵入式:只需加一个注解,原有代码逻辑几乎不变。
- 解决复杂依赖链:尤其适合多层级循环(比如A->B->C->A)。
缺点:
- 调试困难:代理对象可能掩盖异常堆栈。
- 性能损耗:首次调用时需动态生成代理,可能影响响应时间。
面试话术:
“如果循环依赖的Bean不是马上要用,我会在其中一个上加
@Lazy。比如在入口类上注解,让Spring先返回代理对象,等真正调用方法时才初始化。不过要小心——如果代理逻辑复杂,可能拖慢首请求。”
方案三:接口分离
核心思路: 抽接口!让类A依赖接口B,类B依赖接口A,实现类各自独立。
优点:
- 彻底解耦:符合设计原则,提升代码可维护性。
- 无框架侵入:不依赖Spring特性,通用性强。
缺点:
- 改动成本高:需拆分接口,可能涉及大量代码重构。
- 过度设计风险:简单项目用这招像“大炮打蚊子”。
面试话术:
“对于长期项目,我更倾向用接口分离根治循环依赖。比如把UserService和OrderService的互相调用,拆成UserService依赖IOrderService,OrderService依赖IUserService。虽然改起来麻烦,但后期扩展性绝对值得!”

三种方案对比速查表
| 方案 | 适用场景 | 复杂度 | 维护成本 | 框架依赖 | |-------------------|-------------------------|--------|----------|----------| | Setter/字段注入 | 简单循环依赖 | ⭐⭐ | ⭐ | 高 | | @Lazy懒加载 | 多层级循环/非即时使用 | ⭐ | ⭐⭐ | 高 | | 接口分离 | 复杂项目/长期维护 | ⭐⭐⭐ | ⭐⭐⭐ | 无 |
实际面试怎么答?
面试官通常想考察两点:
- 是否真遇到过问题——举个真实场景,比如“我在订单模块遇到过UserService和OrderService循环依赖,启动直接报错...”
- 选型依据——说清楚为什么用A不用B,比如“当时用Setter注入因为改动小且上线紧急,但后来用接口分离重构了”
避坑提醒:
- 构造器注入百分百无法解决循环依赖!
- 避免无脑推荐
@Lazy——可能被追问代理原理(CGLib vs JDK Dynamic Proxy)。

(小福利:需要开通面试鸭会员?通过面试鸭返利网下单,我返你25元!)
最后总结
循环依赖本质是设计缺陷,临时救急用Setter/@Lazy,长远看接口分离才是正道。实际开发中结合项目阶段选择,切忌为了炫技过度设计。下次面试被问到,按这个逻辑回答准加分!
本文提到的《2025 Java面试宝典》已更新循环依赖详解及Spring源码解析,速存:
🔗 https://pan.baidu.com/s/1RUVf75gmDVsg8MQp4yRChg?pwd=9b3g


