日韩成人免费在线_国产成人一二_精品国产免费人成电影在线观..._日本一区二区三区久久久久久久久不

當前位置:首頁 > 科技  > 軟件

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

來源: 責編: 時間:2023-12-04 09:20:27 234觀看
導讀一、學習指引Spring中的循環依賴問題,你真的徹底了解過嗎?Spring的循環依賴問題可以說是面試過程中出現的非常頻繁的問題。比如,在面試過程中面試官可能會問:有了解過Spring中的循環依賴問題嗎?或者會問:什么是循環依賴問題

一、學習指引

Spring中的循環依賴問題,你真的徹底了解過嗎?eZS28資訊網——每日最新資訊28at.com

Spring的循環依賴問題可以說是面試過程中出現的非常頻繁的問題。比如,在面試過程中面試官可能會問:有了解過Spring中的循環依賴問題嗎?或者會問:什么是循環依賴問題?或者會問:Spring中在哪些場景下會產生循環依賴問題?或者會問:Spring是如何解決循環依賴問題的?看似輕描淡寫的一句話,實際要考察的內容也是比較多的。面試官可以通過這個問題考察面試者是否研究過Spring的源碼,有沒有了解過Spring中的循環依賴問題。eZS28資訊網——每日最新資訊28at.com

文末掃碼加星球立減¥200,僅限2023年12月,僅限前300名,先到先得。eZS28資訊網——每日最新資訊28at.com

本章,我們就一起來聊聊Spring中的循環依賴問題。eZS28資訊網——每日最新資訊28at.com

二、循環依賴概述

什么是循環依賴?eZS28資訊網——每日最新資訊28at.com

循環依賴其實也很好理解,可以將這個詞拆分成兩部分,一個是循環,一個是依賴。循環,顧名思義就是指形成了一個閉合環路,也就是閉環。依賴就是指某個事件的發生要依賴另一個事件。eZS28資訊網——每日最新資訊28at.com

在Spring中的循環依賴就是指一個或者多個Bean之間存在著互相依賴的關系,并且形成了循環調用。例如,在Spring中,A依賴B,B又依賴A,A和B之間就形成了相互依賴的關系。創建A對象時,發現A對象依賴了B對象,此時先去創建B對象。創建B對象時,發現B對象又依賴了A對象,此時又去創建A對象。創建A對象時,發現A對象依賴了B對象....如果Spring不去處理這種情況,就會發生死循環,一直會創建A對象和B對象,直到拋出異常為止。eZS28資訊網——每日最新資訊28at.com

同理,在Spring中多個對象之間也有可能存在循環依賴,例如,A依賴B,B依賴C,C又依賴A,A、B、C之間形成了互相依賴的關系,這也是一種循環依賴。eZS28資訊網——每日最新資訊28at.com

三、循環依賴類型

循環依賴有這些類型呢?eZS28資訊網——每日最新資訊28at.com

循環依賴總體上可以分成:自我依賴、直接依賴和間接依賴三種類型,如圖20-1所示。eZS28資訊網——每日最新資訊28at.com

圖片圖片eZS28資訊網——每日最新資訊28at.com

3.1 自我依賴

自我依賴就是自己依賴自己,從而形成的循環依賴,一般情況下不會發生這種循環依賴,如圖20-2所示。eZS28資訊網——每日最新資訊28at.com

圖片圖片eZS28資訊網——每日最新資訊28at.com

3.2 直接依賴

直接依賴一般是發生在兩個對象之間,例如對象A依賴對象B,對象B又依賴對象A,對象A和對象B之間形成了依賴關系,如圖20-3所示。eZS28資訊網——每日最新資訊28at.com

圖片圖片eZS28資訊網——每日最新資訊28at.com

3.3 間接依賴

間接依賴一般是發生在三個或三個以上對象之間互相依賴的場景,例如對象A依賴對象B,對象B依賴對象C,對象C又依賴對象A,對象A、對象B和對象C之間就形成了循環依賴,如圖20-4所示。eZS28資訊網——每日最新資訊28at.com

圖片圖片eZS28資訊網——每日最新資訊28at.com

四、循環依賴場景

Spring中有哪些循環依賴的場景?eZS28資訊網——每日最新資訊28at.com

Spring中的循環依賴場景總體上可以分成單例Bean的setter循環依賴、多例Bean的setter循環依賴、代理對象的setter循環依賴、構造方法的循環依賴和DependsOn的循環依賴。如圖20-5所示。eZS28資訊網——每日最新資訊28at.com

圖片圖片eZS28資訊網——每日最新資訊28at.com

4.1 單例Bean的setter循環依賴的特殊情況

Spring是支持基于單例Bean的setter方法的循環依賴的,不過有一種特殊情況需要注意。本節,我們就一起實現基于單例Bean的setter方法的循環依賴的特殊情況,具體實現步驟如下所示。eZS28資訊網——每日最新資訊28at.com

(1)新增SpecialCircularBeanA類

