自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

萬(wàn)字長(zhǎng)文帶你徹底吃透Spring循環(huán)依賴,堪稱全網(wǎng)最全

開(kāi)發(fā) 前端
本章,主要詳細(xì)分析了Spring的循環(huán)依賴問(wèn)題,首先介紹了緩存依賴的基本概念和循環(huán)依賴的類型。隨后以案例的形式詳細(xì)介紹了循環(huán)依賴的場(chǎng)景,并詳細(xì)分析了Spring循環(huán)依賴的底層解決方案。

一、學(xué)習(xí)指引

Spring中的循環(huán)依賴問(wèn)題,你真的徹底了解過(guò)嗎?

Spring的循環(huán)依賴問(wèn)題可以說(shuō)是面試過(guò)程中出現(xiàn)的非常頻繁的問(wèn)題。比如,在面試過(guò)程中面試官可能會(huì)問(wèn):有了解過(guò)Spring中的循環(huán)依賴問(wèn)題嗎?或者會(huì)問(wèn):什么是循環(huán)依賴問(wèn)題?或者會(huì)問(wèn):Spring中在哪些場(chǎng)景下會(huì)產(chǎn)生循環(huán)依賴問(wèn)題?或者會(huì)問(wèn):Spring是如何解決循環(huán)依賴問(wèn)題的?看似輕描淡寫(xiě)的一句話,實(shí)際要考察的內(nèi)容也是比較多的。面試官可以通過(guò)這個(gè)問(wèn)題考察面試者是否研究過(guò)Spring的源碼,有沒(méi)有了解過(guò)Spring中的循環(huán)依賴問(wèn)題。

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

本章,我們就一起來(lái)聊聊Spring中的循環(huán)依賴問(wèn)題。

二、循環(huán)依賴概述

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

循環(huán)依賴其實(shí)也很好理解,可以將這個(gè)詞拆分成兩部分,一個(gè)是循環(huán),一個(gè)是依賴。循環(huán),顧名思義就是指形成了一個(gè)閉合環(huán)路,也就是閉環(huán)。依賴就是指某個(gè)事件的發(fā)生要依賴另一個(gè)事件。

在Spring中的循環(huán)依賴就是指一個(gè)或者多個(gè)Bean之間存在著互相依賴的關(guān)系,并且形成了循環(huán)調(diào)用。例如,在Spring中,A依賴B,B又依賴A,A和B之間就形成了相互依賴的關(guān)系。創(chuàng)建A對(duì)象時(shí),發(fā)現(xiàn)A對(duì)象依賴了B對(duì)象,此時(shí)先去創(chuàng)建B對(duì)象。創(chuàng)建B對(duì)象時(shí),發(fā)現(xiàn)B對(duì)象又依賴了A對(duì)象,此時(shí)又去創(chuàng)建A對(duì)象。創(chuàng)建A對(duì)象時(shí),發(fā)現(xiàn)A對(duì)象依賴了B對(duì)象....如果Spring不去處理這種情況,就會(huì)發(fā)生死循環(huán),一直會(huì)創(chuàng)建A對(duì)象和B對(duì)象,直到拋出異常為止。

同理,在Spring中多個(gè)對(duì)象之間也有可能存在循環(huán)依賴,例如,A依賴B,B依賴C,C又依賴A,A、B、C之間形成了互相依賴的關(guān)系,這也是一種循環(huán)依賴。

三、循環(huán)依賴類型

循環(huán)依賴有這些類型呢?

循環(huán)依賴總體上可以分成:自我依賴、直接依賴和間接依賴三種類型,如圖20-1所示。

圖片圖片

3.1 自我依賴

自我依賴就是自己依賴自己,從而形成的循環(huán)依賴,一般情況下不會(huì)發(fā)生這種循環(huán)依賴,如圖20-2所示。

圖片圖片

3.2 直接依賴

直接依賴一般是發(fā)生在兩個(gè)對(duì)象之間,例如對(duì)象A依賴對(duì)象B,對(duì)象B又依賴對(duì)象A,對(duì)象A和對(duì)象B之間形成了依賴關(guān)系,如圖20-3所示。

圖片圖片

3.3 間接依賴

間接依賴一般是發(fā)生在三個(gè)或三個(gè)以上對(duì)象之間互相依賴的場(chǎng)景,例如對(duì)象A依賴對(duì)象B,對(duì)象B依賴對(duì)象C,對(duì)象C又依賴對(duì)象A,對(duì)象A、對(duì)象B和對(duì)象C之間就形成了循環(huán)依賴,如圖20-4所示。

圖片圖片

四、循環(huán)依賴場(chǎng)景

Spring中有哪些循環(huán)依賴的場(chǎng)景?

Spring中的循環(huán)依賴場(chǎng)景總體上可以分成單例Bean的setter循環(huán)依賴、多例Bean的setter循環(huán)依賴、代理對(duì)象的setter循環(huán)依賴、構(gòu)造方法的循環(huán)依賴和DependsOn的循環(huán)依賴。如圖20-5所示。

圖片圖片

4.1 單例Bean的setter循環(huán)依賴的特殊情況

Spring是支持基于單例Bean的setter方法的循環(huán)依賴的,不過(guò)有一種特殊情況需要注意。本節(jié),我們就一起實(shí)現(xiàn)基于單例Bean的setter方法的循環(huán)依賴的特殊情況,具體實(shí)現(xiàn)步驟如下所示。

