12-Spring初級(jí)容器初始化:bean的各種子標(biāo)簽解析

開篇
上一節(jié),我們了解了Spring是如何解析bean標(biāo)簽上各種各樣的屬性的,然后將解析到的屬性值設(shè)置到BeanDefinition中。
bean標(biāo)簽中,除了屬性之外當(dāng)然還包括各種各樣的子標(biāo)簽,比如property標(biāo)簽、constructor-arg標(biāo)簽等,接下來(lái),我們就來(lái)看下這些子標(biāo)簽是如何解析的吧,這一節(jié)主要包括以下幾個(gè)部分:
1.先來(lái)看下Spring中的各種標(biāo)簽是在哪里開始解析的
2.然后我們通過(guò)一個(gè)案例使用下標(biāo)簽constructor-arg
3.接下來(lái)看下constructor-arg標(biāo)簽是如何解析的
4.最后再來(lái)看下property標(biāo)簽又是如何將解析的
子標(biāo)簽:meta、lookup-method和replaced-method
我們從上一節(jié)課看到的位置,繼續(xù)來(lái)分析下:

可以看到,bean標(biāo)簽的解析現(xiàn)在就差這些子標(biāo)簽元素element的解析了,首先,我們看到的是解析子標(biāo)簽meta、lookup-method和replaced-method標(biāo)簽。
我們先來(lái)看下meta子標(biāo)簽是如何解析的:

可以看到解析的過(guò)程也是非常簡(jiǎn)單,就是從bean標(biāo)簽下遍歷獲取meta標(biāo)簽,然后將解析到屬性key和屬性value的值封裝到BeanMetadataAtrribute中。
最后將BeanMetadataAtrribute記錄到BeanMetadataAttributeAccessor中,BeanMetadataAtrribute和BeanMetadataAttributeAccessor,我們可以理解為是Bean封裝屬性和訪問(wèn)屬性的底層類。
lookup-method和replaced-method標(biāo)簽的解析也是類似:


正如我們看到的一樣,meta、lookup-method和replaced-method標(biāo)簽的解析過(guò)程也就這樣,可能有些同學(xué)都不太明白這幾個(gè)子標(biāo)簽到底有什么用,其實(shí),這幾個(gè)標(biāo)簽我們?cè)谌粘i_發(fā)中確實(shí)也幾乎都用不到,所以,我們簡(jiǎn)單看下就行了。
相比于標(biāo)簽meta、lookup-method和replaced-method而言,constructor-arg標(biāo)簽和property標(biāo)簽在工作中用到的概率還會(huì)更大一些,接下來(lái),我們重點(diǎn)來(lái)看下這兩個(gè)標(biāo)簽是如何解析的。
constructor-arg標(biāo)簽的使用
我們先來(lái)簡(jiǎn)單看一下constructor-arg標(biāo)簽是如何使用的,首先,我們寫一個(gè)簡(jiǎn)單的java類,還是以Student類舉例:

可以看到,Student類非常的簡(jiǎn)單,包括一個(gè)String類型的變量name,和一個(gè)int類型的變量age,另外,還添加了一個(gè)包含name和age參數(shù)的構(gòu)造方法。
而constructor-arg標(biāo)簽的功能,就是在Spring在創(chuàng)建bean的時(shí)候,給bean的構(gòu)造方法傳入?yún)?shù)。
接下來(lái),我們?cè)赼pplicationContext.xml中配置下Student類:

可以看到,在bean標(biāo)簽下我們添加了constructor-arg標(biāo)簽,標(biāo)簽中的屬性就是構(gòu)造方法中的參數(shù)名稱,而value標(biāo)簽配置的為參數(shù)的值,可以看到我們配置了兩個(gè)constructor-arg標(biāo)簽,對(duì)應(yīng)構(gòu)造方法中的兩個(gè)參數(shù)。
其實(shí),constructor-arg標(biāo)簽也支持我們按照參數(shù)index,也就是參數(shù)位置來(lái)指定參數(shù)的值:

可以看到,在constructor-arg標(biāo)簽中,因?yàn)閕ndex屬性是從0作為起始下標(biāo)的,index為0表示第一個(gè)參數(shù)name,index為1為第二個(gè)參數(shù)age。
最后,我們?cè)偻ㄟ^(guò)代碼來(lái)測(cè)試下:

運(yùn)行一下代碼,看下效果:

可以看到,構(gòu)造方法的參數(shù)name和age的值,成功通過(guò)constructor-arg標(biāo)簽傳入,constructor-arg標(biāo)簽的使用無(wú)非也就是這樣,接下來(lái),我們?cè)賮?lái)針對(duì)性的看下Spring是如何解析constructor-arg標(biāo)簽的吧。
constructor-arg標(biāo)簽的解析
接下來(lái),我們?cè)倩氐街胺治龅牡胤?

首先,我們到方法parseConstructorArgElements中看下constructor-arg標(biāo)簽是如何解析的:

可以看到,先從bean標(biāo)簽下獲取所有的子節(jié)點(diǎn),然后找到名稱為constructor-arg的標(biāo)簽,再調(diào)用方法parseConstructorArgElement進(jìn)一步解析。
我們跟進(jìn)到方法parseConstructorArgElement中看下:

可以看到方法中的東西還是蠻多的,一眼看過(guò)去可能暈,但是,仔細(xì)看了下發(fā)現(xiàn)無(wú)非也做了幾件事而已,首先將constructor-arg標(biāo)簽中的屬性index、type和name的值都解析出來(lái)。
接著可以看到,首先會(huì)判斷屬性indexAttr屬性是否存在,然后因?yàn)槲覀儎偛旁诎咐幸部吹搅?,在constructor-arg標(biāo)簽中,要么使用index屬性來(lái)配置參數(shù),要么就是用name屬性來(lái)配置參數(shù),所以,根據(jù)是否配置了indexAttr屬性,我們可以看到出現(xiàn) 了兩個(gè)分支。
在這兩個(gè)分支中分別有對(duì)應(yīng)的解析方式,且最終解析出來(lái)的結(jié)果和我們之前分析的一樣,都封裝在了BeanDefinition中。
constructor-arg標(biāo)簽屬性的解析
在方法parseConstructorArgElement中的兩個(gè)分支中,有一個(gè)相同的方法也就是parsePropertyValue,它就是用來(lái)解析constructor-arg標(biāo)簽下的所有子標(biāo)簽和屬性的。
在xml的constructor-arg標(biāo)簽下,可能會(huì)配置各種各樣的屬性值,除了剛才案例中看到的name、index屬性以及value標(biāo)簽之外,還有其他數(shù)據(jù)結(jié)構(gòu)對(duì)應(yīng)的子標(biāo)簽,如array標(biāo)簽、list標(biāo)簽、set標(biāo)簽、map標(biāo)簽、props標(biāo)簽等,這些屬性標(biāo)簽都可能在constructor-arg標(biāo)簽下,為什么呢?
因?yàn)橐粋€(gè)bean的構(gòu)造方法的參數(shù)類型是多種多樣的,bean構(gòu)造方法中參數(shù)類型為L(zhǎng)ist、Set、Map等都是合情合理且大概率可能會(huì)出現(xiàn)的,Spring對(duì)這些情況都會(huì)極盡考慮,所以在方法parsePropertyValue中會(huì)依次去解析各種各樣的子標(biāo)簽的值。
那Spring具體是如何解析的呢,我們就要到方法parsePropertyValue中看下了:

關(guān)于標(biāo)簽解析這部分的代碼,可以看到,一方面細(xì)節(jié)確實(shí)是太多了,容易讓人頭暈,我們快速過(guò)一遍把握那些關(guān)鍵的點(diǎn)和主流程就可以了。
在parsePropertyValue方法中,首先是獲取constructor-arg標(biāo)簽下的所有子節(jié)點(diǎn)nl,然后在遍歷這些子節(jié)點(diǎn)時(shí),將description和meta標(biāo)簽剔除掉不處理。
一旦發(fā)現(xiàn)有這兩個(gè)類型之外的標(biāo)簽存在時(shí),就記錄到子標(biāo)簽元素subElement中,準(zhǔn)備進(jìn)一步的解析,當(dāng)然這里subElement指的就是我們剛才提到的array標(biāo)簽、list標(biāo)簽、set標(biāo)簽等子標(biāo)簽。
我們繼續(xù)看下去:

可以看到,接下來(lái)開始判斷在constructor-arg標(biāo)簽中,是否配置了屬性ref和屬性value的值。
屬性ref和value我們都比較熟悉,ref是引用另外一個(gè)bean依賴的,而value是配置具體的值的,兩者只能同時(shí)出現(xiàn)其中一個(gè),ref屬性和value屬性在使用時(shí)只能二選一,當(dāng)然,我們?cè)诖a中也看到了如果兩者同時(shí)存在就會(huì)報(bào)錯(cuò)提示。
然后開始對(duì)ref屬性和value屬性分別進(jìn)行解析處理,將解析到的屬性值封裝到BeanDefinition中,但是,如果既沒有配置ref屬性,也沒有配置value屬性,甚至還沒有配置子標(biāo)簽的話,這是不允許的,在最后的一個(gè)else分支中,我們也看到了會(huì)報(bào)錯(cuò)。
但是,如果constructor-arg標(biāo)簽配置了子標(biāo)簽的話,也就是我們剛才記錄的subElement不為空,就會(huì)調(diào)用方法parsePropertySubElement進(jìn)行進(jìn)一步的解析子標(biāo)簽元素subElement。
很顯然,這里才是解析子標(biāo)簽的關(guān)鍵,我們進(jìn)去看下:

可以看到,在方法parsePropertySubElement中又調(diào)用了一個(gè)重載方法parsePropertySubElement,我們繼續(xù)進(jìn)去看下:

終于,我們看到了在方法parsePropertySubElement在解析各種各樣的標(biāo)簽,同時(shí),Spring考慮子標(biāo)簽的周全程度,也遠(yuǎn)超乎我們的想象。
包括ref標(biāo)簽、idref標(biāo)簽、value標(biāo)簽、null標(biāo)簽等,當(dāng)然,我們剛才提到的array標(biāo)簽、list標(biāo)簽、set標(biāo)簽、map標(biāo)簽、props標(biāo)簽也都在解析的范圍之內(nèi),具體怎么解析,其實(shí)相關(guān)的API調(diào)用也大差不差,如果沒有一些特定的需求的話,我們看到這個(gè)程度也就差不多了。
property標(biāo)簽的解析
回到之前解析constructor-arg標(biāo)簽位置:

最后,再來(lái)看下property標(biāo)簽是如何解析的,我們到parsePropertyElements方法中看下:

可以看到,和之前解析constructor-arg標(biāo)簽一樣,先獲取bean標(biāo)簽下的所有子標(biāo)簽,然后從這些子標(biāo)簽中找到property標(biāo)簽進(jìn)行解析。
我們?cè)俚絧arsePropertyElement方法中看下:

可以看到,property標(biāo)簽的解析理解起來(lái)就比較簡(jiǎn)單了。
首先獲取property標(biāo)簽中的name屬性的值,如果發(fā)現(xiàn)之前已經(jīng)解析過(guò)相同名稱的property標(biāo)簽是會(huì)報(bào)錯(cuò)的,也就是說(shuō)在同一個(gè)bean標(biāo)簽中,是不允許存在相同名稱的property標(biāo)簽的。
然后,我們可以看到調(diào)用了方法parsePropertyValue方法:

方法parsePropertyValue我們剛才已經(jīng)分析過(guò)了,其實(shí)就是在解析各種各樣的子標(biāo)簽而已。
然后可以看到,會(huì)將解析到的結(jié)果封裝為PropertyValue,再將PropertyValue添加到BeanDefinition相應(yīng)的數(shù)據(jù)結(jié)構(gòu)中。
總的來(lái)說(shuō),標(biāo)簽的解析過(guò)程雖然細(xì)節(jié)非常的多,當(dāng)然,我們也沒必要強(qiáng)迫自己記住每個(gè)環(huán)節(jié),了解它解析了哪些東西就差不多了,如果哪天需要深入的分析某個(gè)標(biāo)簽的解析,我們也可以回過(guò)頭來(lái)針對(duì)性的研究。
不管怎么樣,我們現(xiàn)在已經(jīng)了解了在xml配置文件中那些讓人眼花繚亂的標(biāo)簽配置,其實(shí)在Spring源碼中都是有實(shí)實(shí)在在代碼解析的。
總結(jié)
好了,今天的知識(shí)點(diǎn),我們就講到這里了,我們來(lái)總結(jié)一下吧。
一張圖來(lái)梳理下當(dāng)前的流程:
這一節(jié),我們看了下bean標(biāo)簽中的各種子標(biāo)簽的解析,而且我們發(fā)現(xiàn)光是標(biāo)簽中的屬性解析,代碼量就恐怖的嚇人,但是不管怎么樣,我們也算是看到這些標(biāo)簽在哪里解析的了,并且解析得到的信息也都是封裝在BeanDefinition中的。
接下來(lái),就差最后一步了,也就是說(shuō)我們千辛萬(wàn)苦將bean標(biāo)簽的各種屬性、各種子標(biāo)簽的信息都封裝到了BeanDefinition之后,BeanDefinition又是如何注冊(cè)到Spring容器中的呢,下一節(jié)我們來(lái)揭曉這塊內(nèi)容。