SpecialCircularBeanA類的源碼詳見:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.special.bean.SpecialCircularBeanA。eZS28資訊網——每日最新資訊28at.com

@Componentpublic 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類型的對象。eZS28資訊網——每日最新資訊28at.com

(2)新增SpecialCircularBeanB類

SpecialCircularBeanB類的源碼詳見:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.special.bean.SpecialCircularBeanB。eZS28資訊網——每日最新資訊28at.com

@Componentpublic 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類型的對象。eZS28資訊網——每日最新資訊28at.com

(3)新增SpecialCircularConfig類

SpecialCircularConfig類的源碼詳見:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.special.config.SpecialCircularConfig。eZS28資訊網——每日最新資訊28at.com

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

可以看到,在SpecialCircularConfig類上標注了@Configuration注解,說明SpecialCircularConfig類是案例程序的配置類,并且使用@ComponentScan注解指定了掃描的包。eZS28資訊網——每日最新資訊28at.com

(4)新增SpecialCircularTest類

SpecialCircularTest類的源碼詳見:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.special.SpecialCircularTest。eZS28資訊網——每日最新資訊28at.com

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容器。eZS28資訊網——每日最新資訊28at.com

(5)運行SpecialCircularTest類

運行SpecialCircularTest類的main()方法,輸出的結果信息如下所示。eZS28資訊網——每日最新資訊28at.com

Exception in thread "main" java.lang.StackOverflowError

可以看到,程序拋出了StackOverflowError異常。eZS28資訊網——每日最新資訊28at.com

其實,從本質上講,這個異常不是Spring拋出的,而是JVM拋出的棧溢出錯誤。eZS28資訊網——每日最新資訊28at.com

4.2 多例Bean的setter循環依賴

Spring是不支持基于多例Bean,也就是原型模式下的Bean的setter方法的循環依賴。本節,我們一起實現一個基于多例Bean的set方法的循環依賴案例,具體實現步驟如下所示。eZS28資訊網——每日最新資訊28at.com

(1)新增PrototypeCircularBeanA類

PrototypeCircularBeanA類的源碼詳見:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.prototype.bean.PrototypeCircularBeanA。eZS28資訊網——每日最新資訊28at.com

@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對象。eZS28資訊網——每日最新資訊28at.com

(2)新增PrototypeCircularBeanB類

PrototypeCircularBeanB類的源碼詳見:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.prototype.bean.PrototypeCircularBeanB。eZS28資訊網——每日最新資訊28at.com

@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對象。eZS28資訊網——每日最新資訊28at.com

(3)新增PrototypeCircularConfig類

PrototypeCircularConfig類的源碼詳見:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.prototype.config.PrototypeCircularConfig。eZS28資訊網——每日最新資訊28at.com

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

可以看到,在PrototypeCircularConfig類上標注了@Configuration注解,說明PrototypeCircularConfig類是案例程序的配置類,并且使用@ComponentScan注解指定了要掃描的包名。eZS28資訊網——每日最新資訊28at.com

(4)新增PrototypeCircularTest類

PrototypeCircularTest類的源碼詳見:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.prototype.PrototypeCircularTest。eZS28資訊網——每日最新資訊28at.com

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對象。eZS28資訊網——每日最新資訊28at.com

(5)運行PrototypeCircularTest類

運行PrototypeCircularTest類的main()方法,輸出的結果信息如下所示。eZS28資訊網——每日最新資訊28at.com

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方法循環依賴。eZS28資訊網——每日最新資訊28at.com

4.3 代理對象的setter循環依賴

Spring默認是不支持基于代理對象的setter方法的循環依賴,本節,就簡單實現一個基于代理對象的setter方法的循環依賴案例。具體實現步驟如下所示。eZS28資訊網——每日最新資訊28at.com

(1)新增ProxyCircularBeanA類

ProxyCircularBeanA類的源碼詳見:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.proxy.bean.ProxyCircularBeanA。eZS28資訊網——每日最新資訊28at.com

@Componentpublic class ProxyCircularBeanA {    @Autowired    private ProxyCircularBeanB proxyCircularBeanB;    @Async    public ProxyCircularBeanB getProxyCircularBeanB() {        return proxyCircularBeanB;    }}

可以看到,ProxyCircularBeanA類型的Bean對象會依賴ProxyCircularBeanB類型的Bean對象,并且在getProxyCircularBeanB()方法上標注了@Async注解,當調用getProxyCircularBeanB()方法時,會通過AOP自動生成代理對象。eZS28資訊網——每日最新資訊28at.com

(2)新增ProxyCircularBeanB類eZS28資訊網——每日最新資訊28at.com

ProxyCircularBeanB類的源碼詳見:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.proxy.bean.ProxyCircularBeanB。eZS28資訊網——每日最新資訊28at.com

@Componentpublic class ProxyCircularBeanB {    @Autowired    private ProxyCircularBeanA proxyCircularBeanA;    @Async    public ProxyCircularBeanA getProxyCircularBeanA() {        return proxyCircularBeanA;    }}

