萬字長文帶你徹底吃透Spring迴圈依賴,堪稱全網最全(文末福利)

碼農談IT發表於2023-12-06


來源:冰河技術

  • 本章難度:★★★★☆
  • 本章重點:進一步學習並掌握迴圈依賴相關的問題,從原始碼級別徹底掌握Spring解決迴圈依賴的執行流程。

本章目錄如下所示:

  • 學習指引
  • 迴圈依賴概述
  • 迴圈依賴型別
    • 自我依賴
    • 直接依賴
    • 間接依賴
  • 迴圈依賴場景
    • 單例Bean的setter迴圈依賴的特殊情況
    • 多例Bean的setter迴圈依賴
    • 代理物件的setter迴圈依賴
    • 構造方法的迴圈依賴
    • @DependsOn的迴圈依賴
    • 單例Bean的setter迴圈依賴
  • Spring迴圈依賴底層解決方案分析
    • 不支援單例Bean的setter迴圈依賴的特殊情況
    • 不支援多例Bean的setter迴圈依賴
    • 不支援代理物件的setter迴圈依賴
    • 不支援構造方法的迴圈依賴
    • 不支援@DependsOn的迴圈依賴
    • 支援單例Bean的setter迴圈依賴
  • 總結
  • 思考

一、學習指引

Spring中的迴圈依賴問題,你真的徹底瞭解過嗎?

Spring的迴圈依賴問題可以說是面試過程中出現的非常頻繁的問題。比如,在面試過程中面試官可能會問:有了解過Spring中的迴圈依賴問題嗎?或者會問:什麼是迴圈依賴問題?或者會問:Spring中在哪些場景下會產生迴圈依賴問題?或者會問:Spring是如何解決迴圈依賴問題的?看似輕描淡寫的一句話,實際要考察的內容也是比較多的。面試官可以透過這個問題考察面試者是否研究過Spring的原始碼,有沒有了解過Spring中的迴圈依賴問題。

文末掃碼加星球立減¥200,僅限2023年12月,僅限前300名,先到先得。

本章,我們就一起來聊聊Spring中的迴圈依賴問題。

二、迴圈依賴概述

什麼是迴圈依賴?

迴圈依賴其實也很好理解,可以將這個詞拆分成兩部分,一個是迴圈,一個是依賴。迴圈,顧名思義就是指形成了一個閉合環路,也就是閉環。依賴就是指某個事件的發生要依賴另一個事件。

在Spring中的迴圈依賴就是指一個或者多個Bean之間存在著互相依賴的關係,並且形成了迴圈呼叫。例如,在Spring中,A依賴B,B又依賴A,A和B之間就形成了相互依賴的關係。建立A物件時,發現A物件依賴了B物件,此時先去建立B物件。建立B物件時,發現B物件又依賴了A物件,此時又去建立A物件。建立A物件時,發現A物件依賴了B物件....如果Spring不去處理這種情況,就會發生死迴圈,一直會建立A物件和B物件,直到丟擲異常為止。

同理,在Spring中多個物件之間也有可能存在迴圈依賴,例如,A依賴B,B依賴C,C又依賴A,A、B、C之間形成了互相依賴的關係,這也是一種迴圈依賴。

三、迴圈依賴型別

迴圈依賴有這些型別呢?

迴圈依賴總體上可以分成:自我依賴、直接依賴和間接依賴三種型別,如圖20-1所示。

萬字長文帶你徹底吃透Spring迴圈依賴,堪稱全網最全(文末福利)

3.1 自我依賴

自我依賴就是自己依賴自己,從而形成的迴圈依賴,一般情況下不會發生這種迴圈依賴,如圖20-2所示。

萬字長文帶你徹底吃透Spring迴圈依賴,堪稱全網最全(文末福利)

3.2 直接依賴

直接依賴一般是發生在兩個物件之間,例如物件A依賴物件B,物件B又依賴物件A,物件A和物件B之間形成了依賴關係,如圖20-3所示。

萬字長文帶你徹底吃透Spring迴圈依賴,堪稱全網最全(文末福利)

3.3 間接依賴

間接依賴一般是發生在三個或三個以上物件之間互相依賴的場景,例如物件A依賴物件B,物件B依賴物件C,物件C又依賴物件A,物件A、物件B和物件C之間就形成了迴圈依賴,如圖20-4所示。

萬字長文帶你徹底吃透Spring迴圈依賴,堪稱全網最全(文末福利)

四、迴圈依賴場景

Spring中有哪些迴圈依賴的場景?

Spring中的迴圈依賴場景總體上可以分成單例Bean的setter迴圈依賴、多例Bean的setter迴圈依賴、代理物件的setter迴圈依賴、構造方法的迴圈依賴和DependsOn的迴圈依賴。如圖20-5所示。

萬字長文帶你徹底吃透Spring迴圈依賴,堪稱全網最全(文末福利)

4.1 單例Bean的setter迴圈依賴的特殊情況

Spring是支援基於單例Bean的setter方法的迴圈依賴的,不過有一種特殊情況需要注意。本節,我們就一起實現基於單例Bean的setter方法的迴圈依賴的特殊情況,具體實現步驟如下所示。

(1)新增SpecialCircularBeanA類

SpecialCircularBeanA類的原始碼詳見:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.special.bean.SpecialCircularBeanA。

@Component
public class SpecialCircularBeanA {
    @Autowired
    private SpecialCircularBeanB specialCircularBeanB;
    @Override
    public String toString() {
        return "SpecialCircularBeanA{" +
                "specialCircularBeanB=" + specialCircularBeanB +
                '}';
    }
}

可以看到,在SpecialCircularBeanA類上只標註了@Component註解,所以在IOC容器啟動時,會在IOC容器中建立SpecialCircularBeanA型別的單例Bean,並且在SpecialCircularBeanA型別的Bean物件中,會依賴SpecialCircularBeanB型別的Bean物件。同時,在SpecialCircularBeanA類中重寫了toString()方法,列印了依賴的SpecialCircularBeanB型別的物件。

(2)新增SpecialCircularBeanB類

SpecialCircularBeanB類的原始碼詳見:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.special.bean.SpecialCircularBeanB。

@Component
public class SpecialCircularBeanB {
    @Autowired
    private SpecialCircularBeanA specialCircularBeanA;
    @Override
    public String toString() {
        return "SpecialCircularBeanB{" +
                "specialCircularBeanA=" + specialCircularBeanA +
                '}';
    }
}

可以看到,在SpecialCircularBeanB類上只標註了@Component註解,所以在IOC容器啟動時,會在IOC容器中建立SpecialCircularBeanB型別的單例Bean,並且在SpecialCircularBeanB型別的Bean物件中,會依賴SpecialCircularBeanA型別的Bean物件。同時,在SpecialCircularBeanB類中重寫了toString()方法,列印了依賴的SpecialCircularBeanA型別的物件。

(3)新增SpecialCircularConfig類

SpecialCircularConfig類的原始碼詳見:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.special.config.SpecialCircularConfig。

