前言學(xué)習(xí)SpringBoot,絕對(duì)避不開(kāi)自動(dòng)裝配這個(gè)概念,這也是SpringBoot的關(guān)鍵之一 本人也是SpringBoot的初學(xué)者,下面的一些總結(jié)都是結(jié)合個(gè)人理解和實(shí)踐得出的,如果有錯(cuò)誤或者疏漏,請(qǐng)一定一定一定(不是歡迎,是一定)幫我指出,在評(píng)論區(qū)回復(fù)即可,一起學(xué)習(xí)! 篇幅較長(zhǎng),希望你可以有耐心. 如果只關(guān)心SpringBoot裝配過(guò)程,可以直接跳到第7部分 想要理解spring自動(dòng)裝配,需要明確兩個(gè)含義:
1. Warm up在開(kāi)始之前,讓我們先來(lái)看點(diǎn)簡(jiǎn)單的開(kāi)胃菜:spring中bean注入的三種形式 首先我們先來(lái)一個(gè)Person類,這里為了篇幅長(zhǎng)度考慮使用了lombok 如果你不知道lombok是什么,那就最好不要知道,加了幾個(gè)注解之后我的pojo類Person就完成了
在Spring中(不是Spring Boot),要實(shí)現(xiàn)bean的注入,我們有3種注入方式: 1.1 setter注入這是最基本的注入方式 首先我們創(chuàng)建applicationContext.xml文件,在里面加入:
這里使用property為bean對(duì)象賦值 緊接著我們會(huì)在test包下寫(xiě)一個(gè)version1.TestVersion1類
這里我使用了ClassPathXmlApplicationContext來(lái)加載spring配置文件并且讀取其中定義的bean,然后使用getBean方法使用id和類來(lái)獲取這個(gè)Person的Bean對(duì)象,結(jié)果成功輸出:
1.2 構(gòu)造器注入接下來(lái)是使用構(gòu)造器注入,我們需要更改applicationContext.xml文件中的property為construct-arg
version2.TestVersion2內(nèi)容不變:
依然正常輸出結(jié)果:
1.3 屬性注入使用注解方式的屬性注入Bean是比較優(yōu)雅的做法 首先我們需要在applicationContext.xml中開(kāi)啟注解支持和自動(dòng)包掃描:
在pojo類中對(duì)Person類加上@Component注解,將其標(biāo)記為組件,并且使用@Value注解為各屬性賦初值
然后添加新的測(cè)試類version3.TestVersion3
運(yùn)行也可以得到如下結(jié)果:
2. Warm up again什么?還有什么?接下來(lái)我們來(lái)聊聊Spring的兩種配置方式:基于XML的配置和基于JavaConfig類的配置方式,這對(duì)于理解SpringBoot的自動(dòng)裝配原理是非常重要的。 首先我們?cè)赑erson的基礎(chǔ)上再創(chuàng)建幾個(gè)pojo類:這個(gè)Person有Car、有Dog
2.1 基于XML的配置接下來(lái)讓我們嘗試使用XML的配置方式來(lái)為一個(gè)Person注入
然后跟普通的Bean注入一樣,使用ClassPathXmlApplicationContext來(lái)加載配置文件,然后獲取Bean
輸出結(jié)果如下:
2.2 基于JavaConfig類的配置想要成為JavaConfig類,需要使用@Configuration注解 我們新建一個(gè)包命名為config,在config中新增一個(gè)PersonConfig類
此時(shí)我們的XML配置文件可以完全為空了,此時(shí)應(yīng)該使用AnnotationConfigApplicationContext來(lái)獲取注解配置
仍然正常輸出了結(jié)果:
3. BeanDefinition
是spring中所有bean的抽象定義對(duì)象,我把他叫做bean定義 當(dāng)bean.class被JVM類加載到內(nèi)存中時(shí),會(huì)被spring掃描到一個(gè)map容器中:
這個(gè)容器存儲(chǔ)了bean定義,但是bean此時(shí)還沒(méi)有進(jìn)行實(shí)例化,在進(jìn)行實(shí)例化之前,還有一個(gè)
可以對(duì)bean對(duì)象進(jìn)行一些自定義處理 我們打開(kāi)BeanFactoryProcessor這個(gè)接口的源碼可以發(fā)現(xiàn)如下內(nèi)容:
在spring完成標(biāo)準(zhǔn)的初始化過(guò)程后,實(shí)現(xiàn)BeanFactoryPostProcessor接口的對(duì)象可以用于定制bean factory,所有的bean definition都會(huì)被加載,但是此時(shí)還沒(méi)有被實(shí)例化。這個(gè)接口允許對(duì)一些bean定義做出屬性上的改動(dòng)。 簡(jiǎn)言之就是實(shí)現(xiàn)了BeanFactoryPostProcessor這個(gè)接口的類,可以在bean實(shí)例化之前完成一些對(duì)bean的改動(dòng)。 大致流程我畫(huà)了個(gè)圖: ![]() 至此我們能總結(jié)出springIOC容器的本質(zhì):(我的理解) 由BeanDefinitionMap、BeanFactoryPostProcessor、BeanPostProcessor、BeanMap等等容器共同組成、共同完成、提供依賴注入和控制反轉(zhuǎn)功能的一組集合,叫IOC容器。 4. BeanDefinition結(jié)構(gòu)既然講到了BeanDefinition,我們來(lái)看一下BeanDefinition里面究竟定義了些什么 讓我們點(diǎn)進(jìn)AbstractBeanDefinition這個(gè)類,一探究竟: ![]() 哇!好多成員變量,整個(gè)人都要看暈了@_@ 我們來(lái)重點(diǎn)關(guān)注以下三個(gè)成員:
4.1 beanClass這個(gè)屬性決定了該Bean定義的真正class到底是誰(shuí),接下來(lái)我們來(lái)做點(diǎn)實(shí)驗(yàn) 我們定義兩個(gè)Bean類,A和B
接下來(lái)我們實(shí)現(xiàn)上面的BeanFactoryPostProcessor接口,來(lái)創(chuàng)建一個(gè)自定義的bean后置處理器
最后在XML配置文件中開(kāi)啟包掃描
注意:這里不要使用JavaConfig類來(lái)配置bean,不然會(huì)報(bào)如下錯(cuò)誤
這個(gè)錯(cuò)誤出自這一句:
最后,我們創(chuàng)建一個(gè)測(cè)試類:
測(cè)試運(yùn)行!
可以看到MyBeanPostProcessor成功拿到了A的Bean定義,并且輸出了提示信息 接下來(lái)讓我們做點(diǎn)壞事 我們?cè)贛yBeanPostProcessor中修改A的Bean對(duì)象,將A的beanClass修改為B.class
重新運(yùn)行Test類,輸出了一些信息后:報(bào)錯(cuò)了!
我要拿到一個(gè)A類對(duì)象,你怎么給我一個(gè)B類對(duì)象呢?這明顯不對(duì) 綜上所述,我們可以得出beanClass屬性控制bean定義的類 4.2 autowireMode我們繼續(xù)看第二個(gè)屬性:autowireMode,自動(dòng)裝配模式 我們?cè)贏bstractBeanDefinition源碼中可以看到:
自動(dòng)裝配模式默認(rèn)是AUTOWIRE_NO,就是不開(kāi)啟自動(dòng)裝配 可選的常量值有以下四種:不自動(dòng)裝配,通過(guò)名稱裝配,通過(guò)類型裝配,通過(guò)構(gòu)造器裝配
接下來(lái)我們來(lái)模擬一個(gè)自動(dòng)裝配場(chǎng)景,仍然是A和B兩個(gè)類,現(xiàn)在在A類中添加B類對(duì)象
我們希望b對(duì)象能夠自動(dòng)裝配,于是我們給他加上了@Autowired注解,其他的完全不變,我們自定義的MyBeanPostProcessor中也不做任何操作,讓我們運(yùn)行測(cè)試類:
自動(dòng)裝配成功了!我們拿到的A類對(duì)象里面成功注入了B類對(duì)象b 現(xiàn)在問(wèn)題來(lái)了,如果我把@Autowired注解去掉,自動(dòng)裝配會(huì)成功嗎?
必然是不成功的 但是我就是想要不加@Autowired注解,仍然可以實(shí)現(xiàn)自動(dòng)裝配,需要怎么做? 這時(shí)就要在我們的MyBeanPostProcessor中做文章了,加入如下內(nèi)容:
再輸出結(jié)果:
自動(dòng)裝配成功了!這次我們可沒(méi)加@Autowired,在我們的自定義的bean后置處理器中設(shè)置了autowireMode屬性,也實(shí)現(xiàn)了自動(dòng)裝配 綜上,autowireMode屬性是用來(lái)控制自動(dòng)裝配模式的,默認(rèn)值是AUTOWIRE_NO,即不自動(dòng)裝配 4.3 constructorArgumentValuesconstructorArgumentValues的字面含義是構(gòu)造器參數(shù)值 改變這個(gè)參數(shù)值,我們可以做到在實(shí)例化對(duì)象時(shí)指定特定的構(gòu)造器 話不多說(shuō),show me your code: 因?yàn)橐芯繕?gòu)造器,只能先”忍痛“關(guān)掉lombok插件,手寫(xiě)一個(gè)pojo.Student類
我們都知道,spring在實(shí)例化對(duì)象時(shí)使用的是對(duì)象的默認(rèn)空參構(gòu)造器: 我們新建一個(gè)測(cè)試方法test
運(yùn)行可以得到下面結(jié)果:
可以看到,確實(shí)使用了空參構(gòu)造器 但是如何指定(自定義)使用哪個(gè)構(gòu)造器呢?我根本看不見(jiàn)摸不著,Spring全幫我做了,實(shí)在是太貼心了。 接下來(lái)就聊聊constructorArgumentValues的使用: 我們?cè)贛yBeanPostProcessor中加入如下內(nèi)容,對(duì)獲取到的pojo.Student的bean定義進(jìn)行操作:
再次運(yùn)行test:
可以看到這次使用了雙參數(shù)構(gòu)造器 有人會(huì)好奇ConstructorArgumentValues到底是個(gè)什么東西,我點(diǎn)進(jìn)源碼研究一番,結(jié)果發(fā)現(xiàn)這個(gè)類就是一個(gè)普通的包裝類,包裝的對(duì)象是ValueHolder,里面一個(gè)List 而ValueHolder這個(gè)對(duì)象繼承于BeanMetadataElement,就是構(gòu)造器參數(shù)的一個(gè)包裝類型 通過(guò)這個(gè)例子我們可以看到ConstructorArgumentValues就是用來(lái)管控構(gòu)造器參數(shù)的,指定這個(gè)值會(huì)在進(jìn)行bean注入的時(shí)候選擇合適的構(gòu)造器。 5. 裝配對(duì)象現(xiàn)在我們把目光放回到SpringBoot的自動(dòng)裝配上來(lái),原來(lái)在真正進(jìn)行bean實(shí)例化對(duì)象前,我們前面還有這些過(guò)程,尤其是存在使用后置處理器BeanFactoryPostProcessor來(lái)對(duì)bean定義進(jìn)行各種自定義修改的操作。 經(jīng)過(guò)上面我們漫長(zhǎng)的研究過(guò)程,我們終于可以回答第一個(gè)問(wèn)題了: 自動(dòng)裝配的對(duì)象:Bean定義 (BeanDefinition) 6. My自動(dòng)裝配看到這里又自然會(huì)產(chǎn)生疑問(wèn):不會(huì)吧,上面可都是自動(dòng)裝配啊,我在配置文件或者使用注解都配置了變量的值,然后加個(gè)@Autowired注解就OK了,spring也是幫我自動(dòng)去裝配。 再高端一點(diǎn)話,我就把XML文件寫(xiě)成JavaConfig配置類,然后使用@Configuration注解,這樣也能自動(dòng)裝配,這不是很nice了嗎? 6.1 自動(dòng)裝配之再思考我的理解,上面的自動(dòng)裝配,我們至少要寫(xiě)一個(gè)配置文件,無(wú)論是什么形式,我們都至少需要一個(gè)文件把它全部寫(xiě)下來(lái),就算這個(gè)文件的內(nèi)容是固定的,但是為了裝配這個(gè)對(duì)象,我們不得不寫(xiě)。 我們甚至都可以做成模板了,比如我在學(xué)習(xí)spring框架整合時(shí),把經(jīng)常寫(xiě)的都搞成了模板: ![]() 有了這些模板,我們只需要點(diǎn)點(diǎn)點(diǎn),再進(jìn)行修改,就能用了。 這樣做確實(shí)很好,可是對(duì)于越來(lái)越成型的項(xiàng)目體系,我們每次都搞一些重復(fù)動(dòng)作,是會(huì)厭煩的。而且面對(duì)這么多xml配置文件,我太難了。 于是我有了一個(gè)想說(shuō)但不敢說(shuō)的問(wèn)題: 我一個(gè)配置文件都不想寫(xiě),程序還能照樣跑,我只關(guān)心有我需要的組件就可以了,我只需要關(guān)注我的目標(biāo)就可以了,我想打開(kāi)一個(gè)工程之后可以1秒進(jìn)入開(kāi)發(fā)狀態(tài),而不是花3小時(shí)寫(xiě)完配置文件(2.5小時(shí)找bug)希望有個(gè)東西幫我把開(kāi)始之前的準(zhǔn)備工作全做了,即那些套路化的配置,這樣在我接完水之后回來(lái)就可以直接進(jìn)行開(kāi)發(fā)。 說(shuō)到這里,想必大家都懂了:SpringBoot 6.2 一個(gè)例子讓我們?cè)谕祽械牡缆飞侠^續(xù)前進(jìn)。 來(lái)看下面這個(gè)例子: 仍然是A類和B類,其中A類仍然引用了B類,我們給A類組件起id=“a”,B類組件起id=“b”
可以看到我們使用了@Autowired注解來(lái)自動(dòng)注入b,測(cè)試類如下:
細(xì)心的同學(xué)已經(jīng)發(fā)現(xiàn)了:我們這里使用了AnnotationConfigApplicationContext這個(gè)JavaConfig配置類會(huì)使用到的加載類,于是我們順利成章地點(diǎn)開(kāi)它所加載的MyAutoConfig類文件 文件內(nèi)容如下:
what? 我要聲明的Bean對(duì)象都去哪了(注意:這里的applicationContext.xml是空的)? 讓我們運(yùn)行test:
竟然運(yùn)行成功了,這究竟是為什么?(元芳,你怎么看?) 細(xì)心的同學(xué)已經(jīng)發(fā)現(xiàn)了:@MyEnableAutoConfig是什么注解?我怎么沒(méi)有這個(gè)注解 讓我們點(diǎn)進(jìn)@MyEnableAutoConfig一探究竟:
原來(lái)如此!你是用了@Import注解導(dǎo)入了Bean定義對(duì)吧,注釋都寫(xiě)著呢! 可是客官,@Import導(dǎo)入bean定義是沒(méi)錯(cuò),但是它導(dǎo)入的是MyImportSelector這個(gè)bean,不是A也不是B啊… 6.3 @Import注解@Import的功能就是獲取某個(gè)類的bean對(duì)象,他的使用形式大致如下:
6.3.1 @Import(A.class)第一種形式@Import(A.class),是最簡(jiǎn)單易懂的形式 我們需要哪個(gè)Bean定義,直接Import他的class即可 6.3.2 @Import(MyImportBeanDefinitionRegister.class)第二種形式@Import(MyImportBeanDefinitionRegister.class) 傳遞了一個(gè)bean定義注冊(cè)器,這個(gè)注冊(cè)器的具體內(nèi)容如下:
這個(gè)注冊(cè)器實(shí)現(xiàn)了ImportBeanDefinitionRegistrar接口,并且重寫(xiě)了里面的registerBeanDefinitions方法 看他做了什么事:創(chuàng)建了一個(gè)新的bean定義,他的類型就是A,然后把這個(gè)bean定義注冊(cè)到BeanDefinitionMap(還記得吧!)里面,key值我們可以人為設(shè)置,這里就設(shè)置成"a" 這樣在傳遞一個(gè)注冊(cè)器的時(shí)候,我們就可以把注冊(cè)器中新增的bean定義注冊(cè)進(jìn)來(lái)使用 6.3.3 @Import(MyImportSelector.class)可以看到,這種使用方式就是我們剛才的注解中使用的方式 他傳遞了一個(gè)叫MyImportSelector的類,這個(gè)類依然是我們自己定義的,具體內(nèi)容如下:
這個(gè)類實(shí)現(xiàn)了ImportSelector接口,并且重寫(xiě)了selectImports方法,返回一個(gè)字符串?dāng)?shù)組 我們可以看到,返回的字符串?dāng)?shù)組中是我們要導(dǎo)入類的全類名 這個(gè)Importer返回的類如果是組件bean對(duì)象,就會(huì)被加載進(jìn)來(lái)使用;如果是一個(gè)配置類,就會(huì)加載這個(gè)配置類 第三種和第二種的區(qū)別是第三種可以一次性寫(xiě)很多類,而且比較簡(jiǎn)潔,只需要清楚類的全包名即可。而第二種方式需要自己清楚包類名,手動(dòng)創(chuàng)建bean定義,然后手動(dòng)加入BeanDefinitionMap。 6.4 例子的研究我們打開(kāi)MyImportSelector,發(fā)現(xiàn)里面赫然寫(xiě)著幾個(gè)大字:
然后我們找到config.MyConfig類,發(fā)現(xiàn)這個(gè)類竟然就是我們剛才寫(xiě)的JavaConfig版本的配置文件:
加載這個(gè)MyConfig配置類,就相當(dāng)于加載了A和B兩個(gè)Bean定義 喂!你是不是搞我!繞了一大圈,怎么還是加載這個(gè)配置文件??!這個(gè)配置文件明明就是我自己寫(xiě)的。 ![]() 總結(jié)一下,我們這個(gè)例子大概繞了這些過(guò)程: ![]() 6.5 將偷懶進(jìn)行到底"沒(méi)有會(huì)偷懶的人解決不掉的問(wèn)題“ —— 魯迅 上面的例子也沒(méi)有多大優(yōu)化啊,我怎么覺(jué)得更加麻煩了?不但繞了一大圈,定義了許多新東西,到最后還是加載了我寫(xiě)好的JavaConfig類,說(shuō)到底我不是還在寫(xiě)javaConfig類嗎… 但是你注意到?jīng)]有:有了上面的機(jī)制,我只需要把JavaConfig類寫(xiě)一次,然后放在某個(gè)地方,在MyImportSelector中加入這個(gè)地方的全包名路徑,下次用的時(shí)候直接導(dǎo)入最頂層的MyAutoConfig類,所有有關(guān)這個(gè)部件我需要的東西,就全部自動(dòng)整理好了,甚至比鼠標(biāo)點(diǎn)點(diǎn)點(diǎn)添加代碼模板還要快! 我突然有了個(gè)很棒的想法,不知道你有了沒(méi)有 。 如果你開(kāi)始有點(diǎn)感覺(jué)了,就會(huì)自然提出另一個(gè)問(wèn)題:我這樣做確實(shí)可以提高效率,但是一段代碼里寫(xiě)入我自己定制的內(nèi)容,每次更改起來(lái)不是太費(fèi)勁了嗎? 想到這里,我就不禁回想起使用JDBC的時(shí)候,在代碼里改SQL語(yǔ)句的痛苦了,那真是生不如死…這種情況就構(gòu)成了硬編碼的行為,是不好的。 我們自然會(huì)想到:要是我創(chuàng)建一個(gè)配置文件properties來(lái)專門(mén)保存我這個(gè)需求所使用的bean對(duì)象,然后使用的時(shí)候在MyImportSelector中讀取配置文件并且返回全包名,不就更加nice了嗎? 于是MyImportSelector中的代碼又改成了下面這樣:
其中MyPropertyReader是我們自己新創(chuàng)建的用于讀取properties文件的工具類 之所以要自己再定義這樣一個(gè)工具類,是為了以后在其中可以做一些其他操作(比如:去重、預(yù)檢查)
我們的配置文件里面這么寫(xiě):
可以看到,key是注解@MyEnableAutoConfig的類名,也就是根據(jù)這個(gè)注解,就會(huì)導(dǎo)入后面的MyConfig這個(gè)Bean,這個(gè)Bean就是我們的配置文件 如此一來(lái)我們讀取這個(gè)配置文件,然后加載跟這個(gè)注解名稱相符的value(即JavaConfig配置文件),就相當(dāng)于我們?cè)诖a里手寫(xiě)的"config.MyConfig",只不過(guò)現(xiàn)在的形式已經(jīng)發(fā)生了巨大的變化:我們添加或者刪除一個(gè)配件,完全只需要修改MyProperty.properties這個(gè)配置文件就行了! 至此,無(wú)論是添加或者刪除組件,無(wú)非是在配置文件中加上或者刪除一行的問(wèn)題了。 讓我們?cè)诟轮筮\(yùn)行程序,可以看到成功拿到了配置文件的全類名 ![]() 程序的運(yùn)行當(dāng)然也是沒(méi)問(wèn)題的:
到此,我仿佛又領(lǐng)悟了一些東西。。。 我的配置文件好像活了,在我需要的時(shí)候他會(huì)出現(xiàn),在我不需要的時(shí)候只需要在配置文件里面給他”打個(gè)叉“,他自己就跑開(kāi)了 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開(kāi)始研究 7.1 @SpringBootConfiguration會(huì)看到@SpringBootApplication這個(gè)注解由好多注解組成 主要的有以下三個(gè):
先來(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注解嗎?就是我們自己寫(xiě)的那個(gè)注解 進(jìn)入@EnableAutoConfiguration: ![]() 看圖中紅圈位置的注解:@Import(AutoConfigurationImportSelector.class) 是不是跟我們上面自己寫(xiě)的內(nèi)容一樣! 這里的作用便是導(dǎo)入了 AutoConfigurationImportSelector 這個(gè)類的bean定義 我們都知道,如果這個(gè)類實(shí)現(xiàn)了ImportSelector接口,那他肯定重寫(xiě)了一個(gè)方法,就是我們上面重寫(xiě)過(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顯然使我們需要的配置文件,也是我們最關(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í)呢?我們之前寫(xiě)過(guò)一個(gè)專門(mén)用于讀取配置文件的類MyPropertyReader,還記得嗎? 如果你還記得的話,我們自己寫(xiě)的工具類里面也是一個(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 于是我們打開(kāi)spring boot自動(dòng)裝配的依賴jar包: ![]() 那這個(gè)配置文件里面的內(nèi)容,是不是跟我們想的一樣呢? ![]() 原來(lái)如此。 這里的EnableAutoConfiguration注解,正是我們此行的起點(diǎn)啊… 到這里,自動(dòng)裝配到底是什么,應(yīng)該比較清楚了,原來(lái)他是幫我們加載了各種已經(jīng)寫(xiě)好的Config類文件,實(shí)現(xiàn)了這些JavaConfig配置文件的重復(fù)利用和組件化 7.4 loadFactoryNames方法行程不能到此結(jié)束,學(xué)習(xí)不能淺嘗輒止。 我們還有最后一塊(幾塊)面紗沒(méi)有解開(kāi),現(xiàn)在還不能善罷甘休。 讓我們進(jìn)入loadFactoryNames方法: ![]() 這個(gè)方法非常簡(jiǎn)短,因?yàn)樗{(diào)用了真正實(shí)現(xiàn)的方法:loadSpringFactories 這一行return代碼我復(fù)制在下面:
可以分析得出:loadSpringFactories方法的返回值又調(diào)用了一個(gè)getOrDefault方法,這明顯是一個(gè)容器類的方法,目的是從容器中拿點(diǎn)東西出來(lái) 就此推測(cè):loadSpringFactories返回了一個(gè)包含我們需要的Config全類名(字符串)的集合容器,然后從這個(gè)集合容器中拿出來(lái)的東西就是我們的configurations 讓我們看這個(gè)loadSpringFactories方法: ![]() 它確實(shí)返回了一個(gè)容器:Map<String, List 這個(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方法的開(kāi)頭和結(jié)尾: ![]() 喂喂,這個(gè)返回的result好像并不是直接new出來(lái)的哦 它是從cache緩存中取出來(lái)的,你發(fā)現(xiàn)了沒(méi)有 根據(jù)下面的if判斷,如果從緩存中讀取出來(lái)了result,并且result的結(jié)果不為空,就直接返回,不需要再進(jìn)行下面的讀寫(xiě)操作了,這樣減少了磁盤(pán)頻繁的讀寫(xiě)I/O 同理,在我更新完所有的配置文件資源之后,退出時(shí)也要更新緩存。 7.6 getAutoConfigurationEntry再探關(guān)鍵部分已經(jīng)過(guò)去,讓我們反過(guò)頭來(lái)重新審視一下遺漏的內(nèi)容: 還記得getAutoConfigurationEntry方法嗎? ![]() 我們最后來(lái)研究一下這個(gè)類除了getCandidateConfigurations還干了哪些事情:
可以看到,這里對(duì)加載進(jìn)來(lái)的配置進(jìn)行了去重、排除的操作,這是為了使得用戶自定義的排除包生效,同時(shí)避免包沖突異常,在SpringBoot的入口函數(shù)中我們可以通過(guò)注解指定需要排除哪些不用的包: 例如我不使用RabbitMQ的配置包,就把它的配置類的class傳給exclude
8. 自動(dòng)裝配本質(zhì)我的理解:
9. 總結(jié)Spring Boot的自動(dòng)裝配特性可以說(shuō)是Spring Boot最重要、最核心的一環(huán),正是因?yàn)檫@個(gè)特性,使得我們的生產(chǎn)復(fù)雜性大大降低,極大地簡(jiǎn)化了開(kāi)發(fā)流程,可以說(shuō)是給我們帶來(lái)了巨大的福音了~~ 筆者本人對(duì)源碼的理解仍然沒(méi)有那么深刻,只是喜歡分享自己的一些學(xué)習(xí)經(jīng)驗(yàn),希望能和大家共同學(xué)習(xí),畢竟掌握一門(mén)新技術(shù)的快感嘛… 大家都懂的! 寫(xiě)這篇文章耗費(fèi)了巨大的精力,每一個(gè)字均是手碼,真的希望喜歡的朋友可以點(diǎn)贊收藏關(guān)注支持一波,這就是對(duì)我這個(gè)未出世的學(xué)生的最大激勵(lì)了! 最后,我畫(huà)了一份Spring Boot自動(dòng)裝配詳細(xì)流程圖,分享給大家。 Spring Boot自動(dòng)裝配詳細(xì)流程圖: ![]()
|
|
來(lái)自: 昵稱10087950 > 《JAVA》