可以看到,ProxyCircularBeanB類型的Bean對象會依賴ProxyCircularBeanA類型的Bean對象,并且在getProxyCircularBeanA()方法上標注了@Async注解,當調用getProxyCircularBeanA()方法時,會通過AOP自動生成代理對象。eZS28資訊網——每日最新資訊28at.com

(3)新增ProxyCircularConfig類eZS28資訊網——每日最新資訊28at.com

ProxyCircularConfig類的源碼詳見:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.proxy.config.ProxyCircularConfig。eZS28資訊網——每日最新資訊28at.com

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

可以看到,在ProxyCircularConfig類上標注了@Configuration注解,說明ProxyCircularConfig類是案例程序的配置類。并且在ProxyCircularConfig類上使用@ComponentScan注解指定了要掃描的包名。同時,使用@EnableAsync注解開啟了異步調用。eZS28資訊網——每日最新資訊28at.com

(4)新增ProxyCircularTest類eZS28資訊網——每日最新資訊28at.com

ProxyCircularTest類的源碼詳見:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.proxy.ProxyCircularTest。eZS28資訊網——每日最新資訊28at.com

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對象。eZS28資訊網——每日最新資訊28at.com

(5)運行ProxyCircularTest類eZS28資訊網——每日最新資訊28at.com

運行ProxyCircularTest類的main()方法,輸出的結果信息如下所示。eZS28資訊網——每日最新資訊28at.com

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方法的循環依賴。eZS28資訊網——每日最新資訊28at.com

4.4 構造方法的循環依賴

Spring不支持基于構造方法的循環依賴。本節,就簡單實現一個基于構造方法的循環依賴,具體實現步驟如下所示。eZS28資訊網——每日最新資訊28at.com

(1)新增ConstructCircularBeanA類eZS28資訊網——每日最新資訊28at.com

ConstructCircularBeanA類的源碼詳見:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.construct.bean.ConstructCircularBeanA。eZS28資訊網——每日最新資訊28at.com

@Componentpublic class ConstructCircularBeanA {        private ConstructCircularBeanB constructCircularBeanB;    private ConstructCircularBeanA(ConstructCircularBeanB constructCircularBeanB){        this.constructCircularBeanB = constructCircularBeanB;    }    public ConstructCircularBeanB getConstructCircularBeanB() {        return constructCircularBeanB;    }}

可以看到,在ConstructCircularBeanA類中通過構造方法依賴了ConstructCircularBeanB類型的Bean對象,并提供了getConstructCircularBeanB()方法來獲取ConstructCircularBeanB類型的Bean對象。eZS28資訊網——每日最新資訊28at.com

(2)新增ConstructCircularBeanB類eZS28資訊網——每日最新資訊28at.com

ConstructCircularBeanB類的源碼詳見:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.construct.bean.ConstructCircularBeanB。eZS28資訊網——每日最新資訊28at.com

@Componentpublic class ConstructCircularBeanB {    private ConstructCircularBeanA constructCircularBeanA;    public ConstructCircularBeanB(ConstructCircularBeanA constructCircularBeanA) {        this.constructCircularBeanA = constructCircularBeanA;    }    public ConstructCircularBeanA getConstructCircularBeanA() {        return constructCircularBeanA;    }}

可以看到,在ConstructCircularBeanB類中通過構造方法依賴了ConstructCircularBeanA類型的Bean對象,并提供了getConstructCircularBeanA()方法來獲取ConstructCircularBeanA類型的Bean對象。eZS28資訊網——每日最新資訊28at.com

(3)新增ConstructCircularConfig類eZS28資訊網——每日最新資訊28at.com

ConstructCircularConfig類的源碼詳見:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.construct.config.ConstructCircularConfig。eZS28資訊網——每日最新資訊28at.com

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

可以看到,在ConstructCircularConfig類上標注了@Configuration注解,說明ConstructCircularConfig類是案例程序的配置類,并且使用@ComponentScan注解指定了要掃描的包名。eZS28資訊網——每日最新資訊28at.com

(4)新增ConstructCircularTest類eZS28資訊網——每日最新資訊28at.com

ConstructCircularTest類的源碼詳見:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.construct.ConstructCircularTest。eZS28資訊網——每日最新資訊28at.com

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()方法中,創建完IOC容器后,會從IOC容器中獲取ConstructCircularBeanA類型的Bean對象,并打印ConstructCircularBeanA類型的Bean對象和依賴的ConstructCircularBeanB類型的Bean對象。eZS28資訊網——每日最新資訊28at.com

(5)運行ConstructCircularTest類eZS28資訊網——每日最新資訊28at.com

運行ConstructCircularTest類的main()方法,輸出的結果信息如下所示。eZS28資訊網——每日最新資訊28at.com

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拋出了循環依賴的異常,說明Spring不支持基于構造方法的循環依賴。eZS28資訊網——每日最新資訊28at.com

4.5 @DependsOn的循環依賴