@Configuration
@ComponentScan(value = {"io.binghe.spring.annotation.chapter20.special"})
public class SpecialCircularConfig {
}

可以看到,在SpecialCircularConfig類上標註了@Configuration註解,說明SpecialCircularConfig類是案例程式的配置類,並且使用@ComponentScan註解指定了掃描的包。

(4)新增SpecialCircularTest類

SpecialCircularTest類的原始碼詳見:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.special.SpecialCircularTest。

public class SpecialCircularTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpecialCircularConfig.class);
        SpecialCircularBeanA specialCircularBeanA = context.getBean(SpecialCircularBeanA.class);
        System.out.println(specialCircularBeanA);
        context.close();
    }
}

可以看到,在SpecialCircularTest類的main()方法中,傳入SpecialCircularConfig類的Class物件後,建立IOC容器,隨後從IOC容器中獲取SpecialCircularBeanA型別的Bean物件並進行列印,最後關閉IOC容器。

(5)執行SpecialCircularTest類

執行SpecialCircularTest類的main()方法,輸出的結果資訊如下所示。

Exception in thread "main" java.lang.StackOverflowError

可以看到,程式丟擲了StackOverflowError異常。

其實,從本質上講,這個異常不是Spring丟擲的,而是JVM丟擲的棧溢位錯誤。

4.2 多例Bean的setter迴圈依賴

Spring是不支援基於多例Bean,也就是原型模式下的Bean的setter方法的迴圈依賴。本節,我們一起實現一個基於多例Bean的set方法的迴圈依賴案例,具體實現步驟如下所示。

(1)新增PrototypeCircularBeanA類

PrototypeCircularBeanA類的原始碼詳見:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.prototype.bean.PrototypeCircularBeanA。

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class PrototypeCircularBeanA {
    @Autowired
    private PrototypeCircularBeanB prototypeCircularBeanB;

    public PrototypeCircularBeanB getPrototypeCircularBeanB() {
        return prototypeCircularBeanB;
    }
}

可以看到,PrototypeCircularBeanA類的物件在Spring中是多例Bean,並且依賴了PrototypeCircularBeanB型別的Bean物件,並提供了getPrototypeCircularBeanB()方法返回PrototypeCircularBeanB型別的Bean物件。

(2)新增PrototypeCircularBeanB類

PrototypeCircularBeanB類的原始碼詳見:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.prototype.bean.PrototypeCircularBeanB。

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class PrototypeCircularBeanB {
    @Autowired
    private PrototypeCircularBeanA prototypeCircularBeanA;
    
    public PrototypeCircularBeanA getPrototypeCircularBeanA() {
        return prototypeCircularBeanA;
    }
}

可以看到,PrototypeCircularBeanB類的物件在Spring中是多例Bean,並且依賴了PrototypeCircularBeanA型別的Bean物件,並提供了getPrototypeCircularBeanA()方法返回PrototypeCircularBeanA型別的Bean物件。

(3)新增PrototypeCircularConfig類

PrototypeCircularConfig類的原始碼詳見:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.prototype.config.PrototypeCircularConfig。

@Configuration
@ComponentScan(value = {"io.binghe.spring.annotation.chapter20.prototype"})
public class PrototypeCircularConfig {
}

可以看到,在PrototypeCircularConfig類上標註了@Configuration註解,說明PrototypeCircularConfig類是案例程式的配置類,並且使用@ComponentScan註解指定了要掃描的包名。

(4)新增PrototypeCircularTest類

PrototypeCircularTest類的原始碼詳見:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.prototype.PrototypeCircularTest。

public class PrototypeCircularTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(PrototypeCircularConfig.class);
        PrototypeCircularBeanA prototypeCircularBeanA = context.getBean(PrototypeCircularBeanA.class);
        System.out.println("prototypeCircularBeanA===>>>" + prototypeCircularBeanA);
        System.out.println("prototypeCircularBeanB===>>>" + prototypeCircularBeanA.getPrototypeCircularBeanB());
        context.close();
    }
}

可以看到,在PrototypeCircularTest類的main()方法中,建立完IOC容器後,會從IOC容器中獲取PrototypeCircularBeanA型別的Bean物件,並列印PrototypeCircularBeanA型別的Bean物件和依賴的PrototypeCircularBeanB型別的Bean物件。

(5)執行PrototypeCircularTest類

執行PrototypeCircularTest類的main()方法,輸出的結果資訊如下所示。

Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'prototypeCircularBeanA': Unsatisfied dependency expressed through field 'prototypeCircularBeanB': Error creating bean with name 'prototypeCircularBeanB': Unsatisfied dependency expressed through field 'prototypeCircularBeanA': Error creating bean with name 'prototypeCircularBeanA': Requested bean is currently in creation: Is there an unresolvable circular reference?
/***************省略其他輸出資訊**************/
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'prototypeCircularBeanB': Unsatisfied dependency expressed through field 'prototypeCircularBeanA': Error creating bean with name 'prototypeCircularBeanA': Requested bean is currently in creation: Is there an unresolvable circular reference?
/***************省略其他輸出資訊**************/
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'prototypeCircularBeanA': Requested bean is currently in creation: Is there an unresolvable circular reference?
/***************省略其他輸出資訊**************/

可以看到,輸出結果中列印出了迴圈依賴的異常資訊,說明Spring不支援多例Bean的setter方法迴圈依賴。

4.3 代理物件的setter迴圈依賴

Spring預設是不支援基於代理物件的setter方法的迴圈依賴,本節,就簡單實現一個基於代理物件的setter方法的迴圈依賴案例。具體實現步驟如下所示。

(1)新增ProxyCircularBeanA類

ProxyCircularBeanA類的原始碼詳見:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.proxy.bean.ProxyCircularBeanA。

@Component
public class ProxyCircularBeanA {
    @Autowired
    private ProxyCircularBeanB proxyCircularBeanB;

    @Async
    public ProxyCircularBeanB getProxyCircularBeanB() {
        return proxyCircularBeanB;
    }
}

可以看到,ProxyCircularBeanA型別的Bean物件會依賴ProxyCircularBeanB型別的Bean物件,並且在getProxyCircularBeanB()方法上標註了@Async註解,當呼叫getProxyCircularBeanB()方法時,會透過AOP自動生成代理物件。

(2)新增ProxyCircularBeanB類

ProxyCircularBeanB類的原始碼詳見:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.proxy.bean.ProxyCircularBeanB。

@Component
public class ProxyCircularBeanB {
    @Autowired
    private ProxyCircularBeanA proxyCircularBeanA;

    @Async
    public ProxyCircularBeanA getProxyCircularBeanA() {
        return proxyCircularBeanA;
    }
}

可以看到,ProxyCircularBeanB型別的Bean物件會依賴ProxyCircularBeanA型別的Bean物件,並且在getProxyCircularBeanA()方法上標註了@Async註解,當呼叫getProxyCircularBeanA()方法時,會透過AOP自動生成代理物件。

(3)新增ProxyCircularConfig類

ProxyCircularConfig類的原始碼詳見:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.proxy.config.ProxyCircularConfig。

