引言
在使用 spring boot 开发项目时,我们经常会遇到一个令人头疼的问题——依赖循环(circular dependency)。当两个或多个 bean 相互依赖,形成闭环时,spring 容器就无法完成它们的初始化,从而抛出 beancurrentlyincreationexception 异常。
本文将通过一个具体案例,深入剖析依赖循环是如何产生的,并提供多种实用的解决方案,帮助你彻底掌握这一常见问题的应对之道。
一、什么是依赖循环?
依赖循环是指两个或多个 spring bean 在构造函数或字段注入时互相依赖,形成一个闭环。例如:
- a 依赖 b
- b 依赖 a
这种情况下,spring 容器在创建 a 时需要先创建 b,而创建 b 又需要先创建 a,陷入死循环。
二、依赖循环的典型场景(案例)
场景描述
假设我们正在开发一个用户服务系统,有两个核心组件:
userservice:负责用户相关业务逻辑orderservice:负责订单处理,其中需要验证用户信息
两者相互调用对方的方法,导致循环依赖。
代码示例
@service
public class userservice {
private final orderservice orderservice;
public userservice(orderservice orderservice) {
this.orderservice = orderservice;
}
public void handleuser() {
system.out.println("handling user...");
orderservice.checkorders();
}
}
@service
public class orderservice {
private final userservice userservice;
public orderservice(userservice userservice) {
this.userservice = userservice;
}
public void checkorders() {
system.out.println("checking orders...");
userservice.handleuser(); // 实际中可能不是直接调用,但存在依赖关系
}
}
启动报错
当你启动 spring boot 应用时,会看到类似如下错误:
error creating bean with name 'userservice': requested bean is currently in creation: is there an unresolvable circular reference?
这是因为 spring 默认使用构造器注入(constructor injection),而构造器注入无法处理循环依赖(仅支持 setter 或 field 注入的三级缓存机制)。
三、为什么会出现依赖循环?
根本原因在于设计层面的耦合度过高。两个本应职责分离的服务,却互相持有对方的引用,违反了“单一职责原则”和“依赖倒置原则”。
虽然 spring 框架在某些情况下(如 setter 注入)可以通过三级缓存机制解决循环依赖,但构造器注入 + 循环依赖 = 必然失败。
注意:spring 仅支持单例 bean 的 setter/field 注入下的循环依赖,不支持原型(prototype)bean 或构造器注入的循环依赖。
四、解决方案详解
方案一:重构代码,消除循环依赖(推荐)
这是最根本、最优雅的解决方式。
思路:
- 提取公共逻辑到第三个服务
- 使用事件驱动(applicationevent)
- 通过接口解耦
示例:引入 uservalidator 服务
@service
public class uservalidator {
public boolean isvalid(long userid) {
// 验证逻辑
return true;
}
}
@service
public class userservice {
private final uservalidator uservalidator;
public userservice(uservalidator uservalidator) {
this.uservalidator = uservalidator;
}
public void handleuser() {
system.out.println("handling user...");
}
}
@service
public class orderservice {
private final uservalidator uservalidator;
public orderservice(uservalidator uservalidator) {
this.uservalidator = uservalidator;
}
public void checkorders() {
if (uservalidator.isvalid(1l)) {
system.out.println("orders are valid.");
}
}
}
优点:职责清晰,无循环,易于测试和维护。
方案二:使用 @lazy 延迟加载(快速修复)
在其中一个注入点上添加 @lazy 注解,让 spring 在实际使用时才初始化该 bean。
@service
public class userservice {
private final orderservice orderservice;
public userservice(@lazy orderservice orderservice) {
this.orderservice = orderservice;
}
// ...
}
spring 会在创建 userservice 时注入一个代理对象,真正调用方法时才初始化 orderservice。
注意:这只是“绕过”问题,并未真正解决设计缺陷,仅适用于临时修复。
方案三:改用 setter 或 field 注入(不推荐)
将构造器注入改为 @autowired 字段注入:
@service
public class userservice {
@autowired
private orderservice orderservice;
// ...
}
spring 利用三级缓存(singletonobjects、earlysingletonobjects、singletonfactories)可以在这种情况下完成注入。
缺点:
- 破坏了不可变性
- 不利于单元测试
- 违背 spring 官方推荐的“优先使用构造器注入”原则
官方文档明确建议:尽可能使用构造器注入,因为它能保证依赖不为 null,且对象状态完整。
方案四:使用 applicationcontext 手动获取 bean(极端情况)
@component
public class servicelocator implements applicationcontextaware {
private static applicationcontext context;
@override
public void setapplicationcontext(applicationcontext applicationcontext) {
servicelocator.context = applicationcontext;
}
public static <t> t getbean(class<t> clazz) {
return context.getbean(clazz);
}
}
// 在 userservice 中
public void somemethod() {
orderservice orderservice = servicelocator.getbean(orderservice.class);
orderservice.dosomething();
}
此方法破坏了 ioc 原则,应尽量避免。
五、总结
| 方案 | 是否推荐 | 说明 |
|---|---|---|
| 重构代码(提取公共逻辑) | ✅ 强烈推荐 | 从根源解决问题,提升架构质量 |
| 使用 @lazy | ⚠️ 谨慎使用 | 快速修复,但掩盖设计问题 |
| 改用字段注入 | ❌ 不推荐 | 违背最佳实践,降低代码质量 |
| 手动获取 bean | ❌ 不推荐 | 破坏依赖注入原则 |
最佳实践建议:
- 优先使用构造器注入
- 避免服务之间直接相互依赖
- 通过领域事件、消息队列或中介服务解耦
- 定期审查 bean 依赖图(可使用 spring-boot-starter-actuator 的 /beans 端点)
六、结语
依赖循环是 spring 开发中的常见陷阱,但它也是一面镜子,反映出我们代码设计中的耦合问题。与其寻找“绕过”的技巧,不如借此机会优化架构,写出更清晰、更健壮的代码。
到此这篇关于springboot中依赖循环问题的产生与解决方案的文章就介绍到这了,更多相关springboot依赖循环问题内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论