(1)新增SpecialCircularBeanA類

SpecialCircularBeanA類的源碼詳見(jiàn):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類上只標(biāo)注了@Component注解,所以在IOC容器啟動(dòng)時(shí),會(huì)在IOC容器中創(chuàng)建SpecialCircularBeanA類型的單例Bean,并且在SpecialCircularBeanA類型的Bean對(duì)象中,會(huì)依賴SpecialCircularBeanB類型的Bean對(duì)象。同時(shí),在SpecialCircularBeanA類中重寫(xiě)了toString()方法,打印了依賴的SpecialCircularBeanB類型的對(duì)象。

(2)新增SpecialCircularBeanB類

SpecialCircularBeanB類的源碼詳見(jiàn):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類上只標(biāo)注了@Component注解,所以在IOC容器啟動(dòng)時(shí),會(huì)在IOC容器中創(chuàng)建SpecialCircularBeanB類型的單例Bean,并且在SpecialCircularBeanB類型的Bean對(duì)象中,會(huì)依賴SpecialCircularBeanA類型的Bean對(duì)象。同時(shí),在SpecialCircularBeanB類中重寫(xiě)了toString()方法,打印了依賴的SpecialCircularBeanA類型的對(duì)象。

(3)新增SpecialCircularConfig類

SpecialCircularConfig類的源碼詳見(jiàn):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類上標(biāo)注了@Configuration注解,說(shuō)明SpecialCircularConfig類是案例程序的配置類,并且使用@ComponentScan注解指定了掃描的包。

(4)新增SpecialCircularTest類

SpecialCircularTest類的源碼詳見(jiàn):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對(duì)象后,創(chuàng)建IOC容器,隨后從IOC容器中獲取SpecialCircularBeanA類型的Bean對(duì)象并進(jìn)行打印,最后關(guān)閉IOC容器。

(5)運(yùn)行SpecialCircularTest類

運(yùn)行SpecialCircularTest類的main()方法,輸出的結(jié)果信息如下所示。

Exception in thread "main" java.lang.StackOverflowError

可以看到,程序拋出了StackOverflowError異常。

其實(shí),從本質(zhì)上講,這個(gè)異常不是Spring拋出的,而是JVM拋出的棧溢出錯(cuò)誤。

4.2 多例Bean的setter循環(huán)依賴

Spring是不支持基于多例Bean,也就是原型模式下的Bean的setter方法的循環(huán)依賴。本節(jié),我們一起實(shí)現(xiàn)一個(gè)基于多例Bean的set方法的循環(huán)依賴案例,具體實(shí)現(xiàn)步驟如下所示。

(1)新增PrototypeCircularBeanA類

PrototypeCircularBeanA類的源碼詳見(jiàn):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類的對(duì)象在Spring中是多例Bean,并且依賴了PrototypeCircularBeanB類型的Bean對(duì)象,并提供了getPrototypeCircularBeanB()方法返回PrototypeCircularBeanB類型的Bean對(duì)象。

(2)新增PrototypeCircularBeanB類

PrototypeCircularBeanB類的源碼詳見(jiàn):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類的對(duì)象在Spring中是多例Bean,并且依賴了PrototypeCircularBeanA類型的Bean對(duì)象,并提供了getPrototypeCircularBeanA()方法返回PrototypeCircularBeanA類型的Bean對(duì)象。

(3)新增PrototypeCircularConfig類

PrototypeCircularConfig類的源碼詳見(jiàn):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類上標(biāo)注了@Configuration注解,說(shuō)明PrototypeCircularConfig類是案例程序的配置類,并且使用@ComponentScan注解指定了要掃描的包名。

(4)新增PrototypeCircularTest類

PrototypeCircularTest類的源碼詳見(jiàn):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()方法中,創(chuàng)建完IOC容器后,會(huì)從IOC容器中獲取PrototypeCircularBeanA類型的Bean對(duì)象,并打印PrototypeCircularBeanA類型的Bean對(duì)象和依賴的PrototypeCircularBeanB類型的Bean對(duì)象。

(5)運(yùn)行PrototypeCircularTest類

運(yùn)行PrototypeCircularTest類的main()方法,輸出的結(jié)果信息如下所示。

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?
/***************省略其他輸出信息**************/

可以看到,輸出結(jié)果中打印出了循環(huán)依賴的異常信息,說(shuō)明Spring不支持多例Bean的setter方法循環(huán)依賴。

4.3 代理對(duì)象的setter循環(huán)依賴

Spring默認(rèn)是不支持基于代理對(duì)象的setter方法的循環(huán)依賴,本節(jié),就簡(jiǎn)單實(shí)現(xiàn)一個(gè)基于代理對(duì)象的setter方法的循環(huán)依賴案例。具體實(shí)現(xiàn)步驟如下所示。

(1)新增ProxyCircularBeanA類

ProxyCircularBeanA類的源碼詳見(jiàn):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對(duì)象會(huì)依賴ProxyCircularBeanB類型的Bean對(duì)象,并且在getProxyCircularBeanB()方法上標(biāo)注了@Async注解,當(dāng)調(diào)用getProxyCircularBeanB()方法時(shí),會(huì)通過(guò)AOP自動(dòng)生成代理對(duì)象。

(2)新增ProxyCircularBeanB類