@EnableAsync
@Configuration
@ComponentScan(value = {"io.binghe.spring.annotation.chapter20.proxy"})
public class ProxyCircularConfig {
}

可以看到,在ProxyCircularConfig類上標註了@Configuration註解,說明ProxyCircularConfig類是案例程式的配置類。並且在ProxyCircularConfig類上使用@ComponentScan註解指定了要掃描的包名。同時,使用@EnableAsync註解開啟了非同步呼叫。

(4)新增ProxyCircularTest類

ProxyCircularTest類的原始碼詳見:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.proxy.ProxyCircularTest。

public class ProxyCircularTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ProxyCircularConfig.class);
        ProxyCircularBeanA proxyCircularBeanA = context.getBean(ProxyCircularBeanA.class);
        System.out.println("proxyCircularBeanA===>>>" + proxyCircularBeanA);
        System.out.println("proxyCircularBeanB===>>>" + proxyCircularBeanA.getProxyCircularBeanB());
        context.close();
    }
}

可以看到,在ProxyCircularTest類的main()方法中,建立完IOC容器後,會從IOC容器中獲取ProxyCircularBeanA型別的Bean物件,並列印ProxyCircularBeanA型別的Bean物件和依賴的ProxyCircularBeanB型別的Bean物件。

(5)執行ProxyCircularTest類

執行ProxyCircularTest類的main()方法,輸出的結果資訊如下所示。

Exception in thread "main" org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'proxyCircularBeanA': Bean with name 'proxyCircularBeanA' has been injected into other beans [proxyCircularBeanB] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.

從輸出的結果資訊可以看出,Spring丟擲了迴圈依賴的異常。說明Spring預設不支援基於代理物件的setter方法的迴圈依賴。

4.4 構造方法的迴圈依賴

Spring不支援基於構造方法的迴圈依賴。本節,就簡單實現一個基於構造方法的迴圈依賴,具體實現步驟如下所示。

(1)新增ConstructCircularBeanA類

ConstructCircularBeanA類的原始碼詳見:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.construct.bean.ConstructCircularBeanA。

@Component
public class ConstructCircularBeanA {
    
    private ConstructCircularBeanB constructCircularBeanB;

    private ConstructCircularBeanA(ConstructCircularBeanB constructCircularBeanB){
        this.constructCircularBeanB = constructCircularBeanB;
    }

    public ConstructCircularBeanB getConstructCircularBeanB() {
        return constructCircularBeanB;
    }
}

可以看到,在ConstructCircularBeanA類中透過構造方法依賴了ConstructCircularBeanB型別的Bean物件,並提供了getConstructCircularBeanB()方法來獲取ConstructCircularBeanB型別的Bean物件。

(2)新增ConstructCircularBeanB類

ConstructCircularBeanB類的原始碼詳見:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.construct.bean.ConstructCircularBeanB。

@Component
public class ConstructCircularBeanB {

    private ConstructCircularBeanA constructCircularBeanA;

    public ConstructCircularBeanB(ConstructCircularBeanA constructCircularBeanA) {
        this.constructCircularBeanA = constructCircularBeanA;
    }

    public ConstructCircularBeanA getConstructCircularBeanA() {
        return constructCircularBeanA;
    }
}

可以看到,在ConstructCircularBeanB類中透過構造方法依賴了ConstructCircularBeanA型別的Bean物件,並提供了getConstructCircularBeanA()方法來獲取ConstructCircularBeanA型別的Bean物件。

(3)新增ConstructCircularConfig類

ConstructCircularConfig類的原始碼詳見:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.construct.config.ConstructCircularConfig。

@Configuration
@ComponentScan(value = {"io.binghe.spring.annotation.chapter20.construct"})
public class ConstructCircularConfig {
}

可以看到,在ConstructCircularConfig類上標註了@Configuration註解,說明ConstructCircularConfig類是案例程式的配置類,並且使用@ComponentScan註解指定了要掃描的包名。

(4)新增ConstructCircularTest類

ConstructCircularTest類的原始碼詳見:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.construct.ConstructCircularTest。

public class ConstructCircularTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConstructCircularConfig.class);
        ConstructCircularBeanA constructCircularBeanA = context.getBean(ConstructCircularBeanA.class);
        System.out.println("constructCircularBeanA===>>>" + constructCircularBeanA);
        System.out.println("constructCircularBeanB===>>>" + constructCircularBeanA.getConstructCircularBeanB());
        context.close();
    }
}

可以看到,在ConstructCircularTest類的main()方法中,建立完IOC容器後,會從IOC容器中獲取ConstructCircularBeanA型別的Bean物件,並列印ConstructCircularBeanA型別的Bean物件和依賴的ConstructCircularBeanB型別的Bean物件。

(5)執行ConstructCircularTest類

執行ConstructCircularTest類的main()方法,輸出的結果資訊如下所示。

Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'constructCircularBeanA' defined in file [D:\Workspaces\2022\spring\spring-annotation-book\spring-annotation\spring-annotation-chapter-20\target\classes\io\binghe\spring\annotation\chapter20\construct\bean\ConstructCircularBeanA.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'constructCircularBeanBdefined in file [D:\Workspaces\2022\spring\spring-annotation-book\spring-annotation\spring-annotation-chapter-20\target\classes\io\binghe\spring\annotation\chapter20\construct\bean\ConstructCircularBeanB.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'constructCircularBeanA': Requested bean is currently in creationIs there an unresolvable circular reference?
/*************省略其他資訊**************/
Caused byorg.springframework.beans.factory.UnsatisfiedDependencyExceptionError creating bean with name 'constructCircularBeanBdefined in file [D:\Workspaces\2022\spring\spring-annotation-book\spring-annotation\spring-annotation-chapter-20\target\classes\io\binghe\spring\annotation\chapter20\construct\bean\ConstructCircularBeanB.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'constructCircularBeanA': Requested bean is currently in creationIs there an unresolvable circular reference?
/*************省略其他資訊**************/
Caused byorg.springframework.beans.factory.BeanCurrentlyInCreationExceptionError creating bean with name 'constructCircularBeanA': Requested bean is currently in creationIs there an unresolvable circular reference?

可以看到,Spring丟擲了迴圈依賴的異常,說明Spring不支援基於構造方法的迴圈依賴。

4.5 @DependsOn的迴圈依賴

@DependsOn註解主要用於指定Bean的例項化順序,Spring預設是不支援基於@DependsOn註解的迴圈依賴。本節,就實現基於@DependsOn註解的迴圈依賴。具體實現步驟如下所示。

(1)新增DependsOnCircularBeanA類

DependsOnCircularBeanA類的原始碼詳見:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.dependson.bean.DependsOnCircularBeanA。

@Component
@DependsOn("dependsOnCircularBeanB")
public class DependsOnCircularBeanA {
    @Autowired
    private DependsOnCircularBeanB dependsOnCircularBeanB;

    public DependsOnCircularBeanB getDependsOnCircularBeanB() {
        return dependsOnCircularBeanB;
    }
}

可以看到,在DependsOnCircularBeanA類上不僅標註了@Component註解,也標註了@DependsOn註解指定依賴dependsOnCircularBeanB,並且在DependsOnCircularBeanA類中使用@Autowired註解注入了DependsOnCircularBeanB型別的Bean物件。同時,在DependsOnCircularBeanA類中提供了getDependsOnCircularBeanB()方法獲取DependsOnCircularBeanB型別的Bean物件。

(2)新增DependsOnCircularBeanB類

DependsOnCircularBeanB類的原始碼詳見:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.dependson.bean.DependsOnCircularBeanB。

@Component
@DependsOn("dependsOnCircularBeanA")
public class DependsOnCircularBeanB {
    @Autowired
    private DependsOnCircularBeanA dependsOnCircularBeanA;

    public DependsOnCircularBeanA getDependsOnCircularBeanA() {
        return dependsOnCircularBeanA;
    }
}

可以看到,在DependsOnCircularBeanB類上不僅標註了@Component註解,也標註了@DependsOn註解指定依賴dependsOnCircularBeanA,並且在DependsOnCircularBeanB類中使用@Autowired註解注入了DependsOnCircularBeanA型別的Bean物件。同時,在DependsOnCircularBeanB類中提供了getDependsOnCircularBeanA()方法獲取DependsOnCircularBeanA型別的Bean物件。

(3)新增DependsOnCircularConfig類

DependsOnCircularConfig類的原始碼詳見:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.dependson.config.DependsOnCircularConfig。

@Configuration
@ComponentScan(value = {"io.binghe.spring.annotation.chapter20.dependson"})
public class DependsOnCircularConfig {
}

可以看到,在DependsOnCircularConfig類上標註了@Configuration註解,說明DependsOnCircularConfig類是案例程式的配置類。同時,使用@ComponentScan註解指定了要掃描的包名。

(4)新增DependsOnCircularTest類

DependsOnCircularTest類的原始碼詳見:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.dependson.DependsOnCircularTest。

public class DependsOnCircularTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DependsOnCircularConfig.class);
        DependsOnCircularBeanA dependsOnCircularBeanA = context.getBean(DependsOnCircularBeanA.class);
        System.out.println("dependsOnCircularBeanA===>>>" + dependsOnCircularBeanA);
        System.out.println("dependsOnCircularBeanB===>>>" + dependsOnCircularBeanA.getDependsOnCircularBeanB());
        context.close();
    }
}