@DependsOn注解主要用于指定Bean的實例化順序,Spring默認是不支持基于@DependsOn注解的循環依賴。本節,就實現基于@DependsOn注解的循環依賴。具體實現步驟如下所示。eZS28資訊網——每日最新資訊28at.com

(1)新增DependsOnCircularBeanA類eZS28資訊網——每日最新資訊28at.com

DependsOnCircularBeanA類的源碼詳見:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.dependson.bean.DependsOnCircularBeanA。eZS28資訊網——每日最新資訊28at.com

@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對象。eZS28資訊網——每日最新資訊28at.com

(2)新增DependsOnCircularBeanB類eZS28資訊網——每日最新資訊28at.com

DependsOnCircularBeanB類的源碼詳見:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.dependson.bean.DependsOnCircularBeanB。eZS28資訊網——每日最新資訊28at.com

@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對象。eZS28資訊網——每日最新資訊28at.com

(3)新增DependsOnCircularConfig類eZS28資訊網——每日最新資訊28at.com

DependsOnCircularConfig類的源碼詳見:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.dependson.config.DependsOnCircularConfig。eZS28資訊網——每日最新資訊28at.com

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

可以看到,在DependsOnCircularConfig類上標注了@Configuration注解,說明DependsOnCircularConfig類是案例程序的配置類。同時,使用@ComponentScan注解指定了要掃描的包名。eZS28資訊網——每日最新資訊28at.com

(4)新增DependsOnCircularTest類eZS28資訊網——每日最新資訊28at.com

DependsOnCircularTest類的源碼詳見:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.dependson.DependsOnCircularTest。eZS28資訊網——每日最新資訊28at.com

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對象。eZS28資訊網——每日最新資訊28at.com

(5)運行DependsOnCircularTest類eZS28資訊網——每日最新資訊28at.com

運行DependsOnCircularTest類的main()方法,輸出的結果信息如下所示。eZS28資訊網——每日最新資訊28at.com

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注解的循環依賴。eZS28資訊網——每日最新資訊28at.com

4.6 單例Bean的setter循環依賴

Spring支持基于單例Bean的setter方法的循環依賴。本節,就實現基于單例Bean的setter方法的循環依賴案例。具體實現步驟如下所示。eZS28資訊網——每日最新資訊28at.com

(1)新增SingletonCircularBeanA類eZS28資訊網——每日最新資訊28at.com

SingletonCircularBeanA類的源碼詳見:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.singleton.bean.SingletonCircularBeanA。eZS28資訊網——每日最新資訊28at.com

@Componentpublic class SingletonCircularBeanA {    @Autowired    private SingletonCircularBeanB singletonCircularBeanB;    public SingletonCircularBeanB getSingletonCircularBeanB() {        return singletonCircularBeanB;    }}

可以看到,在SingletonCircularBeanA類中依賴了SingletonCircularBeanB類型的Bean對象,并提供了getSingletonCircularBeanB()方法獲取SingletonCircularBeanB類型的Bean對象。eZS28資訊網——每日最新資訊28at.com

(2)新增SingletonCircularBeanB類eZS28資訊網——每日最新資訊28at.com

SingletonCircularBeanB類的源碼詳見:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.singleton.bean.SingletonCircularBeanB。eZS28資訊網——每日最新資訊28at.com

@Componentpublic class SingletonCircularBeanB {    @Autowired    private SingletonCircularBeanA singletonCircularBeanA;    public SingletonCircularBeanA getSingletonCircularBeanA() {        return singletonCircularBeanA;    }}

可以看到,在SingletonCircularBeanB類中依賴了SingletonCircularBeanA類型的Bean對象,并提供了getSingletonCircularBeanA()方法獲取SingletonCircularBeanA類型的Bean對象。eZS28資訊網——每日最新資訊28at.com

(3)新增SingletonCircularConfig類eZS28資訊網——每日最新資訊28at.com

SingletonCircularConfig類的源碼詳見:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.singleton.config.SingletonCircularConfig。eZS28資訊網——每日最新資訊28at.com

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

可以看到,在SingletonCircularConfig類上標注了@Configuration注解,說明SingletonCircularConfig類是案例程序的配置類,并使用@ComponentScan注解指定了要掃描的包名。eZS28資訊網——每日最新資訊28at.com

(4)新增SingletonCircularTest類eZS28資訊網——每日最新資訊28at.com

SingletonCircularTest類的源碼詳見:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.singleton.SingletonCircularTest。eZS28資訊網——每日最新資訊28at.com

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()方法中,創建完IOC容器后,會從IOC容器中獲取SingletonCircularBeanA類型的Bean對象,并打印SingletonCircularBeanA類型的Bean對象和依賴的SingletonCircularBeanB類型的Bean對象。eZS28資訊網——每日最新資訊28at.com

(5)運行SingletonCircularTest類eZS28資訊網——每日最新資訊28at.com

運行SingletonCircularTest類的main()方法,輸出的結果信息如下所示。eZS28資訊網——每日最新資訊28at.com

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

