DevFix
Spring Boot已验证

Spring Boot 启动报 BeanCurrentlyInCreationException?循环依赖的坑与解法

@debug_master更新于 3 天前阅读 6 min0

一句话先说结论:Bean 循环依赖就是 A 依赖 B、B 又依赖 A,Spring 创建时先给 bean 打上「正在创建」标记,回头再要同一个 bean 就撞上 BeanCurrentlyInCreationException。Spring Boot 2.6 起把 spring.main.allow-circular-references 默认值从 true 改成了 false,所以老项目一升级就启动失败。最该做的是重构拆掉这个环;图快可以用 @Lazy 或换字段/setter 注入,临时兜底才去开那个开关。

背景

Spring 的 ApplicationContext 在启动阶段会按依赖关系把一个个 bean 实例化并组装起来。正常情况下,这张依赖图是一棵有向无环的树。循环依赖让图里出现了环,等于「先有鸡还是先有蛋」——容器没法把两边的对象都完整造出来。

最常见的现场是两个 @Service 互相注入:OrderService 要调用 ProductService 查商品,ProductService 又要调 OrderService 查订单。业务上看着合理,结构上却已经耦合过紧。

现象

升级到 2.6+,或者直接改成构造器注入,应用启动直接失败。控制台常见两种报法:

旧样式是底层异常:

org.springframework.beans.factory.BeanCurrentlyInCreationException:
Error creating bean with name 'beanA':
Requested bean is currently in creation: Is there an unresolvable circular reference?

2.6+ 的 FailureAnalysis 报得更直观,会直接把环画出来:

***************************
APPLICATION FAILED TO START
***************************

Description:

The dependencies of some of the beans in the application context form a cycle:

┌─────┐
| orderService defined in file [.../OrderService.class]
↑ ↓
| productService defined in file [.../ProductService.class]
└─────┘

Action:

Relying upon circular references is discouraged and they are prohibited by default.
Update your application to remove the dependency cycle between beans.
As a last resort, it may be possible to break the cycle automatically by setting
spring.main.allow-circular-references to true.

能触发这套报错的复现代码长这样:

@Service
public class OrderService {
    private final ProductService productService;
    public OrderService(ProductService productService) {
        this.productService = productService;
    }
}

@Service
public class ProductService {
    private final OrderService orderService;
    public ProductService(OrderService orderService) {
        this.orderService = orderService;
    }
}

根因分析

Spring 创建单例 bean 时,第一步先把它放进 singletonsCurrentlyInCreation,标记「正在创建」。OrderService 的构造器需要 ProductService,容器就转去创建 ProductService;而 ProductService 的构造器又要 OrderService,容器一看「这个 bean 我正在建呢」,于是抛出 BeanCurrentlyInCreationException

构造器注入之所以无解,是因为必须先拿到所有依赖才能调用构造器完成实例化,中间不存在「半成品」可以提前暴露。而字段/setter 注入则不同——Spring 框架本身有三级缓存(singletonFactoriesearlySingletonObjects 这些),能在属性注入阶段把还没完全初始化的 bean 提前暴露出来,所以 2.5 之前默认就能「绕过去」。

真正的分水岭是 Spring Boot 2.6.0:它把 spring.main.allow-circular-references 的默认值从 true 改成了 false。也就是说,哪怕你用的是字段/setter 注入,2.6 之后也会在启动时被主动拦下来。这不是 bug,而是刻意为之——循环依赖往往是设计缺陷,fail fast 逼你去修,而不是让半初始化的 bean 在运行时埋雷。

排查时最有用的就是上面那段 cycle 图,它会列出环上每一个 bean。如果还看不清,就把 bean 的日志级别调成 DEBUG:

logging.level.org.springframework.beans=DEBUG

解决方案

首选:重构,拆掉环

从根本上让依赖单向化。最常见的做法是把双方共用的逻辑抽到第三个 bean 里:

@Service
public class PricingService {
    public Money calc(Order order) { /* ... */ }
}

@Service
public class OrderService {
    private final PricingService pricing;
    public OrderService(PricingService pricing) { this.pricing = pricing; }
}

@Service
public class ProductService {
    private final PricingService pricing;
    public ProductService(PricingService pricing) { this.pricing = pricing; }
}

环消失了,两个 service 都只依赖 PricingService。这是最干净、最可持续的方案,也是官方推荐的方向。

@Lazy:打断即时初始化

在环的任意一边注入点加 @Lazy,Spring 会先注入一个代理,真正用到时才去初始化被依赖的 bean,从而打断启动时的死锁:

@Service
public class OrderService {
    private final ProductService productService;
    public OrderService(@Lazy ProductService productService) {
        this.productService = productService;
    }
}

构造器、字段、setter 三种注入都适用。代价是这只是绕过,不是消除,代码里依然存在强耦合,而且延迟初始化可能把错误推迟到运行时才暴露。

字段 / setter 注入 + 打开开关(临时兜底)

如果 2.6 之前用字段注入一直没问题、升级后才踩雷,短期内可以在配置里放开:

spring:
  main:
    allow-circular-references: true

开关打开后,字段或 setter 注入能借助三级缓存正常构建,但构造器注入依旧不行。这个开关在官方文档里被明确标为 last resort,属于迁移过渡的临时手段,不建议长期依赖——你等于保留下了一个设计缺陷,而且它可能在未来的版本里被彻底移除。

小结

  • 这个报错的本质是依赖图成环,不是 Spring 抽风。
  • 升级到 2.6+ 最容易撞上,因为默认禁止循环依赖了。
  • 优先重构拆环;图快用 @Lazy;别图省事长期开着 allow-circular-references
  • 排查先从 cycle 图入手,必要时打开 logging.level.org.springframework.beans=DEBUG

来源

最后更新于 2026-08-22

这篇帮到你了吗?

刚解决了一个棘手的报错?花两分钟记录下来,帮助下一个遇到同样问题的开发者。

贡献一条解法