可以看到,在DependsOnCircularTest類的main()方法中,建立完IOC容器後,會從IOC容器中獲取DependsOnCircularBeanA型別的Bean物件,並列印DependsOnCircularBeanA型別的Bean物件和依賴的DependsOnCircularBeanB型別的Bean物件。

(5)執行DependsOnCircularTest類

執行DependsOnCircularTest類的main()方法,輸出的結果資訊如下所示。

Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dependsOnCircularBeanB' defined in file [D:\Workspaces\2022\spring\spring-annotation-book\spring-annotation\spring-annotation-chapter-20\target\classes\io\binghe\spring\annotation\chapter20\dependson\bean\DependsOnCircularBeanB.class]: Circular depends-on relationship between 'dependsOnCircularBeanB' and 'dependsOnCircularBeanA'

從輸出的結果資訊可以看出,Spring丟擲了迴圈依賴的異常。說明Spring不支援@DependsOn註解的迴圈依賴。

4.6 單例Bean的setter迴圈依賴

Spring支援基於單例Bean的setter方法的迴圈依賴。本節,就實現基於單例Bean的setter方法的迴圈依賴案例。具體實現步驟如下所示。

(1)新增SingletonCircularBeanA類

SingletonCircularBeanA類的原始碼詳見:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.singleton.bean.SingletonCircularBeanA。

@Component
public class SingletonCircularBeanA {
    @Autowired
    private SingletonCircularBeanB singletonCircularBeanB;

    public SingletonCircularBeanB getSingletonCircularBeanB() {
        return singletonCircularBeanB;
    }
}

可以看到,在SingletonCircularBeanA類中依賴了SingletonCircularBeanB型別的Bean物件,並提供了getSingletonCircularBeanB()方法獲取SingletonCircularBeanB型別的Bean物件。

(2)新增SingletonCircularBeanB類

SingletonCircularBeanB類的原始碼詳見:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.singleton.bean.SingletonCircularBeanB。

@Component
public class SingletonCircularBeanB {
    @Autowired
    private SingletonCircularBeanA singletonCircularBeanA;

    public SingletonCircularBeanA getSingletonCircularBeanA() {
        return singletonCircularBeanA;
    }
}

可以看到,在SingletonCircularBeanB類中依賴了SingletonCircularBeanA型別的Bean物件,並提供了getSingletonCircularBeanA()方法獲取SingletonCircularBeanA型別的Bean物件。

(3)新增SingletonCircularConfig類

SingletonCircularConfig類的原始碼詳見:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.singleton.config.SingletonCircularConfig。

@Configuration
@ComponentScan(value = {"io.binghe.spring.annotation.chapter20.singleton"})
public class SingletonCircularConfig {
}

可以看到,在SingletonCircularConfig類上標註了@Configuration註解,說明SingletonCircularConfig類是案例程式的配置類,並使用@ComponentScan註解指定了要掃描的包名。

(4)新增SingletonCircularTest類

SingletonCircularTest類的原始碼詳見:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.singleton.SingletonCircularTest。

public class SingletonCircularTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SingletonCircularConfig.class);
        SingletonCircularBeanA singletonCircularBeanA = context.getBean(SingletonCircularBeanA.class);
        System.out.println("singletonCircularBeanA===>>>" + singletonCircularBeanA);
        System.out.println("singletonCircularBeanB===>>>" + singletonCircularBeanA.getSingletonCircularBeanB());
        context.close();
    }
}

可以看到,在SingletonCircularTest類的main()方法中,建立完IOC容器後,會從IOC容器中獲取SingletonCircularBeanA型別的Bean物件,並列印SingletonCircularBeanA型別的Bean物件和依賴的SingletonCircularBeanB型別的Bean物件。

(5)執行SingletonCircularTest類

執行SingletonCircularTest類的main()方法,輸出的結果資訊如下所示。

singletonCircularBeanA===>>>io.binghe.spring.annotation.chapter20.singleton.bean.SingletonCircularBeanA@41e1e210
singletonCircularBeanB===>>>io.binghe.spring.annotation.chapter20.singleton.bean.SingletonCircularBeanB@be35cd9

可以看到,正確輸出了SingletonCircularBeanA型別的Bean物件和SingletonCircularBeanB型別的Bean物件。**說明Spring支援基於單例Bean的setter方法的迴圈依賴。

五、Spring迴圈依賴底層解決方案分析

Spring底層是如何解決迴圈依賴問題的?

由迴圈依賴場景的分析得知:Spring除了預設支援基於單例Bean的setter方法的迴圈依賴外,預設均不支援其他情況下的迴圈依賴。但是,如果是透過標註@Async註解生成的代理物件,則可以透過將標註了@Async註解的類排到後面載入的IOC容器中即可解決迴圈依賴的問題。