ProxyCircularBeanB類的源碼詳見(jiàn):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對(duì)象會(huì)依賴ProxyCircularBeanA類型的Bean對(duì)象,并且在getProxyCircularBeanA()方法上標(biāo)注了@Async注解,當(dāng)調(diào)用getProxyCircularBeanA()方法時(shí),會(huì)通過(guò)AOP自動(dòng)生成代理對(duì)象。

(3)新增ProxyCircularConfig類

ProxyCircularConfig類的源碼詳見(jiàn):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類上標(biāo)注了@Configuration注解,說(shuō)明ProxyCircularConfig類是案例程序的配置類。并且在ProxyCircularConfig類上使用@ComponentScan注解指定了要掃描的包名。同時(shí),使用@EnableAsync注解開(kāi)啟了異步調(diào)用。

(4)新增ProxyCircularTest類

ProxyCircularTest類的源碼詳見(jiàn):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()方法中,創(chuàng)建完IOC容器后,會(huì)從IOC容器中獲取ProxyCircularBeanA類型的Bean對(duì)象,并打印ProxyCircularBeanA類型的Bean對(duì)象和依賴的ProxyCircularBeanB類型的Bean對(duì)象。

(5)運(yùn)行ProxyCircularTest類

運(yùn)行ProxyCircularTest類的main()方法,輸出的結(jié)果信息如下所示。

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.

從輸出的結(jié)果信息可以看出,Spring拋出了循環(huán)依賴的異常。說(shuō)明Spring默認(rèn)不支持基于代理對(duì)象的setter方法的循環(huán)依賴。

4.4 構(gòu)造方法的循環(huán)依賴

Spring不支持基于構(gòu)造方法的循環(huán)依賴。本節(jié),就簡(jiǎn)單實(shí)現(xiàn)一個(gè)基于構(gòu)造方法的循環(huán)依賴,具體實(shí)現(xiàn)步驟如下所示。

(1)新增ConstructCircularBeanA類

ConstructCircularBeanA類的源碼詳見(jiàn):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類中通過(guò)構(gòu)造方法依賴了ConstructCircularBeanB類型的Bean對(duì)象,并提供了getConstructCircularBeanB()方法來(lái)獲取ConstructCircularBeanB類型的Bean對(duì)象。

(2)新增ConstructCircularBeanB類

ConstructCircularBeanB類的源碼詳見(jiàn):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類中通過(guò)構(gòu)造方法依賴了ConstructCircularBeanA類型的Bean對(duì)象,并提供了getConstructCircularBeanA()方法來(lái)獲取ConstructCircularBeanA類型的Bean對(duì)象。

(3)新增ConstructCircularConfig類

ConstructCircularConfig類的源碼詳見(jiàn):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類上標(biāo)注了@Configuration注解,說(shuō)明ConstructCircularConfig類是案例程序的配置類,并且使用@ComponentScan注解指定了要掃描的包名。

(4)新增ConstructCircularTest類

ConstructCircularTest類的源碼詳見(jiàn):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("cnotallow===>>>" + constructCircularBeanA);
        System.out.println("cnotallow===>>>" + constructCircularBeanA.getConstructCircularBeanB());
        context.close();
    }
}

可以看到,在ConstructCircularTest類的main()方法中,創(chuàng)建完IOC容器后,會(huì)從IOC容器中獲取ConstructCircularBeanA類型的Bean對(duì)象,并打印ConstructCircularBeanA類型的Bean對(duì)象和依賴的ConstructCircularBeanB類型的Bean對(duì)象。

(5)運(yùn)行ConstructCircularTest類

運(yùn)行ConstructCircularTest類的main()方法,輸出的結(jié)果信息如下所示。

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 'constructCircularBeanB' 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\ConstructCircularBeanB.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'constructCircularBeanA': Requested bean is currently in creation: Is there an unresolvable circular reference?
/*************省略其他信息**************/
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'constructCircularBeanB' 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\ConstructCircularBeanB.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'constructCircularBeanA': Requested bean is currently in creation: Is there an unresolvable circular reference?
/*************省略其他信息**************/
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'constructCircularBeanA': Requested bean is currently in creation: Is there an unresolvable circular reference?

可以看到,Spring拋出了循環(huán)依賴的異常,說(shuō)明Spring不支持基于構(gòu)造方法的循環(huán)依賴。

4.5 @DependsOn的循環(huán)依賴

@DependsOn注解主要用于指定Bean的實(shí)例化順序,Spring默認(rèn)是不支持基于@DependsOn注解的循環(huán)依賴。本節(jié),就實(shí)現(xiàn)基于@DependsOn注解的循環(huán)依賴。具體實(shí)現(xiàn)步驟如下所示。

(1)新增DependsOnCircularBeanA類

DependsOnCircularBeanA類的源碼詳見(jiàn):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類上不僅標(biāo)注了@Component注解,也標(biāo)注了@DependsOn注解指定依賴dependsOnCircularBeanB,并且在DependsOnCircularBeanA類中使用@Autowired注解注入了DependsOnCircularBeanB類型的Bean對(duì)象。同時(shí),在DependsOnCircularBeanA類中提供了getDependsOnCircularBeanB()方法獲取DependsOnCircularBeanB類型的Bean對(duì)象。

(2)新增DependsOnCircularBeanB類

