您好,登錄后才能下訂單哦!
這篇文章主要為大家展示了“Spring配置文件加載方式變化引發的異常問題”,內容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領大家一起研究并學習一下“Spring配置文件加載方式變化引發的異常問題”這篇文章吧。
我們項目的配置文件一直是通過Apollo進行管理,但是近期由于某些特殊的部署需求,需要使用K8S的原生對象來獲取配置,如此一來的話,就需要使用環境變量spring.config.location來指定application.properties
文件的路徑,以便動態的獲取配置。
說明:項目是一個dubbo項目,配置文件中主要包括一些基礎組件的配置、以及dubbo相關的配置。
這時候問題來了,在所有配置及代碼都沒有變化的情況下,如果不指定環境變量使用本地的application.properties
,則沒有異常任何,項目可以正常啟動,但是一但通過spring.config.location 來加載配置,則項目會直接啟動失敗,并報如下異常:
NoSuchBeanDefinitionException,這個異常前期誤導我不少時間,它一般是Spring在容器初始化時,進行依賴注入的時候沒有找到對應的bean定義,也就意味著這個bean壓根沒有被注冊到BeanFactory中,這就很奇怪,只是配置文件的加載方式不同,為何會影響到bean的注冊?
找不到bean,最常見的問題有兩種:要么是配置問題,比如掃描的包配置錯誤、配置未生效等。要么就是IoC容器的問題,存在多個容器,導致bean隔離。
在這個問題場景下,兩種原因都有可能,不過問題可以復現,就比較好解決。我們直接驗證一下,最簡單粗暴的法子就是斷點伺候,對比兩種配置加載方式方式的差異。我們知道除了延遲加載的bean之外,所有bean都是在org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons
初始化的,那么在這個方法里以異常信息中的bean名稱,打個條件斷點看看
通過斷點,可以拿到兩個信息
1、通過當前的堆棧,可以看到當前的初始化bean邏輯并不是SpringBoot的IoC容器觸發的,而是SpringCloud
2、beanNames,也就是當前beanFactory中所有已注冊的bean中,沒有加載到任何通過Spring的@Service注解標識的bean,但是卻加載到了所有被Dubbo的@Service加載到的bean。
這樣一來我們可以確定的是確實存在多容器隔離,SpringCloud也會通過BootstrapApplicationListener這個監聽器創建一個IoC容器,查看官方說明:
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創建的容器的加載順序比SpringBoot要早,是它的父容器,并且它們共享同一個Environment。
雖然現在知道了異常產生的原因,但是為什么換了配置加載方式就會由父容器加載?根據上面的第二個信息,被Dubbo注解標識的bean都被加載了,但是這些bean依賴的SpringBean還沒有加載進來,這意味著由于配置文件加載方式的變化,導致Dubbo標記的bean加載時機發生的改變。
那接下來就是看一下Dubbo的bean加載邏輯,我們的服務比較老了,使用的spring-boot-starter-dubbo來整合SpringBoot與Dubbo。一般spring-boot-starter都是通過@EnableXXX或spring.factories來自動裝載相關的bean,而spring-boot-starter-dubbo沒有使用@Enable,那直接找到jar包下的spring.factories
文件,找到對應的Initializer:
它實現的是ApplicationContextInitiailizer,這些接口會在準備完Context環境,在prepareContext中調用,那么很明顯,父容器肯定先會執行,子容器后執行。 看代碼邏輯,它只有在讀取到spring.dubbo.scan有值時,才會去注冊bean,到這里原因已經比較明顯了,使用application.properties時父容器讀不到配置,而使用spring.config.location加載配置時,父容器可以讀到配置。
上面提到的SpringCloud文檔中有這么一句話:
By default, bootstrap properties (not bootstrap.properties but properties that are loaded during the bootstrap phase) are added with high precedence
那么通過spring.config.location方式加載屬性是不是在bootstrap phase中呢,直接找到SpringCloud的加載類BootstrapApplicationListener,搜索spring.config.location,發現它確實優先加載
而如果是使用application.properties,那么配置文件則不會被SpringCloud加載到,會由子容器加載。
問題根因找到了,想解決就比較簡單了,兩種方式:
直接關閉SpringCloud的boostrap listener,通過配置spring.cloud.bootstrap.enabled=false
即可
這個問題其實也是dubbo的整合方式不合理導致的,使用Dubbo自帶的注解掃描,不使用配置文件的方式
以上是“Spring配置文件加載方式變化引發的異常問題”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。