可以看到,正確輸出了SingletonCircularBeanA類型的Bean對象和SingletonCircularBeanB類型的Bean對象。**說明Spring支持基于單例Bean的setter方法的循環依賴。eZS28資訊網——每日最新資訊28at.com

五、Spring循環依賴底層解決方案分析

Spring底層是如何解決循環依賴問題的?eZS28資訊網——每日最新資訊28at.com

由循環依賴場景的分析得知:Spring除了默認支持基于單例Bean的setter方法的循環依賴外,默認均不支持其他情況下的循環依賴。但是,如果是通過標注@Async注解生成的代理對象,則可以通過將標注了@Async注解的類排到后面加載的IOC容器中即可解決循環依賴的問題。eZS28資訊網——每日最新資訊28at.com

5.1 不支持單例Bean的setter循環依賴的特殊情況

運行4.1節中SpecialCircularTest類的main()方法,輸出的結果信息如下所示。eZS28資訊網——每日最新資訊28at.com

Exception in thread "main" java.lang.StackOverflowError

可以看到,實際拋出的是StackOverflowError錯誤。這個錯誤信息本質上不是Spring拋出的,而是JVM拋出的。根本原因其實是出在SpecialCircularBeanA和SpecialCircularBeanB兩個類的toString()方法上。eZS28資訊網——每日最新資訊28at.com

當在SpecialCircularTest類的main()方法中打印specialCircularBeanA時,默認會調用SpecialCircularBeanA類的toString()方法,在SpecialCircularBeanA類的toString()方法中,會拼接specialCircularBeanB對象,此時又會調用SpecialCircularBeanB類的toString()方法。而在SpecialCircularBeanB類的toString()方法中,又會拼接specialCircularBeanA對象,此時又會調用SpecialCircularBeanA類的toString()方法。在SpecialCircularBeanA類的toString()方法中,又會拼接specialCircularBeanB對象,繼而調用SpecialCircularBeanB類的toString()方法....如此反復,造成了死循環。eZS28資訊網——每日最新資訊28at.com

簡單點說,就是在SpecialCircularBeanA類的toString()方法中調用了SpecialCircularBeanB類的toString()方法,在SpecialCircularBeanB類的toString()方法中調用了SpecialCircularBeanA類的toString()方法,造成了死循環,最終拋出StackOverflowError錯誤。eZS28資訊網——每日最新資訊28at.com

5.2 不支持多例Bean的setter循環依賴

運行4.2節中PrototypeCircularTest類的main()方法,輸出的結果信息如下所示。eZS28資訊網——每日最新資訊28at.com

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循環依賴。具體分析步驟如下所示。eZS28資訊網——每日最新資訊28at.com

(1)解析AbstractBeanFactory類的doGetBean(String name, @Nullable ClassrequiredType, @Nullable Object[] args, boolean typeCheckOnly)方法eZS28資訊網——每日最新資訊28at.com

源碼詳見:org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean(String name, @Nullable ClassrequiredType, @Nullable Object[] args, boolean typeCheckOnly)。先來看在doGetBean()方法中創建多例Bean對象的邏輯,如下所示。eZS28資訊網——每日最新資訊28at.com

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模式下,創建Bean對象之前會調用beforePrototypeCreation()方法,在創建Bean對象之后會調用afterPrototypeCreation()方法。eZS28資訊網——每日最新資訊28at.com

(2)解析AbstractBeanFactory類的beforePrototypeCreation(String beanName)方法eZS28資訊網——每日最新資訊28at.com

源碼詳見:org.springframework.beans.factory.support.AbstractBeanFactory#beforePrototypeCreation(String beanName)。eZS28資訊網——每日最新資訊28at.com

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又是個什么鬼呢?eZS28資訊網——每日最新資訊28at.com

prototypesCurrentlyInCreation的源碼詳見:org.springframework.beans.factory.support.AbstractBeanFactory#prototypesCurrentlyInCreation,如下所示。eZS28資訊網——每日最新資訊28at.com

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

也就是說,Spring在創建多例Bean時,會使用一個ThreadLocal類型的變量prototypesCurrentlyInCreation來記錄當前線程正在創建中的Bean。并且根據beforePrototypeCreation()方法的源碼又可以看出,在prototypesCurrentlyInCreation變量中使用一個Set集合來存儲正在創建中的Bean。由于Set集合不存在重復對象,所以這樣就能夠保證在一個線程中只能有一個相同的Bean正在被創建。eZS28資訊網——每日最新資訊28at.com

(3)解析AbstractBeanFactory類的afterPrototypeCreation(String beanName)方法eZS28資訊網——每日最新資訊28at.com

源碼詳見:org.springframework.beans.factory.support.AbstractBeanFactory#afterPrototypeCreation(String beanName)。eZS28資訊網——每日最新資訊28at.com

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進行移除操作。eZS28資訊網——每日最新資訊28at.com

綜合beforePrototypeCreation()方法和afterPrototypeCreation()方法可以看出,Spring在創建多例Bean之前,會將當前線程正在創建的Bean存入prototypesCurrentlyInCreation中,待Bean對象實例化完成后,就從prototypesCurrentlyInCreation中移除正在創建的Bean。eZS28資訊網——每日最新資訊28at.com

