Spring中的循環(huán)依賴

1. 什么是循環(huán)依賴

當(dāng)一個(gè)bean A依賴于另一個(gè)bean B,并且bean B也依賴于bean A時(shí),就會(huì)發(fā)生這種情況:

Bean A → Bean B → Bean A

當(dāng)然,我們能有更多的beans依賴如下:

Bean A → Bean B → Bean C → Bean D → Bean E → Bean A

2. Spring中發(fā)生了什么

當(dāng)Spring上下文加載所有bean時(shí),它將嘗試按照它們完全工作所需的順序創(chuàng)建bean。 例如,如果我們沒有循環(huán)依賴關(guān)系,例如以下情況:

Bean A→Bean B→Bean C

Spring將創(chuàng)建bean C,然后創(chuàng)建bean B(并將bean C注入到其中),然后創(chuàng)建bean A(并將bean B注入到其中)。

但是,當(dāng)具有循環(huán)依賴關(guān)系時(shí),Spring無法決定應(yīng)首先創(chuàng)建哪個(gè)bean,因?yàn)樗鼈兿嗷ヒ蕾嚒?在這些情況下,Spring在加載上下文時(shí)將引發(fā)BeanCurrentlyInCreationException。

使用構(gòu)造函數(shù)注入時(shí),它可能會(huì)在Spring中發(fā)生。 如果使用其他類型的注入,則應(yīng)該不會(huì)發(fā)現(xiàn)此問題,因?yàn)橐蕾図?xiàng)將在需要時(shí)注入,而不是在上下文加載時(shí)注入。

3. 一個(gè)簡(jiǎn)單的例子

讓我們定義兩個(gè)相互依賴的bean(通過構(gòu)造函數(shù)注入):

@Component
public class CircularDependencyA {
 
    private CircularDependencyB circB;
 
    @Autowired
    public CircularDependencyA(CircularDependencyB circB) {
        this.circB = circB;
    }
}
@Component
public class CircularDependencyB {
 
    private CircularDependencyA circA;
 
    @Autowired
    public CircularDependencyB(CircularDependencyA circA) {
        this.circA = circA;
    }
}

現(xiàn)在,我們可以為測(cè)試編寫一個(gè)Configuration類,將其稱為TestConfig,它指定要掃描組件的基本程序包。 假設(shè)我們的bean是在“ com.baeldung.circulardependency”包中定義的:

@Configuration
@ComponentScan(basePackages = { "com.baeldung.circulardependency" })
public class TestConfig {
}

最后,我們可以編寫一個(gè)JUnit測(cè)試來檢查循環(huán)依賴。 該測(cè)試可以為空,因?yàn)樵谏舷挛募虞d期間將檢測(cè)到循環(huán)依賴關(guān)系。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { TestConfig.class })
public class CircularDependencyTest {
 
    @Test
    public void givenCircularDependency_whenConstructorInjection_thenItFails() {
        // Empty test; we just want the context to load
    }
}

If you try to run this test, you will get the following exception:

BeanCurrentlyInCreationException: Error creating bean with name 'circularDependencyA':
Requested bean is currently in creation: Is there an unresolvable circular reference?

4. 解決方法

我們將展示一些最流行的方法來解決此問題。

4.1 重新設(shè)計(jì)

如果您有循環(huán)依賴關(guān)系,則可能是您遇到了設(shè)計(jì)問題,并且職責(zé)沒有很好地分開。 您應(yīng)該嘗試正確地重新設(shè)計(jì)組件,以使它們的層次結(jié)構(gòu)設(shè)計(jì)得很好,并且不需要循環(huán)依賴項(xiàng)。

如果您不能重新設(shè)計(jì)組件(可能有很多原因:遺留代碼,已經(jīng)過測(cè)試且無法修改的代碼,沒有足夠的時(shí)間或資源來進(jìn)行完全重新設(shè)計(jì)……),則可以嘗試一些變通辦法。

4.2 使用 @Lazy

打破周期的一種簡(jiǎn)單方法是說Spring懶惰地初始化一個(gè)bean。 也就是說:與其完全初始化bean,不如創(chuàng)建一個(gè)代理以將其注入到另一個(gè)bean中。 注入的Bean僅在首次使用時(shí)才完全創(chuàng)建。

要通過我們的代碼嘗試此操作,可以將CircularDependencyA更改為以下內(nèi)容:

@Component
public class CircularDependencyA {
 
    private CircularDependencyB circB;
 
    @Autowired
    public CircularDependencyA(@Lazy CircularDependencyB circB) {
        this.circB = circB;
    }
}

如果現(xiàn)在運(yùn)行測(cè)試,您將看到這次不會(huì)發(fā)生錯(cuò)誤。

4.3 使用 Setter/Field 注入

最受歡迎的解決方法之一,也是Spring文檔提出的建議,就是使用setter注入。

簡(jiǎn)而言之,如果您更改了使用setter注入(或字段注入)而不是構(gòu)造函數(shù)注入的方式連接bean的方式,那么確實(shí)可以解決該問題。 通過這種方式,Spring創(chuàng)建了bean,但是直到需要它們時(shí)才注入依賴項(xiàng)。

