關(guān)于Spring配置文件加載方式變化引發(fā)的異常詳解
問(wèn)題背景
我們項(xiàng)目的配置文件一直是通過(guò)Apollo進(jìn)行管理,但是近期由于某些特殊的部署需求,需要使用K8S的原生對(duì)象來(lái)獲取配置,如此一來(lái)的話,就需要使用環(huán)境變量spring.config.location來(lái)指定application.properties文件的路徑,以便動(dòng)態(tài)的獲取配置。
說(shuō)明:項(xiàng)目是一個(gè)dubbo項(xiàng)目,配置文件中主要包括一些基礎(chǔ)組件的配置、以及dubbo相關(guān)的配置。
這時(shí)候問(wèn)題來(lái)了,在所有配置及代碼都沒(méi)有變化的情況下,如果不指定環(huán)境變量使用本地的application.properties,則沒(méi)有異常任何,項(xiàng)目可以正常啟動(dòng),但是一但通過(guò)spring.config.location 來(lái)加載配置,則項(xiàng)目會(huì)直接啟動(dòng)失敗,并報(bào)如下異常:

NoSuchBeanDefinitionException,這個(gè)異常前期誤導(dǎo)我不少時(shí)間,它一般是Spring在容器初始化時(shí),進(jìn)行依賴注入的時(shí)候沒(méi)有找到對(duì)應(yīng)的bean定義,也就意味著這個(gè)bean壓根沒(méi)有被注冊(cè)到BeanFactory中,這就很奇怪,只是配置文件的加載方式不同,為何會(huì)影響到bean的注冊(cè)?
過(guò)程
找不到bean,最常見(jiàn)的問(wèn)題有兩種:要么是配置問(wèn)題,比如掃描的包配置錯(cuò)誤、配置未生效等。要么就是IoC容器的問(wèn)題,存在多個(gè)容器,導(dǎo)致bean隔離。
定位
在這個(gè)問(wèn)題場(chǎng)景下,兩種原因都有可能,不過(guò)問(wèn)題可以復(fù)現(xiàn),就比較好解決。我們直接驗(yàn)證一下,最簡(jiǎn)單粗暴的法子就是斷點(diǎn)伺候,對(duì)比兩種配置加載方式方式的差異。我們知道除了延遲加載的bean之外,所有bean都是在org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons初始化的,那么在這個(gè)方法里以異常信息中的bean名稱,打個(gè)條件斷點(diǎn)看看

通過(guò)斷點(diǎn),可以拿到兩個(gè)信息
1、通過(guò)當(dāng)前的堆棧,可以看到當(dāng)前的初始化bean邏輯并不是SpringBoot的IoC容器觸發(fā)的,而是SpringCloud

2、beanNames,也就是當(dāng)前beanFactory中所有已注冊(cè)的bean中,沒(méi)有加載到任何通過(guò)Spring的@Service注解標(biāo)識(shí)的bean,但是卻加載到了所有被Dubbo的@Service加載到的bean。
這樣一來(lái)我們可以確定的是確實(shí)存在多容器隔離,SpringCloud也會(huì)通過(guò)BootstrapApplicationListener這個(gè)監(jiān)聽(tīng)器創(chuàng)建一個(gè)IoC容器,查看官方說(shuō)明:
A Spring Cloud application operates by creating a “bootstrap” context, which is a parent context for the main application. It is responsible for loading configuration properties from the external sources and for decrypting properties in the local external configuration files. The two contexts share an Environment, which is the source of external properties for any Spring application. By default, bootstrap properties (not bootstrap.properties but properties that are loaded during the bootstrap phase) are added with high precedence, so they cannot be overridden by local configuration.
SpringCloud創(chuàng)建的容器的加載順序比SpringBoot要早,是它的父容器,并且它們共享同一個(gè)Environment。
雖然現(xiàn)在知道了異常產(chǎn)生的原因,但是為什么換了配置加載方式就會(huì)由父容器加載?根據(jù)上面的第二個(gè)信息,被Dubbo注解標(biāo)識(shí)的bean都被加載了,但是這些bean依賴的SpringBean還沒(méi)有加載進(jìn)來(lái),這意味著由于配置文件加載方式的變化,導(dǎo)致Dubbo標(biāo)記的bean加載時(shí)機(jī)發(fā)生的改變。
根因
那接下來(lái)就是看一下Dubbo的bean加載邏輯,我們的服務(wù)比較老了,使用的spring-boot-starter-dubbo來(lái)整合SpringBoot與Dubbo。一般spring-boot-starter都是通過(guò)@EnableXXX或spring.factories來(lái)自動(dòng)裝載相關(guān)的bean,而spring-boot-starter-dubbo沒(méi)有使用@Enable,那直接找到j(luò)ar包下的spring.factories文件,找到對(duì)應(yīng)的Initializer:

它實(shí)現(xiàn)的是ApplicationContextInitiailizer,這些接口會(huì)在準(zhǔn)備完Context環(huán)境,在prepareContext中調(diào)用,那么很明顯,父容器肯定先會(huì)執(zhí)行,子容器后執(zhí)行。 看代碼邏輯,它只有在讀取到spring.dubbo.scan有值時(shí),才會(huì)去注冊(cè)bean,到這里原因已經(jīng)比較明顯了,使用application.properties時(shí)父容器讀不到配置,而使用spring.config.location加載配置時(shí),父容器可以讀到配置。
配置加載順序
上面提到的SpringCloud文檔中有這么一句話:
By default, bootstrap properties (not bootstrap.properties but properties that are loaded during the bootstrap phase) are added with high precedence
那么通過(guò)spring.config.location方式加載屬性是不是在bootstrap phase中呢,直接找到SpringCloud的加載類(lèi)BootstrapApplicationListener,搜索spring.config.location,發(fā)現(xiàn)它確實(shí)優(yōu)先加載

而如果是使用application.properties,那么配置文件則不會(huì)被SpringCloud加載到,會(huì)由子容器加載。
解決
問(wèn)題根因找到了,想解決就比較簡(jiǎn)單了,兩種方式:
- 直接關(guān)閉SpringCloud的boostrap listener,通過(guò)配置
spring.cloud.bootstrap.enabled=false即可 - 這個(gè)問(wèn)題其實(shí)也是dubbo的整合方式不合理導(dǎo)致的,使用Dubbo自帶的注解掃描,不使用配置文件的方式
問(wèn)題其實(shí)比較簡(jiǎn)單,但是挺有意思,分享一下過(guò)程與思路*~*
到此這篇關(guān)于Spring配置文件加載方式變化引發(fā)的異常的文章就介紹到這了,更多相關(guān)Spring配置文件加載方式內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
zuul轉(zhuǎn)發(fā)后服務(wù)取不到請(qǐng)求路徑的解決
這篇文章主要介紹了zuul轉(zhuǎn)發(fā)后服務(wù)取不到請(qǐng)求路徑的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07
AsyncHttpClient?ChannelPool線程池頻道池源碼流程解析
這篇文章主要為大家介紹了AsyncHttpClient ChannelPool線程池頻道池源碼流程解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12
SpringBoot框架aop切面的execution表達(dá)式解讀
這篇文章主要介紹了SpringBoot框架aop切面的execution表達(dá)式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05
Java語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單FTP軟件 FTP連接管理模塊實(shí)現(xiàn)(8)
這篇文章主要為大家詳細(xì)介紹了Java語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單FTP軟件,F(xiàn)TP連接管理模塊的實(shí)現(xiàn)方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04
Spring高級(jí)之注解@PropertySource的原理
這篇文章主要介紹了Spring高級(jí)之注解@PropertySource的原理,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03