5.1 不支援單例Bean的setter迴圈依賴的特殊情況

執行4.1節中SpecialCircularTest類的main()方法,輸出的結果資訊如下所示。

Exception in thread "main" java.lang.StackOverflowError

可以看到,實際丟擲的是StackOverflowError錯誤。這個錯誤資訊本質上不是Spring丟擲的,而是JVM丟擲的。根本原因其實是出在SpecialCircularBeanA和SpecialCircularBeanB兩個類的toString()方法上。

當在SpecialCircularTest類的main()方法中列印specialCircularBeanA時,預設會呼叫SpecialCircularBeanA類的toString()方法,在SpecialCircularBeanA類的toString()方法中,會拼接specialCircularBeanB物件,此時又會呼叫SpecialCircularBeanB類的toString()方法。而在SpecialCircularBeanB類的toString()方法中,又會拼接specialCircularBeanA物件,此時又會呼叫SpecialCircularBeanA類的toString()方法。在SpecialCircularBeanA類的toString()方法中,又會拼接specialCircularBeanB物件,繼而呼叫SpecialCircularBeanB類的toString()方法....如此反覆,造成了死迴圈。

簡單點說,就是在SpecialCircularBeanA類的toString()方法中呼叫了SpecialCircularBeanB類的toString()方法,在SpecialCircularBeanB類的toString()方法中呼叫了SpecialCircularBeanA類的toString()方法,造成了死迴圈,最終丟擲StackOverflowError錯誤。

5.2 不支援多例Bean的setter迴圈依賴

執行4.2節中PrototypeCircularTest類的main()方法,輸出的結果資訊如下所示。

Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'prototypeCircularBeanA': Unsatisfied dependency expressed through field 'prototypeCircularBeanB': Error creating bean with name 'prototypeCircularBeanB': Unsatisfied dependency expressed through field 'prototypeCircularBeanA': Error creating bean with name 'prototypeCircularBeanA': Requested bean is currently in creation: Is there an unresolvable circular reference?
/***************省略其他輸出資訊**************/
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'prototypeCircularBeanB': Unsatisfied dependency expressed through field 'prototypeCircularBeanA': Error creating bean with name 'prototypeCircularBeanA': Requested bean is currently in creation: Is there an unresolvable circular reference?
/***************省略其他輸出資訊**************/
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'prototypeCircularBeanA': Requested bean is currently in creation: Is there an unresolvable circular reference?
/***************省略其他輸出資訊**************/

Spring丟擲了迴圈依賴的異常,說明Spring不支援多例Bean的setter迴圈依賴。接下來就分析下Spring中為啥不支援多例Bean的setter迴圈依賴。具體分析步驟如下所示。

(1)解析AbstractBeanFactory類的doGetBean(String name, @Nullable Class

原始碼詳見:org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean(String name, @Nullable Class

protected <T> doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException {
    Object sharedInstance = getSingleton(beanName);
    if (sharedInstance != null && args == null) {
        /**********省略其他程式碼*************/
    }
    else {
        /**********省略其他程式碼*************/
        try {
            /**********省略其他程式碼*************/
            else if (mbd.isPrototype()) {
                Object prototypeInstance = null;
                try {
                    beforePrototypeCreation(beanName);
                    prototypeInstance = createBean(beanName, mbd, args);
                }
                finally {
                    afterPrototypeCreation(beanName);
                }
                beanInstance = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
            }
  /**********省略其他程式碼*************/
        }
        catch (BeansException ex) {
            /**********省略其他程式碼*************/
        }
        finally {
           /**********省略其他程式碼*************/
        }
    }
    return adaptBeanInstance(name, beanInstance, requiredType);
}

可以看到,在多例Bean模式下,建立Bean物件之前會呼叫beforePrototypeCreation()方法,在建立Bean物件之後會呼叫afterPrototypeCreation()方法。

(2)解析AbstractBeanFactory類的beforePrototypeCreation(String beanName)方法

原始碼詳見:org.springframework.beans.factory.support.AbstractBeanFactory#beforePrototypeCreation(String beanName)。

protected void beforePrototypeCreation(String beanName) {
    Object curVal = this.prototypesCurrentlyInCreation.get();
    if (curVal == null) {
        this.prototypesCurrentlyInCreation.set(beanName);
    }
    else if (curVal instanceof String strValue) {
        Set<String> beanNameSet = new HashSet<>(2);
        beanNameSet.add(strValue);
        beanNameSet.add(beanName);
        this.prototypesCurrentlyInCreation.set(beanNameSet);
    }
    else {
        Set<String> beanNameSet = (Set<String>) curVal;
        beanNameSet.add(beanName);
    }
}

可以看到,Spring在建立多例Bean時,會在beforePrototypeCreation()方法中,使用prototypesCurrentlyInCreation記錄正在建立中的Bean,那prototypesCurrentlyInCreation又是個什麼鬼呢?

prototypesCurrentlyInCreation的原始碼詳見:org.springframework.beans.factory.support.AbstractBeanFactory#prototypesCurrentlyInCreation,如下所示。

private final ThreadLocal<Object> prototypesCurrentlyInCreation = new NamedThreadLocal<>("Prototype beans currently in creation");

也就是說,Spring在建立多例Bean時,會使用一個ThreadLocal型別的變數prototypesCurrentlyInCreation來記錄當前執行緒正在建立中的Bean。並且根據beforePrototypeCreation()方法的原始碼又可以看出,在prototypesCurrentlyInCreation變數中使用一個Set集合來儲存正在建立中的Bean。由於Set集合不存在重複物件,所以這樣就能夠保證在一個執行緒中只能有一個相同的Bean正在被建立。

(3)解析AbstractBeanFactory類的afterPrototypeCreation(String beanName)方法

原始碼詳見:org.springframework.beans.factory.support.AbstractBeanFactory#afterPrototypeCreation(String beanName)。

protected void afterPrototypeCreation(String beanName) {
    Object curVal = this.prototypesCurrentlyInCreation.get();
    if (curVal instanceof String) {
        this.prototypesCurrentlyInCreation.remove();
    }
    else if (curVal instanceof Set<?> beanNameSet) {
        beanNameSet.remove(beanName);
        if (beanNameSet.isEmpty()) {
            this.prototypesCurrentlyInCreation.remove();
        }
    }
}

可以看到,afterPrototypeCreation()方法中主要是對prototypesCurrentlyInCreation中儲存的Bean進行移除操作。

綜合beforePrototypeCreation()方法和afterPrototypeCreation()方法可以看出,Spring在建立多例Bean之前,會將當前執行緒正在建立的Bean存入prototypesCurrentlyInCreation中,待Bean物件例項化完成後,就從prototypesCurrentlyInCreation中移除正在建立的Bean。

(4)返回AbstractBeanFactory類的doGetBean(String name, @Nullable Class

protected <T> doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)throws BeansException {
    String beanName = transformedBeanName(name);
    Object beanInstance;
    Object sharedInstance = getSingleton(beanName);
    if (sharedInstance != null && args == null) {
         /**********省略其他程式碼*************/
        beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null);
    }
    else {
        if (isPrototypeCurrentlyInCreation(beanName)) {
            throw new BeanCurrentlyInCreationException(beanName);
        }
    /**********省略其他程式碼*************/
    }
    return adaptBeanInstance(name, beanInstance, requiredType);
}

在AbstractBeanFactory類的doGetBean()方法開始的部分,會呼叫getSingleton()方法從快取中獲取單例Bean物件,首先會執行if條件進行判斷,如果獲取到的單例Bean物件為空,說明此時可能是第一次執行doGetBean()方法,也可能是建立的多例Bean。接下來會進入else分支邏輯,在else分支邏輯中,首先會判斷當前執行緒是否已經存在正在建立的Bean,如果存在,則直接丟擲BeanCurrentlyInCreationException異常。

(5)解析AbstractBeanFactory類的isPrototypeCurrentlyInCreation(String beanName)方法

原始碼詳見:org.springframework.beans.factory.support.AbstractBeanFactory#isPrototypeCurrentlyInCreation(String beanName)。

protected boolean isPrototypeCurrentlyInCreation(String beanName) {
    Object curVal = this.prototypesCurrentlyInCreation.get();
    return (curVal != null && (curVal.equals(beanName) || (curVal instanceof Set<?> set && set.contains(beanName))));
}

所以,在Spring建立多例Bean時,無法解決Bean的迴圈依賴。如果在建立多例Bean的過程中,發現存在迴圈依賴,則直接丟擲BeanCurrentlyInCreationException異常。

5.3 不支援代理物件的setter迴圈依賴

執行4.3節中ProxyCircularTest類的main()方法,輸出的結果資訊如下所示。

Exception in thread "main" org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'proxyCircularBeanA': Bean with name 'proxyCircularBeanA' has been injected into other beans [proxyCircularBeanB] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.

從輸出的結果資訊可以看出,Spring丟擲了迴圈依賴的異常。接下來,具體分析Spring為何不支援代理物件的setter迴圈依賴。

(1)解析AbstractAutowireCapableBeanFactory類的doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)方法。