DependsOnCircularBeanB類的源碼詳見(jiàn):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類上不僅標(biāo)注了@Component注解,也標(biāo)注了@DependsOn注解指定依賴dependsOnCircularBeanA,并且在DependsOnCircularBeanB類中使用@Autowired注解注入了DependsOnCircularBeanA類型的Bean對(duì)象。同時(shí),在DependsOnCircularBeanB類中提供了getDependsOnCircularBeanA()方法獲取DependsOnCircularBeanA類型的Bean對(duì)象。

(3)新增DependsOnCircularConfig類

DependsOnCircularConfig類的源碼詳見(jiàn):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類上標(biāo)注了@Configuration注解,說(shuō)明DependsOnCircularConfig類是案例程序的配置類。同時(shí),使用@ComponentScan注解指定了要掃描的包名。

(4)新增DependsOnCircularTest類

DependsOnCircularTest類的源碼詳見(jiàn):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()方法中,創(chuàng)建完IOC容器后,會(huì)從IOC容器中獲取DependsOnCircularBeanA類型的Bean對(duì)象,并打印DependsOnCircularBeanA類型的Bean對(duì)象和依賴的DependsOnCircularBeanB類型的Bean對(duì)象。

(5)運(yùn)行DependsOnCircularTest類

運(yùn)行DependsOnCircularTest類的main()方法,輸出的結(jié)果信息如下所示。

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'

從輸出的結(jié)果信息可以看出,Spring拋出了循環(huán)依賴的異常。說(shuō)明Spring不支持@DependsOn注解的循環(huán)依賴。

4.6 單例Bean的setter循環(huán)依賴

Spring支持基于單例Bean的setter方法的循環(huán)依賴。本節(jié),就實(shí)現(xiàn)基于單例Bean的setter方法的循環(huán)依賴案例。具體實(shí)現(xiàn)步驟如下所示。

(1)新增SingletonCircularBeanA類

SingletonCircularBeanA類的源碼詳見(jiàn):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對(duì)象,并提供了getSingletonCircularBeanB()方法獲取SingletonCircularBeanB類型的Bean對(duì)象。

(2)新增SingletonCircularBeanB類

SingletonCircularBeanB類的源碼詳見(jiàn):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對(duì)象,并提供了getSingletonCircularBeanA()方法獲取SingletonCircularBeanA類型的Bean對(duì)象。

(3)新增SingletonCircularConfig類

SingletonCircularConfig類的源碼詳見(jiàn):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類上標(biāo)注了@Configuration注解,說(shuō)明SingletonCircularConfig類是案例程序的配置類,并使用@ComponentScan注解指定了要掃描的包名。

(4)新增SingletonCircularTest類

SingletonCircularTest類的源碼詳見(jiàn):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("singletnotallow===>>>" + singletonCircularBeanA);
        System.out.println("singletnotallow===>>>" + singletonCircularBeanA.getSingletonCircularBeanB());
        context.close();
    }
}

可以看到,在SingletonCircularTest類的main()方法中,創(chuàng)建完IOC容器后,會(huì)從IOC容器中獲取SingletonCircularBeanA類型的Bean對(duì)象,并打印SingletonCircularBeanA類型的Bean對(duì)象和依賴的SingletonCircularBeanB類型的Bean對(duì)象。

(5)運(yùn)行SingletonCircularTest類

運(yùn)行SingletonCircularTest類的main()方法,輸出的結(jié)果信息如下所示。

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

可以看到,正確輸出了SingletonCircularBeanA類型的Bean對(duì)象和SingletonCircularBeanB類型的Bean對(duì)象。**說(shuō)明Spring支持基于單例Bean的setter方法的循環(huán)依賴。

五、Spring循環(huán)依賴底層解決方案分析

Spring底層是如何解決循環(huán)依賴問(wèn)題的?

由循環(huán)依賴場(chǎng)景的分析得知:Spring除了默認(rèn)支持基于單例Bean的setter方法的循環(huán)依賴外,默認(rèn)均不支持其他情況下的循環(huán)依賴。但是,如果是通過(guò)標(biāo)注@Async注解生成的代理對(duì)象,則可以通過(guò)將標(biāo)注了@Async注解的類排到后面加載的IOC容器中即可解決循環(huán)依賴的問(wèn)題。

5.1 不支持單例Bean的setter循環(huán)依賴的特殊情況

運(yùn)行4.1節(jié)中SpecialCircularTest類的main()方法,輸出的結(jié)果信息如下所示。

Exception in thread "main" java.lang.StackOverflowError

可以看到,實(shí)際拋出的是StackOverflowError錯(cuò)誤。這個(gè)錯(cuò)誤信息本質(zhì)上不是Spring拋出的,而是JVM拋出的。根本原因其實(shí)是出在SpecialCircularBeanA和SpecialCircularBeanB兩個(gè)類的toString()方法上。

當(dāng)在SpecialCircularTest類的main()方法中打印specialCircularBeanA時(shí),默認(rèn)會(huì)調(diào)用SpecialCircularBeanA類的toString()方法,在SpecialCircularBeanA類的toString()方法中,會(huì)拼接specialCircularBeanB對(duì)象,此時(shí)又會(huì)調(diào)用SpecialCircularBeanB類的toString()方法。而在SpecialCircularBeanB類的toString()方法中,又會(huì)拼接specialCircularBeanA對(duì)象,此時(shí)又會(huì)調(diào)用SpecialCircularBeanA類的toString()方法。在SpecialCircularBeanA類的toString()方法中,又會(huì)拼接specialCircularBeanB對(duì)象,繼而調(diào)用SpecialCircularBeanB類的toString()方法....如此反復(fù),造成了死循環(huán)。

