
你有没有过这样的经历?升级到 Spring Boot3 后,原本在 Spring Boot2 里跑的好好的项目,一启动就报出 “Circular dependency detected” 的错误?盯着控制台那串红色报错信息,明明代码逻辑没改多少,可就是找不到问题根源,最后只能临时改代码绕开依赖,心里却始终没底 —— 这循环依赖在 Spring Boot3 里到底是怎么回事?又该怎么彻底解决?
作为常年和 Spring 框架打交道的开发,咱们都清楚循环依赖不是个新鲜事,列如 Service A 依赖 Service B,Service B 又依赖 Service A,这种 “相互依赖” 的场景在复杂业务模块里很常见。但为什么到了 Spring Boot3,以前能自动处理的循环依赖,突然就报错了?这得先从 Spring Boot3 和 Spring Boot2 在循环依赖处理逻辑上的差异说起。
先搞懂:Spring Boot3 为什么对循环依赖更 “严格”?
在 Spring Boot2 中,默认使用的是 Spring Framework 5.x,对于单例 Bean 的循环依赖,Spring 会通过 “三级缓存” 机制自动解决 —— 简单说就是先创建 Bean 的早期对象,把它放进缓存里,等依赖的 Bean 创建完成后,再回过头来填充早期对象的依赖,最后完成 Bean 的初始化。这种处理方式让咱们开发时不用过多担心单例 Bean 的循环依赖问题。
但到了 Spring Boot3,它对应的 Spring Framework 6.x 做了一个重大调整:默认关闭了对 “构造器注入循环依赖” 的支持,同时对 “字段注入循环依赖” 的处理也增加了更严格的校验。为什么要这么改?Spring 官方给出的解释是,循环依赖本质上是代码设计不够合理的体现,过度依赖框架的自动处理,可能会掩盖代码架构上的问题,列如模块职责划分不清晰、业务逻辑耦合过高等。而且随着 Spring 对 Bean 初始化流程的优化,保留对所有循环依赖场景的支持,会影响 Bean 创建的效率和稳定性。
不过咱们也不用慌,Spring Boot3 并不是完全禁止了循环依赖,而是对不同注入方式的循环依赖做了区分处理。接下来就针对实际开发中最常见的三种注入场景,教你怎么一步步解决循环依赖问题。
三种常见场景:Spring Boot3 循环依赖的解决方案
场景 1:字段注入(@Autowired)导致的循环依赖
这是咱们开发中用得最多的注入方式,列如:
@Service
public class UserService {
@Autowired
private OrderService orderService;
// 业务方法...
}
@Service
public class OrderService {
@Autowired
private UserService userService;
// 业务方法...
}
在 Spring Boot3 中,这种字段注入的循环依赖,默认情况下依然是支持的,但如果启动时还是报错,大致率是由于你在配置类里加了这样一行代码:
@Configuration
public class AppConfig {
@Bean
public static BeanFactoryPostProcessor beanFactoryPostProcessor() {
return beanFactory -> beanFactory.setAllowCircularReferences(false);
}
}
这里的
setAllowCircularReferences(false)就是手动关闭了循环依赖支持,把它改成true,或者直接删掉这个配置,重启项目就能解决问题。
不过要提醒一句:虽然字段注入能自动解决循环依赖,但它有个缺点 —— 依赖的 Bean 在初始化时可能为null,如果不小心在构造方法里使用依赖的 Bean,就会报空指针异常。所以用字段注入时,尽量在@PostConstruct方法里初始化依赖相关的逻辑,避免在构造器中操作依赖 Bean。
场景 2:构造器注入导致的循环依赖
如果你的项目里用了构造器注入,列如:
@Service
public class UserService {
private final OrderService orderService;
// 构造器注入
public UserService(OrderService orderService) {
this.orderService = orderService;
}
}
@Service
public class OrderService {
private final UserService userService;
// 构造器注入
public OrderService(UserService userService) {
this.userService = userService;
}
}
这种情况在 Spring Boot3 中必定会报错,由于 Spring Framework 6.x 默认不支持构造器注入的循环依赖。那该怎么改?有两种常用方案:
第一种:改用字段注入或setter注入。如果项目对注入方式没有强制要求,把构造器注入改成@Autowired字段注入,就能直接解决问题,这也是最快捷的方式。
第二种:如果必须用构造器注入(列如项目遵循 DDD 架构,要求依赖通过构造器显式声明),可以给其中一个 Bean 的注入加上@Lazy注解,让 Spring 延迟初始化依赖的 Bean。修改后的代码如下:
@Service
public class UserService {
private final OrderService orderService;
public UserService(@Lazy OrderService orderService) {
this.orderService = orderService;
}
}
@Service
public class OrderService {
private final UserService userService;
public OrderService(UserService userService) {
this.userService = userService;
}
}
@Lazy注解的作用是,Spring 在创建 UserService 时,不会立即创建 OrderService 的实例,而是先创建一个 OrderService 的代理对象注入到 UserService 中,等后续真正用到 OrderService 时,再去初始化它的实例。这样就打破了 “创建 UserService 需要 OrderService,创建 OrderService 又需要 UserService” 的死循环。
场景 3:原型 Bean(@Scope (“prototype”))的循环依赖
不管是 Spring Boot2 还是 Spring Boot3,原型 Bean 的循环依赖都是默认不支持的。由于原型 Bean 每次请求都会创建一个新实例,Spring 的三级缓存机制只针对单例 Bean,没办法缓存原型 Bean 的早期对象。列如:
@Service
@Scope("prototype")
public class UserService {
@Autowired
private OrderService orderService;
}
@Service
@Scope("prototype")
public class OrderService {
@Autowired
private UserService userService;
}
这种情况启动时会直接报错,解决方案只有一个:重构代码,消除循环依赖。怎么重构?核心思路是 “提取公共依赖” 或 “调整依赖方向”。
举个例子,如果 UserService 和 OrderService 都依赖对方的 “查询用户订单” 和 “查询订单所属用户” 的方法,可以把这两个方法提取到一个新的UserOrderCommonService中,然后让 UserService 和 OrderService 都依赖这个新的 Service,而不是相互依赖。重构后的代码如下:
// 提取的公共Service
@Service
public class UserOrderCommonService {
// 原UserService中的查询订单方法
public List<Order> getOrdersByUserId(Long userId) {
// 业务逻辑...
}
// 原OrderService中的查询用户方法
public User getUserByOrderId(Long orderId) {
// 业务逻辑...
}
}
@Service
@Scope("prototype")
public class UserService {
@Autowired
private UserOrderCommonService commonService;
// 用commonService取代原来的OrderService
}
@Service
@Scope("prototype")
public class OrderService {
@Autowired
private UserOrderCommonService commonService;
// 用commonService取代原来的UserService
}
这样一来,不仅解决了原型 Bean 的循环依赖问题,还让代码的职责更清晰 ——UserService 专注于用户相关业务,OrderService 专注于订单相关业务,公共的跨模块逻辑放在 UserOrderCommonService 中,后续维护也更方便。
总结:解决循环依赖,更要避免循环依赖
看到这里,信任你已经清楚 Spring Boot3 中循环依赖的解决方法了。最后再跟你梳理几个核心要点,帮你少踩坑:
- 优先用字段注入解决单例 Bean 循环依赖:如果是单例 Bean,字段注入(@Autowired)在 Spring Boot3 中默认支持,简单高效,适合大多数场景;
- 构造器注入加 @Lazy 打破循环:必须用构造器注入时,给其中一个依赖加 @Lazy,延迟初始化就能解决问题;
- 原型 Bean 只能重构代码:别指望框架自动处理,提取公共模块、调整依赖关系,从根源上消除循环依赖才是王道;
- 别轻易关闭循环依赖支持:除非项目架构超级清晰,能确保没有循环依赖,否则不要手动设置setAllowCircularReferences(false),避免不必要的报错。
实则 Spring Boot3 对循环依赖的 “严格化”,也是在提醒咱们:好的代码不应该依赖框架来 “兜底”。日常开发中,写完代码后多思考一下 —— 这个依赖是不是必须的?模块之间的职责有没有重叠?能不能通过拆分 Service、引入中间层等方式,让代码更简洁、耦合更低?
最后想问问你:你在升级 Spring Boot3 时,还遇到过哪些循环依赖相关的坑?是怎么解决的?欢迎在评论区分享你的经验,咱们一起交流学习,把 Spring Boot3 用得更溜!




