如何解决 Spring Bean 循环依赖
在用 Spring 开发项目时,经常会遇到一个让人头疼的问题——循环依赖。最典型的场景就是:A 注入了 B,B 又注入了 A,结果 Spring 在启动的时候就直接报错了。这种问题并不罕见,尤其在业务逻辑复杂、Bean 之间相互依赖较多的系统里,很容易就掉坑里。本文就结合一个小 Demo,带你一步步看清楚 Spring Bean 循环依赖的本质原因,并且给出几种常见的解决思路。
大家好,我是 展菲,目前在上市企业从事人工智能项目研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括iOS、前端、Harmony OS、Java、Python等方向。在移动端开发、鸿蒙开发、物联网、嵌入式、云原生、开源等领域有深厚造诣。
图书作者:《ESP32-C3 物联网工程开发实战》
图书作者:《SwiftUI 入门,进阶与实战》
超级个体:COC上海社区主理人
特约讲师:大学讲师,谷歌亚马逊分享嘉宾
科技博主:华为HDE/HDG
我的博客内容涵盖广泛,主要分享技术教程、Bug解决方案、开发工具使用、前沿科技资讯、产品评测与使用体验。我特别关注云服务产品评测、AI 产品对比、开发板性能测试以及技术报告,同时也会提供产品优缺点分析、横向对比,并分享技术沙龙与行业大会的参会体验。我的目标是为读者提供有深度、有实用价值的技术洞察与分析。
展菲:您的前沿技术领航员
👋 大家好,我是展菲!
📱 全网搜索“展菲”,即可纵览我在各大平台的知识足迹。
📣 公众号“Swift社区”,每周定时推送干货满满的技术长文,从新兴框架的剖析到运维实战的复盘,助您技术进阶之路畅通无阻。
💬 微信端添加好友“fzhanfei”,与我直接交流,不管是项目瓶颈的求助,还是行业趋势的探讨,随时畅所欲言。
📅 最新动态:2025 年 3 月 17 日
快来加入技术社区,一起挖掘技术的无限潜能,携手迈向数字化新征程!
前言
在用 Spring 开发项目时,经常会遇到一个让人头疼的问题——循环依赖。
最典型的场景就是:A
注入了 B
,B
又注入了 A
,结果 Spring 在启动的时候就直接报错了。
这种问题并不罕见,尤其在业务逻辑复杂、Bean 之间相互依赖较多的系统里,很容易就掉坑里。本文就结合一个小 Demo,带你一步步看清楚 Spring Bean 循环依赖的本质原因,并且给出几种常见的解决思路。
背景:为什么会有循环依赖?
假设有两个业务类:
UserService
依赖OrderService
来查询订单。OrderService
又依赖UserService
来获取用户信息。
在代码里写出来就是这样:
@Service
public class UserService {
@Autowired
private OrderService orderService;
public void getUserInfo() {
System.out.println("UserService: 获取用户信息");
orderService.getOrderInfo();
}
}
@Service
public class OrderService {
@Autowired
private UserService userService;
public void getOrderInfo() {
System.out.println("OrderService: 获取订单信息");
userService.getUserInfo();
}
}
启动项目的时候,Spring 容器在实例化 UserService
时需要先实例化 OrderService
,
但 OrderService
又需要 UserService
,结果就会进入死循环,最终抛出 BeanCurrentlyInCreationException
。
报错信息一般长这样:
Error creating bean with name 'userService': Requested bean is currently in creation: Is there an unresolvable circular reference?
Demo:复现循环依赖问题
我们来写一个最小可运行的 Spring Boot Demo。
Application.java
:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
UserService.java
和 OrderService.java
就是上面那两个。
运行后你就能看到启动失败,并且日志里清楚提示是循环依赖导致的。
重构代码,消除循环
第一种解决办法其实很直白:从业务逻辑上消除循环依赖。
比如在上面的例子中,UserService
和 OrderService
其实不应该互相依赖,而应该通过一个中间的 DAO 层 或者 Facade 层 来解耦。
优化后的写法:
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public void getUserInfo() {
System.out.println("UserService: 获取用户信息");
userRepository.findById(1L);
}
}
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
public void getOrderInfo() {
System.out.println("OrderService: 获取订单信息");
orderRepository.findById(1L);
}
}
这样 UserService
和 OrderService
就互相独立了,循环依赖自然就消失了。
这也是最推荐的方式:设计层面尽量避免相互依赖。
使用 @Lazy 注解
如果重构比较麻烦,Spring 还提供了一个小技巧:在其中一个依赖上加 @Lazy
。
@Service
public class UserService {
@Autowired
@Lazy
private OrderService orderService;
public void getUserInfo() {
System.out.println("UserService: 获取用户信息");
orderService.getOrderInfo();
}
}
这样 Spring 在实例化 UserService
时不会立刻去创建 OrderService
,而是等真正用到的时候才去注入。
这种方式适合于临时解决问题,但需要注意:
- 如果两个 Bean 的依赖逻辑很复杂,
@Lazy
可能只是在延迟报错; - 滥用
@Lazy
可能掩盖系统的设计问题。
避免构造函数循环注入
很多人为了写出不可变对象,会习惯性地用构造函数注入。但要注意,如果出现循环依赖,构造函数注入会直接挂掉,Spring 没法解决。
错误示例:
@Service
public class UserService {
private final OrderService orderService;
@Autowired
public UserService(OrderService orderService) {
this.orderService = orderService;
}
}
@Service
public class OrderService {
private final UserService userService;
@Autowired
public OrderService(UserService userService) {
this.userService = userService;
}
}
这种写法一旦循环依赖,Spring 根本没机会注入,就直接失败了。
因此在有可能产生循环依赖的场景下,建议用 Setter 注入 或 字段注入,再配合 @Lazy
,避免死循环。
实际场景中的思考
循环依赖问题并不只是技术 bug,更多时候反映了代码设计上的问题:
- 如果两个 Service 互相调用,很可能是业务边界没有划清。
- 如果一个 Service 同时依赖多个下游,可能说明它承担了过多职责,需要拆分。
- 如果实在绕不开依赖,最好通过接口或者事件机制来解耦,而不是直接注入对方。
举个例子:
在电商系统中,订单服务 和 库存服务 就是经典的相互依赖。订单需要库存确认才能生成,而库存也需要知道订单的结果来更新。
这类场景往往不是直接互相调用,而是通过 消息队列 或 事件驱动 的方式解决。这样既避免了循环依赖,又提升了系统的解耦性。
总结
- Spring Bean 循环依赖 本质是对象实例化和依赖注入的顺序问题。
- 最好的解决办法是重构代码,消除循环;
- 如果实在改不了,可以用
@Lazy
延迟注入,或者改用 Setter 注入; - 构造函数注入要慎用,一旦循环依赖会直接报错。
从设计角度来看,循环依赖通常意味着模块职责划分不合理。与其依赖 Spring 的补救措施,不如在业务设计阶段就避免这种耦合。
更多推荐
所有评论(0)