簡(jiǎn)單點(diǎn)說(shuō),就是在SpecialCircularBeanA類的toString()方法中調(diào)用了SpecialCircularBeanB類的toString()方法,在SpecialCircularBeanB類的toString()方法中調(diào)用了SpecialCircularBeanA類的toString()方法,造成了死循環(huán),最終拋出StackOverflowError錯(cuò)誤。

5.2 不支持多例Bean的setter循環(huán)依賴

運(yùn)行4.2節(jié)中PrototypeCircularTest類的main()方法,輸出的結(jié)果信息如下所示。

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拋出了循環(huán)依賴的異常,說(shuō)明Spring不支持多例Bean的setter循環(huán)依賴。接下來(lái)就分析下Spring中為啥不支持多例Bean的setter循環(huán)依賴。具體分析步驟如下所示。

(1)解析AbstractBeanFactory類的doGetBean(String name, @Nullable ClassrequiredType, @Nullable Object[] args, boolean typeCheckOnly)方法

源碼詳見(jiàn):org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean(String name, @Nullable ClassrequiredType, @Nullable Object[] args, boolean typeCheckOnly)。先來(lái)看在doGetBean()方法中創(chuàng)建多例Bean對(duì)象的邏輯,如下所示。

protected <T> 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模式下,創(chuàng)建Bean對(duì)象之前會(huì)調(diào)用beforePrototypeCreation()方法,在創(chuàng)建Bean對(duì)象之后會(huì)調(diào)用afterPrototypeCreation()方法。

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

源碼詳見(jiàn):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在創(chuàng)建多例Bean時(shí),會(huì)在beforePrototypeCreation()方法中,使用prototypesCurrentlyInCreation記錄正在創(chuàng)建中的Bean,那prototypesCurrentlyInCreation又是個(gè)什么鬼呢?

prototypesCurrentlyInCreation的源碼詳見(jiàn):org.springframework.beans.factory.support.AbstractBeanFactory#prototypesCurrentlyInCreation,如下所示。

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

也就是說(shuō),Spring在創(chuàng)建多例Bean時(shí),會(huì)使用一個(gè)ThreadLocal類型的變量prototypesCurrentlyInCreation來(lái)記錄當(dāng)前線程正在創(chuàng)建中的Bean。并且根據(jù)beforePrototypeCreation()方法的源碼又可以看出,在prototypesCurrentlyInCreation變量中使用一個(gè)Set集合來(lái)存儲(chǔ)正在創(chuàng)建中的Bean。由于Set集合不存在重復(fù)對(duì)象,所以這樣就能夠保證在一個(gè)線程中只能有一個(gè)相同的Bean正在被創(chuàng)建。

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

源碼詳見(jiàn):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()方法中主要是對(duì)prototypesCurrentlyInCreation中存儲(chǔ)的Bean進(jìn)行移除操作。

綜合beforePrototypeCreation()方法和afterPrototypeCreation()方法可以看出,Spring在創(chuàng)建多例Bean之前,會(huì)將當(dāng)前線程正在創(chuàng)建的Bean存入prototypesCurrentlyInCreation中,待Bean對(duì)象實(shí)例化完成后,就從prototypesCurrentlyInCreation中移除正在創(chuàng)建的Bean。

(4)返回AbstractBeanFactory類的doGetBean(String name, @Nullable ClassrequiredType, @Nullable Object[] args, boolean typeCheckOnly)方法。此時(shí)重點(diǎn)關(guān)注doGetBean()方法開(kāi)始部分的代碼片段。

protected <T> 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()方法開(kāi)始的部分,會(huì)調(diào)用getSingleton()方法從緩存中獲取單例Bean對(duì)象,首先會(huì)執(zhí)行if條件進(jìn)行判斷,如果獲取到的單例Bean對(duì)象為空,說(shuō)明此時(shí)可能是第一次執(zhí)行doGetBean()方法,也可能是創(chuàng)建的多例Bean。接下來(lái)會(huì)進(jìn)入else分支邏輯,在else分支邏輯中,首先會(huì)判斷當(dāng)前線程是否已經(jīng)存在正在創(chuàng)建的Bean,如果存在,則直接拋出BeanCurrentlyInCreationException異常。

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

源碼詳見(jiàn):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創(chuàng)建多例Bean時(shí),無(wú)法解決Bean的循環(huán)依賴。如果在創(chuàng)建多例Bean的過(guò)程中,發(fā)現(xiàn)存在循環(huán)依賴,則直接拋出BeanCurrentlyInCreationException異常。

5.3 不支持代理對(duì)象的setter循環(huán)依賴

運(yùn)行4.3節(jié)中ProxyCircularTest類的main()方法,輸出的結(jié)果信息如下所示。

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.

從輸出的結(jié)果信息可以看出,Spring拋出了循環(huán)依賴的異常。接下來(lái),具體分析Spring為何不支持代理對(duì)象的setter循環(huán)依賴。

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

通過(guò)前面章節(jié)對(duì)于創(chuàng)建Bean的流程分析可知,無(wú)論是創(chuàng)建單例Bean還是創(chuàng)建多例Bean,Spring都會(huì)執(zhí)行到AbstractAutowireCapableBeanFactory類的doCreateBean()方法中。