透過前面章節對於建立Bean的流程分析可知,無論是建立單例Bean還是建立多例Bean,Spring都會執行到AbstractAutowireCapableBeanFactory類的doCreateBean()方法中。

原始碼詳見:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)。重點關注如下程式碼片段。

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {
 /********省略其他程式碼**********/ 
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                                      isSingletonCurrentlyInCreation(beanName));
    /********省略其他程式碼**********/
    if (earlySingletonExposure) {
        Object earlySingletonReference = getSingleton(beanName, false);
        if (earlySingletonReference != null) {
            if (exposedObject == bean) {
                exposedObject = earlySingletonReference;
            }
            else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
                String[] dependentBeans = getDependentBeans(beanName);
                Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
                for (String dependentBean : dependentBeans) {
                    if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
                        actualDependentBeans.add(dependentBean);
                    }
                }
                if (!actualDependentBeans.isEmpty()) {
                    throw new BeanCurrentlyInCreationException(beanName, "Bean with name '" + beanName + "' has been injected into other beans [" + StringUtils.collectionToCommaDelimitedString(actualDependentBeans) + reference, but has eventually been " + "wrapped. This means that said other beans do not use the final version of the " + "bean. This is often the result of over-eager type matching - consider using " +"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
                }
            }
        }
    }
 /********省略其他程式碼**********/
    return exposedObject;
}

可以看到,在AbstractAutowireCapableBeanFactory類的doCreateBean()方法中,會判斷從二級快取中獲取到的物件是否等於原始物件,程式碼片段如下所示。

Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
    if (exposedObject == bean) {
        exposedObject = earlySingletonReference;
    }

此時,如果是代理物件的話,二級快取中會存放由AOP生成出來的代理物件,與原始物件不相等。所以,會丟擲BeanCurrentlyInCreationException異常。

另外,仔細觀察AbstractAutowireCapableBeanFactory類的doCreateBean()方法時,會發現如果從二級快取中獲取到的earlySingletonReference物件為空,就會直接返回,不會丟擲BeanCurrentlyInCreationException異常。由於Spring預設會按照檔案全路徑遞迴搜尋,並且會按照路徑+檔名的方式進行排序,排序靠前的Bean先被載入。所以,將標註了@Async註解的類排在後面即可解決迴圈依賴的問題。

5.4 不支援構造方法的迴圈依賴

執行4.4節中ConstructCircularTest類的main()方法,輸出的結果資訊如下所示。

Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'constructCircularBeanA' defined in file [D:\Workspaces\2022\spring\spring-annotation-book\spring-annotation\spring-annotation-chapter-20\target\classes\io\binghe\spring\annotation\chapter20\construct\bean\ConstructCircularBeanA.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'constructCircularBeanBdefined in file [D:\Workspaces\2022\spring\spring-annotation-book\spring-annotation\spring-annotation-chapter-20\target\classes\io\binghe\spring\annotation\chapter20\construct\bean\ConstructCircularBeanB.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'constructCircularBeanA': Requested bean is currently in creationIs there an unresolvable circular reference?
/*************省略其他資訊**************/
Caused byorg.springframework.beans.factory.UnsatisfiedDependencyExceptionError creating bean with name 'constructCircularBeanBdefined in file [D:\Workspaces\2022\spring\spring-annotation-book\spring-annotation\spring-annotation-chapter-20\target\classes\io\binghe\spring\annotation\chapter20\construct\bean\ConstructCircularBeanB.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'constructCircularBeanA': Requested bean is currently in creationIs there an unresolvable circular reference?
/*************省略其他資訊**************/
Caused byorg.springframework.beans.factory.BeanCurrentlyInCreationExceptionError creating bean with name 'constructCircularBeanA': Requested bean is currently in creationIs there an unresolvable circular reference?

可以看到,Spring丟擲了迴圈依賴的異常,說明Spring不支援基於構造方法的迴圈依賴。接下來,具體分析下Spring不支援基於構造方法的迴圈依賴的原因。具體分析步驟如下所示。

(1)解析AbstractBeanFactory類的doGetBean(String name, @Nullable Class

原始碼詳見:org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean(String name, @Nullable Class

此時重點關注如下程式碼片段。

protected <T> doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException {
    String beanName = transformedBeanName(name);
    Object beanInstance;
    Object sharedInstance = getSingleton(beanName);
    if (sharedInstance != null && args == null) {
       /********省略其他程式碼**********/
    } else {
       /********省略其他程式碼**********/
            if (mbd.isSingleton()) {
                sharedInstance = getSingleton(beanName, () -> {
                    try {
                        return createBean(beanName, mbd, args);
                    }
                    catch (BeansException ex) {
                        /********省略其他程式碼**********/
                    }
                });
                beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
            }
   /********省略其他程式碼**********/
        }
        catch (BeansException ex) {
           /********省略其他程式碼**********/
        }
        finally {
            /********省略其他程式碼**********/
        }
    }
    return adaptBeanInstance(name, beanInstance, requiredType);
}

可以看到,在AbstractBeanFactory類的doGetBean()方法中,會呼叫getSingleton()方法。

(2)解析DefaultSingletonBeanRegistry類的getSingleton(String beanName, ObjectFactory<?> singletonFactory)方法

原始碼詳見:org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(String beanName, ObjectFactory<?> singletonFactory)。

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(beanName, "Bean name must not be null");
    synchronized (this.singletonObjects) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null) {
            if (this.singletonsCurrentlyInDestruction) {
                throw new BeanCreationNotAllowedException(beanName, "Singleton bean creation not allowed while singletons of this factory are in destruction " + "(Do not request a bean from a BeanFactory in a destroy method implementation!)");
            }
            if (logger.isDebugEnabled()) {
                logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
            }
            beforeSingletonCreation(beanName);
            boolean newSingleton = false;
            boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
            if (recordSuppressedExceptions) {
                this.suppressedExceptions = new LinkedHashSet<>();
            }
            try {
                singletonObject = singletonFactory.getObject();
                newSingleton = true;
            }
            catch (IllegalStateException ex) {
                singletonObject = this.singletonObjects.get(beanName);
                if (singletonObject == null) {
                    throw ex;
                }
            }
            catch (BeanCreationException ex) {
                if (recordSuppressedExceptions) {
                    for (Exception suppressedException : this.suppressedExceptions) {
                        ex.addRelatedCause(suppressedException);
                    }
                }
                throw ex;
            }
            finally {
                if (recordSuppressedExceptions) {
                    this.suppressedExceptions = null;
                }
                afterSingletonCreation(beanName);
            }
            if (newSingleton) {
                addSingleton(beanName, singletonObject);
            }
        }
        return singletonObject;
    }
}