我們來做–讓我們更改類以使用setter注入,并將另一個(gè)字段(消息)添加到CircularDependencyB中,以便我們進(jìn)行適當(dāng)?shù)膯卧獪y(cè)試:

@Component
public class CircularDependencyA {
 
    private CircularDependencyB circB;
 
    @Autowired
    public void setCircB(CircularDependencyB circB) {
        this.circB = circB;
    }
 
    public CircularDependencyB getCircB() {
        return circB;
    }
}
@Component
public class CircularDependencyB {
 
    private CircularDependencyA circA;
 
    private String message = "Hi!";
 
    @Autowired
    public void setCircA(CircularDependencyA circA) {
        this.circA = circA;
    }
 
    public String getMessage() {
        return message;
    }
}

現(xiàn)在我們必須對(duì)單元測(cè)試進(jìn)行一些更改:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { TestConfig.class })
public class CircularDependencyTest {
 
    @Autowired
    ApplicationContext context;
 
    @Bean
    public CircularDependencyA getCircularDependencyA() {
        return new CircularDependencyA();
    }
 
    @Bean
    public CircularDependencyB getCircularDependencyB() {
        return new CircularDependencyB();
    }
 
    @Test
    public void givenCircularDependency_whenSetterInjection_thenItWorks() {
        CircularDependencyA circA = context.getBean(CircularDependencyA.class);
 
        Assert.assertEquals("Hi!", circA.getCircB().getMessage());
    }
}

以下說明了上面看到的注釋:

@Bean:告訴Spring框架,必須使用這些方法來檢索要注入的bean的實(shí)現(xiàn)。

@Test:測(cè)試將從上下文中獲取CircularDependencyA bean,并斷言其CircularDependencyB已被正確注入,并檢查其消息屬性的值。

4.4 使用 @PostConstruct

打破周期的另一種方法是在一個(gè)bean上使用@Autowired注入依賴項(xiàng),然后使用帶有@PostConstruct注釋的方法設(shè)置另一個(gè)依賴項(xiàng)。

我們的bean可能具有以下代碼:

@Component
public class CircularDependencyA {
 
    @Autowired
    private CircularDependencyB circB;
 
    @PostConstruct
    public void init() {
        circB.setCircA(this);
    }
 
    public CircularDependencyB getCircB() {
        return circB;
    }
}
@Component
public class CircularDependencyB {
 
    private CircularDependencyA circA;
     
    private String message = "Hi!";
 
    public void setCircA(CircularDependencyA circA) {
        this.circA = circA;
    }
     
    public String getMessage() {
        return message;
    }
}

并且我們可以運(yùn)行與之前相同的測(cè)試,因此我們檢查循環(huán)依存關(guān)系異常是否仍未拋出,并且依存關(guān)系已正確注入。

4.5 實(shí)現(xiàn)ApplicationContextAware and InitializingBean

如果其中一個(gè)bean實(shí)現(xiàn)了ApplicationContextAware,則該bean可以訪問Spring上下文,并且可以從那里提取另一個(gè)bean。 在實(shí)現(xiàn)InitializingBean時(shí),我們指示該bean必須在其所有屬性都已設(shè)置之后才能執(zhí)行一些操作; 在這種情況下,我們要手動(dòng)設(shè)置依賴關(guān)系。

我們的bean的代碼為:

@Component
public class CircularDependencyA implements ApplicationContextAware, InitializingBean {
 
    private CircularDependencyB circB;
 
    private ApplicationContext context;
 
    public CircularDependencyB getCircB() {
        return circB;
    }
 
    @Override
    public void afterPropertiesSet() throws Exception {
        circB = context.getBean(CircularDependencyB.class);
    }
 
    @Override
    public void setApplicationContext(final ApplicationContext ctx) throws BeansException {
        context = ctx;
    }
}
@Component
public class CircularDependencyB {
 
    private CircularDependencyA circA;
 
    private String message = "Hi!";
 
    @Autowired
    public void setCircA(CircularDependencyA circA) {
        this.circA = circA;
    }
 
    public String getMessage() {
        return message;
    }
}

同樣,我們可以運(yùn)行先前的測(cè)試,并查看未引發(fā)異常,并且該測(cè)試按預(yù)期運(yùn)行。

5. 總結(jié)

在Spring中,有很多方法可以處理循環(huán)依賴。 首先要考慮的是重新設(shè)計(jì)bean,因此不需要循環(huán)依賴:它們通常是可以改進(jìn)的設(shè)計(jì)的癥狀。

但是,如果您的項(xiàng)目中絕對(duì)需要循環(huán)依賴,則可以遵循此處建議的一些解決方法。

首選方法是使用二傳手進(jìn)樣。 但是還有其他選擇,通常基于阻止Spring管理Bean的初始化和注入,然后自己使用一種或多種策略來完成。

這些示例可以在GitHub項(xiàng)目中找到。

相關(guān)文章可看:Java詳解之Spring Bean的循環(huán)依賴解決方案

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容