(4)返回AbstractBeanFactory類的doGetBean(String name, @Nullable ClassrequiredType, @Nullable Object[] args, boolean typeCheckOnly)方法。此時重點關注doGetBean()方法開始部分的代碼片段。eZS28資訊網——每日最新資訊28at.com

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()方法開始的部分,會調用getSingleton()方法從緩存中獲取單例Bean對象,首先會執行if條件進行判斷,如果獲取到的單例Bean對象為空,說明此時可能是第一次執行doGetBean()方法,也可能是創建的多例Bean。接下來會進入else分支邏輯,在else分支邏輯中,首先會判斷當前線程是否已經存在正在創建的Bean,如果存在,則直接拋出BeanCurrentlyInCreationException異常。eZS28資訊網——每日最新資訊28at.com

(5)解析AbstractBeanFactory類的isPrototypeCurrentlyInCreation(String beanName)方法eZS28資訊網——每日最新資訊28at.com

源碼詳見:org.springframework.beans.factory.support.AbstractBeanFactory#isPrototypeCurrentlyInCreation(String beanName)。eZS28資訊網——每日最新資訊28at.com

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異常。eZS28資訊網——每日最新資訊28at.com

5.3 不支持代理對象的setter循環依賴

運行4.3節中ProxyCircularTest類的main()方法,輸出的結果信息如下所示。eZS28資訊網——每日最新資訊28at.com

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循環依賴。eZS28資訊網——每日最新資訊28at.com

(1)解析AbstractAutowireCapableBeanFactory類的doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)方法。eZS28資訊網——每日最新資訊28at.com

通過前面章節對于創建Bean的流程分析可知,無論是創建單例Bean還是創建多例Bean,Spring都會執行到AbstractAutowireCapableBeanFactory類的doCreateBean()方法中。eZS28資訊網——每日最新資訊28at.com

源碼詳見:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)。重點關注如下代碼片段。eZS28資訊網——每日最新資訊28at.com

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()方法中,會判斷從二級緩存中獲取到的對象是否等于原始對象,代碼片段如下所示。eZS28資訊網——每日最新資訊28at.com

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

此時,如果是代理對象的話,二級緩存中會存放由AOP生成出來的代理對象,與原始對象不相等。所以,會拋出BeanCurrentlyInCreationException異常。eZS28資訊網——每日最新資訊28at.com

另外,仔細觀察AbstractAutowireCapableBeanFactory類的doCreateBean()方法時,會發現如果從二級緩存中獲取到的earlySingletonReference對象為空,就會直接返回,不會拋出BeanCurrentlyInCreationException異常。由于Spring默認會按照文件全路徑遞歸搜索,并且會按照路徑+文件名的方式進行排序,排序靠前的Bean先被加載。所以,將標注了@Async注解的類排在后面即可解決循環依賴的問題。eZS28資訊網——每日最新資訊28at.com

5.4 不支持構造方法的循環依賴

運行4.4節中ConstructCircularTest類的main()方法,輸出的結果信息如下所示。eZS28資訊網——每日最新資訊28at.com

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拋出了循環依賴的異常,說明Spring不支持基于構造方法的循環依賴。接下來,具體分析下Spring不支持基于構造方法的循環依賴的原因。具體分析步驟如下所示。eZS28資訊網——每日最新資訊28at.com

(1)解析AbstractBeanFactory類的doGetBean(String name, @Nullable ClassrequiredType, @Nullable Object[] args, boolean typeCheckOnly)方法eZS28資訊網——每日最新資訊28at.com

源碼詳見:org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean(String name, @Nullable ClassrequiredType, @Nullable Object[] args, boolean typeCheckOnly)。eZS28資訊網——每日最新資訊28at.com

此時重點關注如下代碼片段。eZS28資訊網——每日最新資訊28at.com

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()方法中,會調用getSingleton()方法。eZS28資訊網——每日最新資訊28at.com

(2)解析DefaultSingletonBeanRegistry類的getSingleton(String beanName, ObjectFactory<?> singletonFactory)方法eZS28資訊網——每日最新資訊28at.com

源碼詳見:org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(String beanName, ObjectFactory<?> singletonFactory)。eZS28資訊網——每日最新資訊28at.com

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()方法。eZS28資訊網——每日最新資訊28at.com

(3)解析DefaultSingletonBeanRegistry類的beforeSingletonCreation(String beanName)方法eZS28資訊網——每日最新資訊28at.com

源碼詳見:org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#beforeSingletonCreation(String beanName)。eZS28資訊網——每日最新資訊28at.com

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又是個什么鬼呢?eZS28資訊網——每日最新資訊28at.com

singletonsCurrentlyInCreation的源碼詳見:org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#singletonsCurrentlyInCreation。eZS28資訊網——每日最新資訊28at.com

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