可以看到,在getSingleton()方法建立Bean之前會呼叫beforeSingletonCreation()方法,在建立Bean之後會呼叫afterSingletonCreation()方法。

(3)解析DefaultSingletonBeanRegistry類的beforeSingletonCreation(String beanName)方法

原始碼詳見:org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#beforeSingletonCreation(String beanName)。

protected void beforeSingletonCreation(String beanName) {
    if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
        throw new BeanCurrentlyInCreationException(beanName);
    }
}

可以看到,在建立單例Bean之前,會將要建立的Bean的名稱新增到singletonsCurrentlyInCreation中,新增失敗,說明singletonsCurrentlyInCreation集合中已經存在當前Bean的名稱,發生了迴圈依賴,就會丟擲BeanCurrentlyInCreationException異常。那麼singletonsCurrentlyInCreation又是個什麼鬼呢?

singletonsCurrentlyInCreation的原始碼詳見:org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#singletonsCurrentlyInCreation。

private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16));

可以看到,singletonsCurrentlyInCreation其實就是一個Set集合。也就是說,Spring會在建立單例Bean之前,將正在建立的Bean的名稱新增到singletonsCurrentlyInCreation集合中。

(4)解析DefaultSingletonBeanRegistry類的afterSingletonCreation(String beanName)方法

原始碼詳見:org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#afterSingletonCreation(String beanName)。

protected void afterSingletonCreation(String beanName) {
    if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) {
        throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");
    }
}

可以看到,在afterSingletonCreation()方法中,主要是移除singletonsCurrentlyInCreation中對應的Bean的名稱。

綜合beforeSingletonCreation()和afterSingletonCreation()兩個方法可以看出,Spring在建立單例Bean之前,會將Bean的名稱存入singletonsCurrentlyInCreation集合中,例項化Bean之後,會將Bean的名稱從singletonsCurrentlyInCreation集合中移除。並且,Spring在建立單例Bean之前,會呼叫beforeSingletonCreation()方法將要建立的Bean的名稱新增到singletonsCurrentlyInCreation中,新增失敗,說明singletonsCurrentlyInCreation集合中已經存在當前Bean的名稱,發生了迴圈依賴,就會丟擲BeanCurrentlyInCreationException異常。

5.5 不支援@DependsOn的迴圈依賴

執行4.5節中DependsOnCircularTest類的main()方法,輸出的結果資訊如下所示。

Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dependsOnCircularBeanB' defined in file [D:\Workspaces\2022\spring\spring-annotation-book\spring-annotation\spring-annotation-chapter-20\target\classes\io\binghe\spring\annotation\chapter20\dependson\bean\DependsOnCircularBeanB.class]: Circular depends-on relationship between 'dependsOnCircularBeanB' and 'dependsOnCircularBeanA'

從輸出的結果資訊可以看出,Spring丟擲了迴圈依賴的異常。說明Spring不支援@DependsOn註解的迴圈依賴。接下來,就分析下Spring不支援@DependsOn註解的迴圈依賴的原因。具體分析步驟如下所示。

