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