可以看到,singletonsCurrentlyInCreation其實就是一個Set集合。也就是說,Spring會在創建單例Bean之前,將正在創建的Bean的名稱添加到singletonsCurrentlyInCreation集合中。eZS28資訊網——每日最新資訊28at.com

(4)解析DefaultSingletonBeanRegistry類的afterSingletonCreation(String beanName)方法eZS28資訊網——每日最新資訊28at.com

源碼詳見:org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#afterSingletonCreation(String beanName)。eZS28資訊網——每日最新資訊28at.com

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的名稱。eZS28資訊網——每日最新資訊28at.com

綜合beforeSingletonCreation()和afterSingletonCreation()兩個方法可以看出,Spring在創建單例Bean之前,會將Bean的名稱存入singletonsCurrentlyInCreation集合中,實例化Bean之后,會將Bean的名稱從singletonsCurrentlyInCreation集合中移除。并且,Spring在創建單例Bean之前,會調用beforeSingletonCreation()方法將要創建的Bean的名稱添加到singletonsCurrentlyInCreation中,添加失敗,說明singletonsCurrentlyInCreation集合中已經存在當前Bean的名稱,發生了循環依賴,就會拋出BeanCurrentlyInCreationException異常。eZS28資訊網——每日最新資訊28at.com

5.5 不支持@DependsOn的循環依賴

運行4.5節中DependsOnCircularTest類的main()方法,輸出的結果信息如下所示。eZS28資訊網——每日最新資訊28at.com

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注解的循環依賴的原因。具體分析步驟如下所示。eZS28資訊網——每日最新資訊28at.com

解析AbstractBeanFactory類的doGetBean(String name, @Nullable ClassrequiredType, @Nullable Object[] args, boolean typeCheckOnly)方法eZS28資訊網——每日最新資訊28at.com

源碼詳見:org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean(String name, @Nullable ClassrequiredType, @Nullable Object[] args, boolean typeCheckOnly)。重點關注如下代碼片段。eZS28資訊網——每日最新資訊28at.com

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)方法中,會判斷是否存在@DependsOn注解的循環依賴,如果存在則拋出BeanCreationException異常。所以,Spring不支持@DependsOn注解的循環依賴。eZS28資訊網——每日最新資訊28at.com

5.6 支持單例Bean的setter循環依賴

運行4.6節中SingletonCircularTest類的main()方法,輸出的結果信息如下所示。eZS28資訊網——每日最新資訊28at.com

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

可以看到,正確輸出了SingletonCircularBeanA類型的Bean對象和SingletonCircularBeanB類型的Bean對象。說明Spring支持基于單例Bean的setter方法的循環依賴。接下來,就分析下Spring為何支持單例Bean的setter循環依賴。eZS28資訊網——每日最新資訊28at.com

(1)三級緩存eZS28資訊網——每日最新資訊28at.com

Spring使用了三級緩存來解決循環依賴的問題,三級緩存的源碼詳見:org.springframework.beans.factory.support.DefaultSingletonBeanRegistry。eZS28資訊網——每日最新資訊28at.com

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的含義如下所示。eZS28資訊網——每日最新資訊28at.com

  • singletonObjects:一級緩存,存儲所有實例化,并且為屬性賦值的單實例Bean。
  • earlySingletonObjects:二級緩存,存儲實例化后還沒來得及為屬性賦值的單實例Bean。
  • singletonFactories:三級緩存,存儲生產單實例Bean的工廠。

關于三級緩存,面試中經常會問如下兩個問題,這里也給大家分享下。eZS28資訊網——每日最新資訊28at.com

  • Spring解決循環依賴為什么需要二級緩存?

二級緩存主要是為了分離創建出的完整的Bean和未對屬性賦值的Bean,二級緩存中實際主要存儲的是未對屬性賦值的Bean,這樣做的目的就是為了防止在多線程并發的場景中,讀取到還未創建完成的Bean。所以,為了保證在多線程并發的環境中,讀取到的Bean是完整的(已經為屬性賦值),不會讀取到未對屬性賦值的Bean,需要使用二級緩存解決循環依賴。eZS28資訊網——每日最新資訊28at.com

另外,就一、二、三級緩存而言,二級緩存主要存儲的是三級緩存創建出來的,并且未對屬性賦值的Bean,這樣做的目的也是為了防止三級緩存中的工廠類重復執行創建對象的邏輯。eZS28資訊網——每日最新資訊28at.com

  • Spring只用二級緩存能否解決循環依賴?為什么一定要用三級緩存來解決循環依賴呢?

其實,Spring使用二級緩存就完全能夠解決循環依賴的問題,也可以支持Spring基于BeanPostProcessor的擴展能力。但是,由于Spring中的方法在設計上遵循了單一職責的原則,一個方法通常只做一件事情,getBean()方法就是獲取Bean對象。但是,調用BeanPostProcessor創建動態代理是處于創建Bean的過程,如果在getBean()中實現這個邏輯,顯然代碼邏輯比較耦合。為了解決代碼耦合的問題,保持方法的職責單一,方面后期維護。需要將創建動態代理的BeanPostProcessor放在創建Bean的方法中。并且將判斷是否存在循環依賴的邏輯放在getSingleton()方法中。此時就需要三級緩存,在三級緩存中存放一個工廠接口,在接口的實現類中調用BeanPostProcessor創建動態代理對象。為了防止重復創建代理對象,將三級緩存中創建的代理對象存入二級緩存。在Spring中使用三級緩存完美解決了解耦、性能、擴展的問題。eZS28資訊網——每日最新資訊28at.com