解析AbstractBeanFactory類的doGetBean(String name, @Nullable Class

原始碼詳見:org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean(String name, @Nullable Class

protected <T> doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException {
    String beanName = transformedBeanName(name);
    Object beanInstance;
    Object sharedInstance = getSingleton(beanName);
    if (sharedInstance != null && args == null) {
         /*********省略其他程式碼*********/
    }
    else {
        /*********省略其他程式碼*********/
        try {
            /*********省略其他程式碼*********/
            String[] dependsOn = mbd.getDependsOn();
            if (dependsOn != null) {
                for (String dep : dependsOn) {
                    if (isDependent(beanName, dep)) {
                        throw new BeanCreationException(mbd.getResourceDescription(), beanName,  "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
                    }
                    registerDependentBean(dep, beanName);
                    try {
                        getBean(dep);
                    }
                    catch (NoSuchBeanDefinitionException ex) {
                        throw new BeanCreationException(mbd.getResourceDescription(), beanName,  "'" + beanName + "' depends on missing bean '" + dep + "'", ex);
                    }
                }
            }
             /*********省略其他程式碼*********/
        }
        catch (BeansException ex) {
            /*********省略其他程式碼*********/
        }
        finally {
           /*********省略其他程式碼*********/

        }
    }
    return adaptBeanInstance(name, beanInstance, requiredType);
}

可以看到,在AbstractBeanFactory類的doGetBean(String name, @Nullable Class

5.6 支援單例Bean的setter迴圈依賴

執行4.6節中SingletonCircularTest類的main()方法,輸出的結果資訊如下所示。

singletonCircularBeanA===>>>io.binghe.spring.annotation.chapter20.singleton.bean.SingletonCircularBeanA@41e1e210
singletonCircularBeanB===>>>io.binghe.spring.annotation.chapter20.singleton.bean.SingletonCircularBeanB@be35cd9

可以看到,正確輸出了SingletonCircularBeanA型別的Bean物件和SingletonCircularBeanB型別的Bean物件。說明Spring支援基於單例Bean的setter方法的迴圈依賴。接下來,就分析下Spring為何支援單例Bean的setter迴圈依賴。

(1)三級快取

Spring使用了三級快取來解決迴圈依賴的問題,三級快取的原始碼詳見:org.springframework.beans.factory.support.DefaultSingletonBeanRegistry。

private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

其中,每個Map的含義如下所示。

  • singletonObjects:一級快取,儲存所有例項化,並且為屬性賦值的單例項Bean。
  • earlySingletonObjects:二級快取,儲存例項化後還沒來得及為屬性賦值的單例項Bean。
  • singletonFactories:三級快取,儲存生產單例項Bean的工廠。

關於三級快取,面試中經常會問如下兩個問題,這裡也給大家分享下。

  • Spring解決迴圈依賴為什麼需要二級快取?

二級快取主要是為了分離建立出的完整的Bean和未對屬性賦值的Bean,二級快取中實際主要儲存的是未對屬性賦值的Bean,這樣做的目的就是為了防止在多執行緒併發的場景中,讀取到還未建立完成的Bean。所以,為了保證在多執行緒併發的環境中,讀取到的Bean是完整的(已經為屬性賦值),不會讀取到未對屬性賦值的Bean,需要使用二級快取解決迴圈依賴。

另外,就一、二、三級快取而言,二級快取主要儲存的是三級快取建立出來的,並且未對屬性賦值的Bean,這樣做的目的也是為了防止三級快取中的工廠類重複執行建立物件的邏輯。

  • Spring只用二級快取能否解決迴圈依賴?為什麼一定要用三級快取來解決迴圈依賴呢?

其實,Spring使用二級快取就完全能夠解決迴圈依賴的問題,也可以支援Spring基於BeanPostProcessor的擴充套件能力。但是,由於Spring中的方法在設計上遵循了單一職責的原則,一個方法通常只做一件事情,getBean()方法就是獲取Bean物件。但是,呼叫BeanPostProcessor建立動態代理是處於建立Bean的過程,如果在getBean()中實現這個邏輯,顯然程式碼邏輯比較耦合。為了解決程式碼耦合的問題,保持方法的職責單一,方面後期維護。需要將建立動態代理的BeanPostProcessor放在建立Bean的方法中。並且將判斷是否存在迴圈依賴的邏輯放在getSingleton()方法中。此時就需要三級快取,在三級快取中存放一個工廠介面,在介面的實現類中呼叫BeanPostProcessor建立動態代理物件。為了防止重複建立代理物件,將三級快取中建立的代理物件存入二級快取。在Spring中使用三級快取完美解決了解耦、效能、擴充套件的問題。

(2)建立單例工廠

Spring在建立Bean物件時,會先建立一個和Bean的名稱相同的單例工廠,並將Bean先放入單例工廠中。

原始碼詳見:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)。

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {
 /**********省略其他程式碼***********/
    if (earlySingletonExposure) {
       /**********省略其他程式碼***********/
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }
 /**********省略其他程式碼***********/
    return exposedObject;
}

在AbstractAutowireCapableBeanFactory類的doCreateBean()方法中呼叫了addSingletonFactory()方法。

addSingletonFactory()方法的原始碼詳見:org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory)。

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            this.singletonFactories.put(beanName, singletonFactory);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}

可以看到,addSingletonFactory()方法的作用是將正在建立中的Bean的單例工廠,存放在三級快取裡,這樣就保證了在迴圈依賴查詢的時候是可以找到Bean的引用的。

(3)讀取快取資料

具體讀取快取獲取Bean的過程在類DefaultSingletonBeanRegistry的getSingleton()方法中,原始碼詳見:org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(String beanName, boolean allowEarlyReference)。

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // Quick check for existing instance without full singleton lock
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        singletonObject = this.earlySingletonObjects.get(beanName);
        if (singletonObject == null && allowEarlyReference) {
            synchronized (this.singletonObjects) {
                // Consistent creation of early reference within full singleton lock
                singletonObject = this.singletonObjects.get(beanName);
                if (singletonObject == null) {
                    singletonObject = this.earlySingletonObjects.get(beanName);
                    if (singletonObject == null) {
                        ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                        if (singletonFactory != null) {
                            singletonObject = singletonFactory.getObject();
                            this.earlySingletonObjects.put(beanName, singletonObject);
                            this.singletonFactories.remove(beanName);
                        }
                    }
                }
            }
        }
    }
    return singletonObject;
}

透過上面的原始碼我們可以看到,在獲取單例Bean的時候,會先從一級快取singletonObjects裡獲取,如果沒有獲取到(說明不存在或沒有例項化完成),會去第二級快取earlySingletonObjects中去找,如果還是沒有找到的話,就會三級快取中獲取單例工廠singletonFactory,透過從singletonFactory中獲取正在建立中的引用,將singletonFactory儲存在earlySingletonObjects 二級快取中,這樣就將建立中的單例引用從三級快取中升級到了二級快取中,二級快取earlySingletonObjects,是會提前暴露已完成構造,還未執行屬性注入的單例bean的。這個時候如何還有其他的bean也是需要屬性注入,那麼就可以直接從earlySingletonObjects中獲取了。

注意:為了防止多執行緒併發環境下重複執行三級快取中建立Bean的過程,在對singletonObjects加鎖後,還會先從一級快取singletonObjects中獲取資料,如果資料不存在則從二級快取earlySingletonObjects中獲取資料,如果資料仍然不存在,才會從三級快取singletonFactories中獲取singletonFactory,呼叫singletonFactory的getObject()方法獲取例項化但未對屬性賦值的Bean物件,將其存入二級快取,並且從三級快取中移除對應的singletonFactory。

(4)解決迴圈依賴的完整流程圖

最後給出Spring支援單例Bean的setter迴圈依賴的完整流程圖,如圖20-6所示。

萬字長文帶你徹底吃透Spring迴圈依賴,堪稱全網最全(文末福利)

大家可以按照20-6的邏輯分析Spring解決迴圈依賴的程式碼,就相對比較清晰了。這裡,就不再分析具體原始碼了。

六、總結

Spring的迴圈依賴問題介紹完了,我們一起總結下吧!

本章,主要詳細分析了Spring的迴圈依賴問題,首先介紹了快取依賴的基本概念和迴圈依賴的型別。隨後以案例的形式詳細介紹了迴圈依賴的場景,並詳細分析了Spring迴圈依賴的底層解決方案。透過分析得知:Spring預設會支援單例Bean的setter迴圈依賴,對於其他情況下的迴圈依賴,Spring預設是不支援的。並且,最後給出了Spring解決迴圈依賴的流程圖。

七、思考

既然學完了,就開始思考幾個問題吧?

關於Spring的迴圈依賴,通常會有如下幾個經典面試題:

  • 什麼是迴圈依賴問題?
  • 迴圈依賴有哪些型別?
  • 在Spring中支援哪種迴圈依賴?
  • 列舉幾種Spring不支援的迴圈依賴的場景,為什麼不支援?
  • Spring解決迴圈依賴的流程是什麼?
  • Spring解決快取依賴時,二級快取的作用是什麼?
  • Spring只用二級快取能否解決迴圈依賴?為什麼一定要用三級快取來解決迴圈依賴呢?
  • 你從Spring解決迴圈依賴的設計中得到了哪些啟發?




來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024924/viewspace-2998731/,如需轉載,請註明出處,否則將追究法律責任。

相關文章