源碼詳見(jiàn):org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)。重點(diǎn)關(guān)注如下代碼片段。

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()方法中,會(huì)判斷從二級(jí)緩存中獲取到的對(duì)象是否等于原始對(duì)象,代碼片段如下所示。

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

此時(shí),如果是代理對(duì)象的話,二級(jí)緩存中會(huì)存放由AOP生成出來(lái)的代理對(duì)象,與原始對(duì)象不相等。所以,會(huì)拋出BeanCurrentlyInCreationException異常。

另外,仔細(xì)觀察AbstractAutowireCapableBeanFactory類的doCreateBean()方法時(shí),會(huì)發(fā)現(xiàn)如果從二級(jí)緩存中獲取到的earlySingletonReference對(duì)象為空,就會(huì)直接返回,不會(huì)拋出BeanCurrentlyInCreationException異常。由于Spring默認(rèn)會(huì)按照文件全路徑遞歸搜索,并且會(huì)按照路徑+文件名的方式進(jìn)行排序,排序靠前的Bean先被加載。所以,將標(biāo)注了@Async注解的類排在后面即可解決循環(huán)依賴的問(wèn)題。

5.4 不支持構(gòu)造方法的循環(huán)依賴

運(yùn)行4.4節(jié)中ConstructCircularTest類的main()方法,輸出的結(jié)果信息如下所示。

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 'constructCircularBeanB' 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\ConstructCircularBeanB.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'constructCircularBeanA': Requested bean is currently in creation: Is there an unresolvable circular reference?
/*************省略其他信息**************/
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'constructCircularBeanB' 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\ConstructCircularBeanB.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'constructCircularBeanA': Requested bean is currently in creation: Is there an unresolvable circular reference?
/*************省略其他信息**************/
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'constructCircularBeanA': Requested bean is currently in creation: Is there an unresolvable circular reference?

可以看到,Spring拋出了循環(huán)依賴的異常,說(shuō)明Spring不支持基于構(gòu)造方法的循環(huán)依賴。接下來(lái),具體分析下Spring不支持基于構(gòu)造方法的循環(huán)依賴的原因。具體分析步驟如下所示。

(1)解析AbstractBeanFactory類的doGetBean(String name, @Nullable ClassrequiredType, @Nullable Object[] args, boolean typeCheckOnly)方法

源碼詳見(jiàn):org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean(String name, @Nullable ClassrequiredType, @Nullable Object[] args, boolean typeCheckOnly)。

此時(shí)重點(diǎn)關(guān)注如下代碼片段。

protected <T> 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()方法中,會(huì)調(diào)用getSingleton()方法。

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

源碼詳見(jiàn):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()方法創(chuàng)建Bean之前會(huì)調(diào)用beforeSingletonCreation()方法,在創(chuàng)建Bean之后會(huì)調(diào)用afterSingletonCreation()方法。

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

源碼詳見(jiàn):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);
    }
}

可以看到,在創(chuàng)建單例Bean之前,會(huì)將要?jiǎng)?chuàng)建的Bean的名稱添加到singletonsCurrentlyInCreation中,添加失敗,說(shuō)明singletonsCurrentlyInCreation集合中已經(jīng)存在當(dāng)前Bean的名稱,發(fā)生了循環(huán)依賴,就會(huì)拋出BeanCurrentlyInCreationException異常。那么singletonsCurrentlyInCreation又是個(gè)什么鬼呢?

singletonsCurrentlyInCreation的源碼詳見(jiàn):org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#singletonsCurrentlyInCreation。

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

可以看到,singletonsCurrentlyInCreation其實(shí)就是一個(gè)Set集合。也就是說(shuō),Spring會(huì)在創(chuàng)建單例Bean之前,將正在創(chuàng)建的Bean的名稱添加到singletonsCurrentlyInCreation集合中。

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

源碼詳見(jiàn):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中對(duì)應(yīng)的Bean的名稱。

綜合beforeSingletonCreation()和afterSingletonCreation()兩個(gè)方法可以看出,Spring在創(chuàng)建單例Bean之前,會(huì)將Bean的名稱存入singletonsCurrentlyInCreation集合中,實(shí)例化Bean之后,會(huì)將Bean的名稱從singletonsCurrentlyInCreation集合中移除。并且,Spring在創(chuàng)建單例Bean之前,會(huì)調(diào)用beforeSingletonCreation()方法將要?jiǎng)?chuàng)建的Bean的名稱添加到singletonsCurrentlyInCreation中,添加失敗,說(shuō)明singletonsCurrentlyInCreation集合中已經(jīng)存在當(dāng)前Bean的名稱,發(fā)生了循環(huán)依賴,就會(huì)拋出BeanCurrentlyInCreationException異常。

5.5 不支持@DependsOn的循環(huán)依賴

運(yùn)行4.5節(jié)中DependsOnCircularTest類的main()方法,輸出的結(jié)果信息如下所示。

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'

從輸出的結(jié)果信息可以看出,Spring拋出了循環(huán)依賴的異常。說(shuō)明Spring不支持@DependsOn注解的循環(huán)依賴。接下來(lái),就分析下Spring不支持@DependsOn注解的循環(huán)依賴的原因。具體分析步驟如下所示。

解析AbstractBeanFactory類的doGetBean(String name, @Nullable ClassrequiredType, @Nullable Object[] args, boolean typeCheckOnly)方法

