你知道Spring如何解決所有循環(huán)依賴的嗎
以下內(nèi)容基于 Spring6.0.4。
看了上篇文章的小伙伴,對(duì)于 Spring 解決循環(huán)依賴的思路應(yīng)該有一個(gè)大致了解了,今天我們?cè)賮?lái)看一看,按照上篇文章介紹的思路,有哪些循環(huán)依賴 Spring 處理不了。
嚴(yán)格來(lái)說(shuō),其實(shí)也不是解決不了,所有問(wèn)題都有辦法解決,只是還需要額外配置,這個(gè)不是本文的主題,松哥后面再整文章和小伙伴們細(xì)聊。
1. 基于構(gòu)造器注入
如果依賴的對(duì)象是基于構(gòu)造器注入的,那么執(zhí)行的時(shí)候就會(huì)報(bào)錯(cuò),代碼如下:
@Service public?class?AService?{ ????BService?bService; ????public?AService(BService?bService)?{ ????????this.bService?=?bService; ????} } @Service public?class?BService?{ ????AService?aService; ????public?BService(AService?aService)?{ ????????this.aService?=?aService; ????} }
運(yùn)行時(shí)報(bào)錯(cuò)如下:
原因分析:
上篇文章我們說(shuō)解決循環(huán)依賴的思路是加入緩存,如下圖:
我們說(shuō)先把 AService 原始對(duì)象創(chuàng)建出來(lái),存入到緩存池中,然后再處理 AService 中需要注入的外部 Bean 等等,但是,如果 AService 依賴的 BService 是通過(guò)構(gòu)造器注入的,那就會(huì)導(dǎo)致在創(chuàng)建 AService 原始對(duì)象的時(shí)候就需要用到 BService,去創(chuàng)建 BService 時(shí)候又需要 AService,這樣就陷入到死循環(huán)了,對(duì)于這樣的循環(huán)依賴執(zhí)行時(shí)候就會(huì)出錯(cuò)。
更進(jìn)一步,如果我們?cè)?AService 中是通過(guò) @Autowired 來(lái)注入 BService 的,那么應(yīng)該是可以運(yùn)行的,代碼如下:
@Service public?class?AService?{ ????@Autowired ????BService?bService; } @Service public?class?BService?{ ????AService?aService; ????public?BService(AService?aService)?{ ????????this.aService?=?aService; ????} }
上面這段代碼,AService 的原始對(duì)象就可以順利創(chuàng)建出來(lái)放到緩存池中,BService 創(chuàng)建所需的 AService 也就能從緩存中獲取到,所以就可以執(zhí)行了。
2. prototype 對(duì)象
循環(huán)依賴雙方 scope 都是 prototype 的話,也會(huì)循環(huán)依賴失敗,代碼如下:
@Service @Scope("prototype") public?class?AService?{ ????@Autowired ????BService?bService; } @Service @Scope("prototype") public?class?BService?{ ????@Autowired ????AService?aService; }
這種循環(huán)依賴運(yùn)行時(shí)也會(huì)報(bào)錯(cuò),報(bào)錯(cuò)信息如下(跟前面報(bào)錯(cuò)信息一樣):
原因分析:
scope 為 prototype 意思就是說(shuō)這個(gè) Bean 每次需要的時(shí)候都現(xiàn)場(chǎng)創(chuàng)建,不用緩存里的。那么 AService 需要 BService,所以就去現(xiàn)場(chǎng)創(chuàng)建 BService,結(jié)果 BService 又需要 AService,繼續(xù)現(xiàn)場(chǎng)創(chuàng)建,AService 又需要 BService...,所以最終就陷入到死循環(huán)了。
3. @Async
帶有 @Async 注解的 Bean 產(chǎn)生循環(huán)依賴,代碼如下:
@Service public?class?AService?{ ????@Autowired ????BService?bService; ????@Async ????public?void?hello()?{ ????} } @Service public?class?BService?{ ????@Autowired ????AService?aService; }
報(bào)錯(cuò)信息如下:
其實(shí)大家從這段報(bào)錯(cuò)信息中也能看出來(lái)個(gè)七七八八:在 BService 中注入了 AService 的原始對(duì)象,但是 AService 在后續(xù)的處理流程中被 AOP 代理了,產(chǎn)生了新的對(duì)象,導(dǎo)致 BService 中的 AService 并不是最終的 AService,所以就出錯(cuò)了!
那有小伙伴要問(wèn)了,上篇文章我們不是說(shuō)了三級(jí)緩存就是為了解決 AOP 問(wèn)題嗎,為什么這里發(fā)生了 AOP 卻無(wú)法解決?
如下兩個(gè)前置知識(shí)大家先理解一下:
第一:
其實(shí)大部分的 AOP 循環(huán)依賴是沒(méi)有問(wèn)題的,這個(gè) @Async 只是一個(gè)特例,特別在哪里呢?一般的 AOP 都是由 AbstractAutoProxyCreator 這個(gè)后置處理器來(lái)處理的,通過(guò)這個(gè)后置處理器生成代理對(duì)象,AbstractAutoProxyCreator 后置處理器是 SmartInstantiationAwareBeanPostProcessor 接口的子類,并且 AbstractAutoProxyCreator 后置處理器重寫了 SmartInstantiationAwareBeanPostProcessor 接口的 getEarlyBeanReference 方法;而 @Async 是由 AsyncAnnotationBeanPostProcessor 來(lái)生成代理對(duì)象的,AsyncAnnotationBeanPostProcessor 也是 SmartInstantiationAwareBeanPostProcessor 的子類,但是卻沒(méi)有重寫 getEarlyBeanReference 方法,默認(rèn)情況下,getEarlyBeanReference 方法就是將傳進(jìn)來(lái)的 Bean 原封不動(dòng)的返回去。
第二:
在 Bean 初始化的時(shí)候,Bean 創(chuàng)建完成后,后面會(huì)執(zhí)行兩個(gè)方法:
- populateBean:這個(gè)方法是用來(lái)做屬性填充的。
- initializeBean:這個(gè)方法是用來(lái)初始化 Bean 的實(shí)例,執(zhí)行工廠回調(diào)、init 方法以及各種 BeanPostProcessor。
大家先把這兩點(diǎn)搞清楚,然后我來(lái)跟大家說(shuō)上面代碼的執(zhí)行流程。
- 首先 AService 初始化,初始化完成之后,存入到三級(jí)緩存中。
- 執(zhí)行 populateBean 方法進(jìn)行 AService 的屬性填充,填充時(shí)發(fā)現(xiàn)需要用到 BService,于是就去初始化 BService。
- 初始化 BService 發(fā)現(xiàn)需要用到 AService,于是就去緩存池中找,找到之后拿來(lái)用,但是?。?!這里找到的 AService 不是代理對(duì)象,而是原始對(duì)象。因?yàn)樵谌?jí)緩存中保存的 AService 的那個(gè) ObjectFactory 工廠,在對(duì) AService 進(jìn)行提前 AOP 的時(shí)候,執(zhí)行的是 SmartInstantiationAwareBeanPostProcessor 類型的后置處理器 中的 getEarlyBeanReference 方法,如果是普通的 AOP,調(diào)用 getEarlyBeanReference 方法最終會(huì)觸發(fā)提前 AOP,但是,這里執(zhí)行的是 AsyncAnnotationBeanPostProcessor 中的 getEarlyBeanReference 方法,該方法只是返回了原始的 Bean,并未做任何額外處理。
- 當(dāng) BService 創(chuàng)建完成后,AService 繼續(xù)初始化,繼續(xù)執(zhí)行 initializeBean 方法。
- 在 initializeBean 方法中,執(zhí)行其他的各種后置處理器,包括 AsyncAnnotationBeanPostProcessor,此時(shí)調(diào)用的是 AsyncAnnotationBeanPostProcessor 的 postProcessAfterInitialization 方法,在該方法中為 AService 生成了代理對(duì)象。
- 在 initializeBean 方法執(zhí)行完成之后,AService 會(huì)繼續(xù)去檢查最終的 Bean 是不是還是一開始的 Bean,如果不是,就去檢查當(dāng)前 Bean 有沒(méi)有被其他 Bean 引用過(guò),如果被引用過(guò),就會(huì)拋出來(lái)異常,也就是上圖大家看到的異常信息。
到此這篇關(guān)于你知道Spring如何解決所有循環(huán)依賴的嗎的文章就介紹到這了,更多相關(guān)Spring解決循環(huán)依賴內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決java頁(yè)面URL地址傳輸參數(shù)亂碼的方法
這篇文章主要介紹了解決java頁(yè)面URL地址傳輸參數(shù)亂碼的方法,URL地址參數(shù)亂碼問(wèn)題,算是老話重談了吧!需要的朋友可以參考下2015-09-09IDEA之web項(xiàng)目導(dǎo)入jar包方式
這篇文章主要介紹了IDEA之web項(xiàng)目導(dǎo)入jar包方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05Java數(shù)組隊(duì)列概念與用法實(shí)例分析
這篇文章主要介紹了Java數(shù)組隊(duì)列概念與用法,結(jié)合實(shí)例形式分析了Java數(shù)組隊(duì)列相關(guān)概念、原理、用法及操作注意事項(xiàng),需要的朋友可以參考下2020-03-03maven-assembly-plugin報(bào)紅無(wú)法加載報(bào)錯(cuò):Plugin?‘maven-assembly-plugin
maven-assembly-plugin是一個(gè)常用的打包插件,但是在使用過(guò)程中經(jīng)常會(huì)遇到各種報(bào)錯(cuò),本文就來(lái)介紹一下maven-assembly-plugin報(bào)紅無(wú)法加載報(bào)錯(cuò),具有一定的參考價(jià)值2023-08-08Java文件分級(jí)目錄打包下載zip的實(shí)例代碼
這篇文章主要介紹了Java文件分級(jí)目錄打包下載zip的實(shí)例代碼,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08Java調(diào)用構(gòu)造函數(shù)和方法及使用詳解
在Java編程中,構(gòu)造函數(shù)用于初始化新創(chuàng)建的對(duì)象,而方法則用于執(zhí)行對(duì)象的行為,構(gòu)造函數(shù)在使用new關(guān)鍵字創(chuàng)建類實(shí)例時(shí)自動(dòng)調(diào)用,沒(méi)有返回類型,并且名稱與類名相同,本文通過(guò)示例詳細(xì)介紹了如何在Java中使用構(gòu)造函數(shù)和方法,感興趣的朋友一起看看吧2024-10-10SpringBoot自定義注解及AOP的開發(fā)和使用詳解
在公司項(xiàng)目中,如果需要做一些公共的功能,如日志等,最好的方式是使用自定義注解,自定義注解可以實(shí)現(xiàn)我們對(duì)想要添加日志的方法上添加,這篇文章基于日志功能來(lái)講講自定義注解應(yīng)該如何開發(fā)和使用,需要的朋友可以參考下2023-08-08在SpringBoot中實(shí)現(xiàn)斷點(diǎn)續(xù)傳的實(shí)例代碼
在 Spring Boot 或任何其他 web 開發(fā)框架中,斷點(diǎn)續(xù)傳是一種技術(shù),允許文件的傳輸在中斷后可以從中斷點(diǎn)重新開始,而不是從頭開始,種技術(shù)在處理大文件或在不穩(wěn)定的網(wǎng)絡(luò)環(huán)境中尤為重要,本文給大家介紹了SpringBoot中實(shí)現(xiàn)斷點(diǎn)續(xù)傳的實(shí)例代碼,需要的朋友可以參考下2024-07-07