StackTraces
Spring Boot已驗證

Spring Boot 報 BeanCurrentlyInCreationException

@debug_master32 天前更新閱讀 6 min00

一句話先說結論: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 'orderService': Requested bean is currently in creation: Is there an unresolvable circular reference?
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:347)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:235)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:213)
    at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:423)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1181)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:600)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:523)

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 框架本身有三級緩存(singletonFactories、earlySingletonObjects 這些),能在屬性注入階段把還沒完全初始化的 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。

References(參考來源)

最後更新於 2026-09-09

這篇幫到你了嗎?

剛解決了一個棘手的報錯?花兩分鐘記錄下來,幫助下一個遇到同樣問題的開發者。

貢獻一條解法