源碼詳見(jiàn):org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean(String name, @Nullable ClassrequiredType, @Nullable Object[] args, boolean typeCheckOnly)。重點(diǎn)關(guān)注如下代碼片段。

protected <T> 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 ClassrequiredType, @Nullable Object[] args, boolean typeCheckOnly)方法中,會(huì)判斷是否存在@DependsOn注解的循環(huán)依賴,如果存在則拋出BeanCreationException異常。所以,Spring不支持@DependsOn注解的循環(huán)依賴。

5.6 支持單例Bean的setter循環(huán)依賴

運(yùn)行4.6節(jié)中SingletonCircularTest類的main()方法,輸出的結(jié)果信息如下所示。

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

可以看到,正確輸出了SingletonCircularBeanA類型的Bean對(duì)象和SingletonCircularBeanB類型的Bean對(duì)象。說(shuō)明Spring支持基于單例Bean的setter方法的循環(huán)依賴。接下來(lái),就分析下Spring為何支持單例Bean的setter循環(huán)依賴。

(1)三級(jí)緩存

Spring使用了三級(jí)緩存來(lái)解決循環(huán)依賴的問(wèn)題,三級(jí)緩存的源碼詳見(jiàn):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);

其中,每個(gè)Map的含義如下所示。

  • singletonObjects:一級(jí)緩存,存儲(chǔ)所有實(shí)例化,并且為屬性賦值的單實(shí)例Bean。
  • earlySingletonObjects:二級(jí)緩存,存儲(chǔ)實(shí)例化后還沒(méi)來(lái)得及為屬性賦值的單實(shí)例Bean。
  • singletonFactories:三級(jí)緩存,存儲(chǔ)生產(chǎn)單實(shí)例Bean的工廠。

關(guān)于三級(jí)緩存,面試中經(jīng)常會(huì)問(wèn)如下兩個(gè)問(wèn)題,這里也給大家分享下。

  • Spring解決循環(huán)依賴為什么需要二級(jí)緩存?

二級(jí)緩存主要是為了分離創(chuàng)建出的完整的Bean和未對(duì)屬性賦值的Bean,二級(jí)緩存中實(shí)際主要存儲(chǔ)的是未對(duì)屬性賦值的Bean,這樣做的目的就是為了防止在多線程并發(fā)的場(chǎng)景中,讀取到還未創(chuàng)建完成的Bean。所以,為了保證在多線程并發(fā)的環(huán)境中,讀取到的Bean是完整的(已經(jīng)為屬性賦值),不會(huì)讀取到未對(duì)屬性賦值的Bean,需要使用二級(jí)緩存解決循環(huán)依賴。

另外,就一、二、三級(jí)緩存而言,二級(jí)緩存主要存儲(chǔ)的是三級(jí)緩存創(chuàng)建出來(lái)的,并且未對(duì)屬性賦值的Bean,這樣做的目的也是為了防止三級(jí)緩存中的工廠類重復(fù)執(zhí)行創(chuàng)建對(duì)象的邏輯。

  • Spring只用二級(jí)緩存能否解決循環(huán)依賴?為什么一定要用三級(jí)緩存來(lái)解決循環(huán)依賴呢?

其實(shí),Spring使用二級(jí)緩存就完全能夠解決循環(huán)依賴的問(wèn)題,也可以支持Spring基于BeanPostProcessor的擴(kuò)展能力。但是,由于Spring中的方法在設(shè)計(jì)上遵循了單一職責(zé)的原則,一個(gè)方法通常只做一件事情,getBean()方法就是獲取Bean對(duì)象。但是,調(diào)用BeanPostProcessor創(chuàng)建動(dòng)態(tài)代理是處于創(chuàng)建Bean的過(guò)程,如果在getBean()中實(shí)現(xiàn)這個(gè)邏輯,顯然代碼邏輯比較耦合。為了解決代碼耦合的問(wèn)題,保持方法的職責(zé)單一,方面后期維護(hù)。需要將創(chuàng)建動(dòng)態(tài)代理的BeanPostProcessor放在創(chuàng)建Bean的方法中。并且將判斷是否存在循環(huán)依賴的邏輯放在getSingleton()方法中。此時(shí)就需要三級(jí)緩存,在三級(jí)緩存中存放一個(gè)工廠接口,在接口的實(shí)現(xiàn)類中調(diào)用BeanPostProcessor創(chuàng)建動(dòng)態(tài)代理對(duì)象。為了防止重復(fù)創(chuàng)建代理對(duì)象,將三級(jí)緩存中創(chuàng)建的代理對(duì)象存入二級(jí)緩存。在Spring中使用三級(jí)緩存完美解決了解耦、性能、擴(kuò)展的問(wèn)題。

(2)創(chuàng)建單例工廠

Spring在創(chuàng)建Bean對(duì)象時(shí),會(huì)先創(chuàng)建一個(gè)和Bean的名稱相同的單例工廠,并將Bean先放入單例工廠中。

源碼詳見(jiàn):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()方法中調(diào)用了addSingletonFactory()方法。

addSingletonFactory()方法的源碼詳見(jiàn):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()方法的作用是將正在創(chuàng)建中的Bean的單例工廠,存放在三級(jí)緩存里,這樣就保證了在循環(huán)依賴查找的時(shí)候是可以找到Bean的引用的。

(3)讀取緩存數(shù)據(jù)

具體讀取緩存獲取Bean的過(guò)程在類DefaultSingletonBeanRegistry的getSingleton()方法中,源碼詳見(jiàn):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;
}