(2)創建單例工廠eZS28資訊網——每日最新資訊28at.com

Spring在創建Bean對象時,會先創建一個和Bean的名稱相同的單例工廠,并將Bean先放入單例工廠中。eZS28資訊網——每日最新資訊28at.com

源碼詳見:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)。eZS28資訊網——每日最新資訊28at.com

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()方法。eZS28資訊網——每日最新資訊28at.com

addSingletonFactory()方法的源碼詳見:org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory)。eZS28資訊網——每日最新資訊28at.com

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的引用的。eZS28資訊網——每日最新資訊28at.com

(3)讀取緩存數據eZS28資訊網——每日最新資訊28at.com

具體讀取緩存獲取Bean的過程在類DefaultSingletonBeanRegistry的getSingleton()方法中,源碼詳見:org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(String beanName, boolean allowEarlyReference)。eZS28資訊網——每日最新資訊28at.com

@Nullableprotected 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中獲取了。eZS28資訊網——每日最新資訊28at.com

注意:為了防止多線程并發環境下重復執行三級緩存中創建Bean的過程,在對singletonObjects加鎖后,還會先從一級緩存singletonObjects中獲取數據,如果數據不存在則從二級緩存earlySingletonObjects中獲取數據,如果數據仍然不存在,才會從三級緩存singletonFactories中獲取singletonFactory,調用singletonFactory的getObject()方法獲取實例化但未對屬性賦值的Bean對象,將其存入二級緩存,并且從三級緩存中移除對應的singletonFactory。eZS28資訊網——每日最新資訊28at.com

(4)解決循環依賴的完整流程圖eZS28資訊網——每日最新資訊28at.com

最后給出Spring支持單例Bean的setter循環依賴的完整流程圖,如圖20-6所示。eZS28資訊網——每日最新資訊28at.com

圖片eZS28資訊網——每日最新資訊28at.com

大家可以按照20-6的邏輯分析Spring解決循環依賴的代碼,就相對比較清晰了。這里,就不再分析具體源碼了。eZS28資訊網——每日最新資訊28at.com

六、總結

Spring的循環依賴問題介紹完了,我們一起總結下吧!eZS28資訊網——每日最新資訊28at.com

本章,主要詳細分析了Spring的循環依賴問題,首先介紹了緩存依賴的基本概念和循環依賴的類型。隨后以案例的形式詳細介紹了循環依賴的場景,并詳細分析了Spring循環依賴的底層解決方案。通過分析得知:Spring默認會支持單例Bean的setter循環依賴,對于其他情況下的循環依賴,Spring默認是不支持的。并且,最后給出了Spring解決循環依賴的流程圖。eZS28資訊網——每日最新資訊28at.com

七、思考

既然學完了,就開始思考幾個問題吧?eZS28資訊網——每日最新資訊28at.com

關于Spring的循環依賴,通常會有如下幾個經典面試題:eZS28資訊網——每日最新資訊28at.com

  • 什么是循環依賴問題?
  • 循環依賴有哪些類型?
  • 在Spring中支持哪種循環依賴?
  • 列舉幾種Spring不支持的循環依賴的場景,為什么不支持?
  • Spring解決循環依賴的流程是什么?
  • Spring解決緩存依賴時,二級緩存的作用是什么?
  • Spring只用二級緩存能否解決循環依賴?為什么一定要用三級緩存來解決循環依賴呢?
  • 你從Spring解決循環依賴的設計中得到了哪些啟發?

eZS28資訊網——每日最新資訊28at.com

本文鏈接:http://www.www897cc.com/showinfo-26-37253-0.html萬字長文帶你徹底吃透Spring循環依賴,堪稱全網最全(文末福利)

聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: Tomcat 配合虛擬線程,一種新的編程體驗

下一篇: 純CSS實現炫酷背景霓虹燈文字效果

標簽:
  • 熱門焦點
Top 主站蜘蛛池模板: 朝阳市| 岢岚县| 南澳县| 梁河县| 新泰市| 紫云| 宕昌县| 长海县| 隆德县| 武城县| 太仆寺旗| 合水县| 莆田市| 同德县| 沁水县| 赤水市| 根河市| 宜都市| 绿春县| 棋牌| 苏州市| 南充市| 元氏县| 浦东新区| 白河县| 平遥县| 楚雄市| 天等县| 澄迈县| 当涂县| 铁力市| 长海县| 三亚市| 大同县| 古浪县| 晋江市| 鹿邑县| 西城区| 周宁县| 仁怀市| 同仁县|