7. 自動裝配源碼分析
終于來到了大家喜聞樂見的部分:源碼分析
在我們前面6節學習了各種”招式“之后,讓我們請出對手:SpringBoot
現在在你面前的是一個SpringBoot”空項目“,沒有添加任何依賴包和starter包
啟動項目:
正常啟動,讓我們從@SpringBootApplication開始研究
7.1 @SpringBootConfiguration
會看到@SpringBootApplication這個注解由好多注解組成
主要的有以下三個:
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
先來看第一個:@SpringBootConfiguration
進入這個注解之后會發現
原來你就是一個@Configuration啊,一個JavaConfig配置類
那我們使用JavaConfig不就是用來配置bean嗎,所以有了這個注解之后我們可以在SpringBoot運行的主類中使用@Bean標簽配置類了,如下圖所示:
7.2 @ComponentScan
這個注解相信大家都認識了,組件掃描
這個掃描的范圍是:SpringBoot主啟動類的同級路徑及子路徑
7.3 @EnableAutoConfiguration
來看這個注解,也是最核心的內容
這個注解怎么這么眼熟啊,還記得剛才的@MyEnableAutoConfig注解嗎?就是我們自己寫的那個注解
進入@EnableAutoConfiguration:
看圖中紅圈位置的注解:@Import(AutoConfigurationImportSelector.class)
是不是跟我們上面自己寫的內容一樣!
這里的作用便是導入了 AutoConfigurationImportSelector 這個類的bean定義
我們都知道,如果這個類實現了ImportSelector接口,那他肯定重寫了一個方法,就是我們上面重寫過的selectImports方法:
果然,在這個類里面確實有這個selectImports方法:
我的天,好長的一串代碼,一行都放不下!
此時此刻,我又回想起了在家鄉的母親,夏天的蟬鳴,池塘的荷花…
等等等等,這個類我們當時返回的是什么?是一個字符串數組String[ ],那這個類無論多么長,返回的肯定就是一個字符串數組,不信你自己看:
這個字符串數組存放的內容我們是否清楚呢?當然清楚了!我們返回的是要加載的Config配置文件的全包名,通過返回這個全包名,我們就能自動裝配上這些配置文件下定義的bean對象,從而達到了自動裝配的目的!
根據剛才我們自己實現的selectImports方法,我們是通過注解類的名字來查找,并且最終得到需要加載的Config類的全類名,最后返回的。
因此,這里必然有一個根據注解類名字來查找相應的Config文件的操作
我們繼續反推,看到返回時的定義如下:
我們發現autoConfigurationEntry中保存著我們需要的配置信息,它是通過getAutoConfigurationEntry方法獲取的,于是我們繼續深入,進入getAutoConfigurationEntry方法
這一段代碼真是把人難住了,好大一片,不知道在做什么
此時此刻,我又回想起了在家鄉的母親,夏天的蟬鳴,池塘的荷花…
回家!有了!我們先想這個方法應該返回什么,根據我們前面的經驗,這里應該返回一個類似于Entry的保存了我們需要的配置信息的對象
這個方法返回的是新建的AutoConfigurationEntry對象,根據最后一行的構造函數來看,給他了兩個參數:
configurations, exclusions
configurations顯然使我們需要的配置文件,也是我們最關心的,而exclusions字面意思是排除,也就是不需要的,那我們接下來應該關注configurations到底是怎么來的
根據我們前面的經驗,我們是根據注解類名來從一個配置文件中讀取出我們需要的Config配置類,這里configurations就代表了Config配置類,那么我們應該找到一個入口,這個入口跟注解相關,并且返回了configurations這個參數。
正如我們所料,這個方法的參數確實傳遞過來了一個東西,跟注解有關:
看見那個大大的Annotation(注解)了嗎!
那么根據這條”線索“,我們按圖索驥,找到了三行代碼,范圍進一步縮小了!
此時再加上返回了configurations,我們最終確定了一行代碼:
就是這個getCandidateConfigurations方法,符合我們的要求!
從字面意思上分析,獲取候選的配置,確實是我們需要的方法
OK,讓我們繼續前進,進入這個方法:
這個方法是不是也似曾相識呢?我們之前寫過一個專門用于讀取配置文件的類MyPropertyReader,還記得嗎?
如果你還記得的話,我們自己寫的工具類里面也是一個靜態方法readPropertyForMe來幫我讀取配置文件
但是我們的配置文件路徑一定是需要指定的,不能亂放。
從這個loadFactoryNames方法體來看,好像沒有給他傳遞一個具體路徑
但是從下面的Assert斷言中,我們發現了玄機:
在META-INF/spring.factories文件中沒有找到自動配置類Config,你要檢查balabala。。。。
根據我不太靈光的腦袋的判斷,他的這個配置文件就叫spring.factories,存放的路徑是META-INF/spring.factories
于是我們打開spring boot自動裝配的依賴jar包:
那這個配置文件里面的內容,是不是跟我們想的一樣呢?
原來如此。
這里的EnableAutoConfiguration注解,正是我們此行的起點啊…
到這里,自動裝配到底是什么,應該比較清楚了,原來他是幫我們加載了各種已經寫好的Config類文件,實現了這些JavaConfig配置文件的重復利用和組件化
7.4 loadFactoryNames方法
行程不能到此結束,學習不能淺嘗輒止。
我們還有最后一塊(幾塊)面紗沒有解開,現在還不能善罷甘休。
讓我們進入loadFactoryNames方法:
這個方法非常簡短,因為他調用了真正實現的方法:loadSpringFactories
這一行return代碼我復制在下面:
loadSpringFactories(classLoader)
.getOrDefault(factoryTypeName, Collections.emptyList());
可以分析得出:loadSpringFactories方法的返回值又調用了一個getOrDefault方法,這明顯是一個容器類的方法,目的是從容器中拿點東西出來
就此推測:loadSpringFactories返回了一個包含我們需要的Config全類名(字符串)的集合容器,然后從這個集合容器中拿出來的東西就是我們的configurations
讓我們看這個loadSpringFactories方法:
它確實返回了一個容器:Map 這個容器的類型是:MultiValueMap
這個數據結構就非常牛逼了,多值集合映射(我自己的翻譯)簡單來說,一個key可以對應多個value,根據他的返回值,我們可以看到在這個方法中一個String對應了一個List
那么不難想到MultiValueMap中存放的形式:是”注解的類名——多個Config配置類“ 讓我們打個斷點來驗證一下:
果然是這樣,并且@EnableAutoConfiguration注解竟然加載了多達124個配置類!
接下來我們繼續思考:我們來的目的是獲取configurations,所以無論你做什么,必須得讀取配置文件,拿到configurations
于是我們在try方法體中果然發現了這個操作:
他獲取了一個路徑urls,那么這個路徑是否就是我們前面驗證的META-INF/spring.factories呢?
我們查看靜態常量FACTORIES_RESOURCE_LOCATION的值:
果真如此,bingo!繼續往下看,果然他遍歷了urls中的內容,從這個路徑加載了配置文件:終于看到了我們熟悉的loadProperties方法!
那我們大概就知道了,他確實是通過找到路徑,然后根據路徑讀取了配置文件,然后返回了讀取的result
這就是loadFactoryNames方法的內部實現。
7.5 cache探秘
到這里有的人又要問了:是不是結束了?其實還遠沒有!
細心地朋友已經發現了玄機,隱藏在loadFactoryNames方法的開頭和結尾:
喂喂,這個返回的result好像并不是直接new出來的哦
它是從cache緩存中取出來的,你發現了沒有
根據下面的if判斷,如果從緩存中讀取出來了result,并且result的結果不為空,就直接返回,不需要再進行下面的讀寫操作了,這樣減少了磁盤頻繁的讀寫I/O
同理,在我更新完所有的配置文件資源之后,退出時也要更新緩存。
7.6 getAutoConfigurationEntry再探
關鍵部分已經過去,讓我們反過頭來重新審視一下遺漏的內容:
還記得getAutoConfigurationEntry方法嗎?
我們最后來研究一下這個類除了getCandidateConfigurations還干了哪些事情:
- removeDuplicates
- configurations.removeAll(exclusions)
可以看到,這里對加載進來的配置進行了去重、排除的操作,這是為了使得用戶自定義的排除包生效,同時避免包沖突異常,在SpringBoot的入口函數中我們可以通過注解指定需要排除哪些不用的包:
例如我不使用RabbitMQ的配置包,就把它的配置類的class傳給exclude
@SpringBootApplication(exclude = {RabbitAutoConfiguration.class})
8. 自動裝配本質
我的理解:
- SpringBoot自動裝配的本質就是通過Spring去讀取META-INF/spring.factories中保存的配置類文件然后加載bean定義的過程。
- 如果是標了@Configuration注解,就是批量加載了里面的bean定義
- 如何實現”自動“:通過配置文件獲取對應的批量配置類,然后通過配置類批量加載bean定義,只要有寫好的配置文件spring.factories就實現了自動。
9. 總結
Spring Boot的自動裝配特性可以說是Spring Boot最重要、最核心的一環,正是因為這個特性,使得我們的生產復雜性大大降低,極大地簡化了開發流程,可以說是給我們帶來了巨大的福音了~~
筆者本人對源碼的理解仍然沒有那么深刻,只是喜歡分享自己的一些學習經驗,希望能和大家共同學習,畢竟掌握一門新技術的快感嘛… 大家都懂的!
寫這篇文章耗費了巨大的精力,每一個字均是手碼,真的希望喜歡的朋友可以點贊收藏關注支持一波,這就是對我這個未出世的學生的最大激勵了!
-
spring
+關注
關注
0文章
338瀏覽量
14312 -
源碼分析
+關注
關注
0文章
5瀏覽量
5541 -
自動裝配
+關注
關注
0文章
7瀏覽量
646
發布評論請先 登錄
相關推薦
評論