通過(guò)上面的源碼我們可以看到,在獲取單例Bean的時(shí)候,會(huì)先從一級(jí)緩存singletonObjects里獲取,如果沒(méi)有獲取到(說(shuō)明不存在或沒(méi)有實(shí)例化完成),會(huì)去第二級(jí)緩存earlySingletonObjects中去找,如果還是沒(méi)有找到的話,就會(huì)三級(jí)緩存中獲取單例工廠singletonFactory,通過(guò)從singletonFactory中獲取正在創(chuàng)建中的引用,將singletonFactory存儲(chǔ)在earlySingletonObjects 二級(jí)緩存中,這樣就將創(chuàng)建中的單例引用從三級(jí)緩存中升級(jí)到了二級(jí)緩存中,二級(jí)緩存earlySingletonObjects,是會(huì)提前暴露已完成構(gòu)造,還未執(zhí)行屬性注入的單例bean的。這個(gè)時(shí)候如何還有其他的bean也是需要屬性注入,那么就可以直接從earlySingletonObjects中獲取了。

注意:為了防止多線程并發(fā)環(huán)境下重復(fù)執(zhí)行三級(jí)緩存中創(chuàng)建Bean的過(guò)程,在對(duì)singletonObjects加鎖后,還會(huì)先從一級(jí)緩存singletonObjects中獲取數(shù)據(jù),如果數(shù)據(jù)不存在則從二級(jí)緩存earlySingletonObjects中獲取數(shù)據(jù),如果數(shù)據(jù)仍然不存在,才會(huì)從三級(jí)緩存singletonFactories中獲取singletonFactory,調(diào)用singletonFactory的getObject()方法獲取實(shí)例化但未對(duì)屬性賦值的Bean對(duì)象,將其存入二級(jí)緩存,并且從三級(jí)緩存中移除對(duì)應(yīng)的singletonFactory。

(4)解決循環(huán)依賴的完整流程圖

最后給出Spring支持單例Bean的setter循環(huán)依賴的完整流程圖,如圖20-6所示。

圖片

大家可以按照20-6的邏輯分析Spring解決循環(huán)依賴的代碼,就相對(duì)比較清晰了。這里,就不再分析具體源碼了。

六、總結(jié)

Spring的循環(huán)依賴問(wèn)題介紹完了,我們一起總結(jié)下吧!

本章,主要詳細(xì)分析了Spring的循環(huán)依賴問(wèn)題,首先介紹了緩存依賴的基本概念和循環(huán)依賴的類型。隨后以案例的形式詳細(xì)介紹了循環(huán)依賴的場(chǎng)景,并詳細(xì)分析了Spring循環(huán)依賴的底層解決方案。通過(guò)分析得知:Spring默認(rèn)會(huì)支持單例Bean的setter循環(huán)依賴,對(duì)于其他情況下的循環(huán)依賴,Spring默認(rèn)是不支持的。并且,最后給出了Spring解決循環(huán)依賴的流程圖。

七、思考

既然學(xué)完了,就開(kāi)始思考幾個(gè)問(wèn)題吧?

關(guān)于Spring的循環(huán)依賴,通常會(huì)有如下幾個(gè)經(jīng)典面試題:

  • 什么是循環(huán)依賴問(wèn)題?
  • 循環(huán)依賴有哪些類型?
  • 在Spring中支持哪種循環(huán)依賴?
  • 列舉幾種Spring不支持的循環(huán)依賴的場(chǎng)景,為什么不支持?
  • Spring解決循環(huán)依賴的流程是什么?
  • Spring解決緩存依賴時(shí),二級(jí)緩存的作用是什么?
  • Spring只用二級(jí)緩存能否解決循環(huán)依賴?為什么一定要用三級(jí)緩存來(lái)解決循環(huán)依賴呢?
  • 你從Spring解決循環(huán)依賴的設(shè)計(jì)中得到了哪些啟發(fā)?
責(zé)任編輯:武曉燕 來(lái)源: 冰河技術(shù)
相關(guān)推薦

2021-10-18 11:58:56

負(fù)載均衡虛擬機(jī)

2022-09-06 08:02:40

死鎖順序鎖輪詢鎖

2022-09-14 09:01:55

shell可視化

2020-07-09 07:54:35

ThreadPoolE線程池

2022-07-19 16:03:14

KubernetesLinux

2021-01-19 05:49:44

DNS協(xié)議

2021-02-26 05:17:38

計(jì)算機(jī)網(wǎng)絡(luò)二進(jìn)制

2020-07-15 08:57:40

HTTPSTCP協(xié)議

2020-11-16 10:47:14

FreeRTOS應(yīng)用嵌入式

2022-10-10 08:35:17

kafka工作機(jī)制消息發(fā)送

2024-03-07 18:11:39

Golang采集鏈接

2021-01-04 05:53:35

MyBatis底層Java

2024-05-10 12:59:58

PyTorch人工智能

2023-06-12 08:49:12

RocketMQ消費(fèi)邏輯

2021-08-26 05:02:50

分布式設(shè)計(jì)

2022-09-08 10:14:29

人臉識(shí)別算法

2024-01-11 09:53:31

面試C++

2024-01-05 08:30:26

自動(dòng)駕駛算法

2022-07-15 16:31:49

Postman測(cè)試

2021-06-04 07:27:24

sourcemap前端技術(shù)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)