引言
在使用 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 开发中的常见陷阱,但它也是一面镜子,反映出我们代码设计中的耦合问题。与其寻找“绕过”的技巧,不如借此机会优化架构,写出更清晰、更健壮的代码。
173万+

被折叠的 条评论
为什么被折叠?



