13-Spring初級容器初始化:BeanDefinition是如何注冊到Spring容器

開篇
上一節(jié),我們分析了bean標(biāo)簽下的各種子標(biāo)簽的解析,包括meta標(biāo)簽、lookup-method標(biāo)簽、replaced-method標(biāo)簽、constructor-arg標(biāo)簽、property標(biāo)簽等。
同時,我們也看到了對于constructor-arg標(biāo)簽和property標(biāo)簽而言,會進一步解析各種屬性的子標(biāo)簽,如set標(biāo)簽、list標(biāo)簽、map標(biāo)簽等,不管怎么樣,最終都是將這些標(biāo)簽解析到的信息封裝到BeanDefinition中。
但是,現(xiàn)在還有最后一些問題,那就是Spring容器是個什么東西呢?BeanDefinition又是如何注冊到Spring容器中的呢?這一節(jié)我們一起來看下,主要包含以下幾個部分:
1.xml標(biāo)簽解析到的BeanDefinition,是在哪里注冊到Spring容器中的
2.然后來看下BeanDefinition注冊到Spring容器前,會做哪些準(zhǔn)備性的工作
3.再看下Spring容器到底是什么東西,并且看下BeanDefinition是如何注冊到Spring容器中的
4.最后來看下BeanDefinition注冊到Spring容器后,如何注冊別名以及其完成其他的一些收尾工作
BeanDefinition注冊的入口
我們回到上一節(jié)源碼分析的位置:

可以看到,當(dāng)解析完各種標(biāo)簽后直接就將BeanDefinition返回了。
我們再往前,退回到調(diào)用方法parseBeanDefinitionElement的位置看下:

可以看到,前面解析bean標(biāo)簽下的各種屬性和標(biāo)簽都是在方法parseBeanDefinitionElement中完成了,然后得到GenericBeanDefinition類型的beanDefinition。
最后,我們可以看到parseBeanDefinitionElement方法其實就是將beanDefinition,連帶著解析得到的別名aliasesArray一起封裝到了BeanDefinitionHolder中,BeanDefinitionHolder我們可以理解為是持有BeanDefinition的一個對象而已。
接下來,我們再往前回退到上一個方法的位置看下:

可以看到,方法parseBeanDefinitionElement返回了我們剛才封裝好的BeanDefinitionHolder。
方法decorateBeanDefinitionIfRequired其實是對標(biāo)簽進行進一步的檢測,如果發(fā)現(xiàn)bean標(biāo)簽中還存在自定義標(biāo)簽,就對自定義標(biāo)簽進行解析,當(dāng)然自定義標(biāo)簽的解析我們這里不再深究,接下來,我們重點來看下BeanDefinitionReaderUtils中的registerBeanDefinition方法里面的邏輯,也就是BeanDefinition是如何注冊到Spring容器中的。
BeanDefinition注冊前的檢查
我們跟進到方法registerBeanDefinition中看下:

可以看到在registerBeanDefinition方法中,首先會從BeanDefinitionHolder中取出beanName以及BeanDefinition,然后調(diào)用registerBeanDefinition方法進行注冊。
我們繼續(xù)到方法registerBeanDefinition中看下:

方法中的代碼量比較多,我們依次來看下,首先,beanDefinition肯定是AbstractBeanDefinition的實例,所以調(diào)用方法validate進行最后一次校驗。
我們到validate方法中看下是如何校驗的:

可以看到,在方法validate中不允許methodOverrides屬性和factoryMethodName屬性同時設(shè)置,這是為什么呢?
不知道大家還記得,在上一節(jié)在解析lookup-method標(biāo)簽和replaced-method標(biāo)簽時,會將這兩個標(biāo)簽需要覆蓋的方法名設(shè)置到MethodOverrides中,一旦MethodOverrides不為空,這就意味著Spring創(chuàng)建出來bean還要重新覆寫這些方法。
而factoryMethodName屬性也就是工廠方法的名稱,了解過工廠設(shè)計模式的同學(xué)應(yīng)該都知道,通過工廠方法也可以創(chuàng)建一個bean出來,但是這相比于Spring默認的創(chuàng)建方式而言,算是一種不允許外界覆蓋bean中方法的創(chuàng)建方式了。
也就是說要么你通過工廠方法創(chuàng)建bean,要么就按Spring普通的方式來創(chuàng)建bean,兩者選其一,當(dāng)然這些后面我們在bean的加載環(huán)節(jié)會詳細講解,暫時只需要知道這兩種創(chuàng)建bean的方式是不一樣的,我們這里只能存在其中一種。
接下來,方法prepareMethodOverrides就是對MethodOverrides的一些準(zhǔn)備工作了,我們可以簡單進去看下:

可以看到,在prepareMethodOverrides方法中如果存在MethodOverrides屬性的話,就會通過方法prepareMethodOverride依次預(yù)處理這些需要覆蓋的方法。
預(yù)處理的方式也比較簡單,就是在方法prepareMethodOverride中判斷一下,如果lookup-method標(biāo)簽或replaced-method標(biāo)簽中配置了bean中需要覆蓋的方法,就將MethodOverride中的overloaded屬性值設(shè)置為false。
為什么要這樣設(shè)置overloaded屬性值為false呢?當(dāng)然是為了提高性能,也就是告訴Spring MethodOverride中記錄的那些需要覆蓋的方法,在bean中是沒有重載方法的,這樣的話,Spring就不需要額外根據(jù)參數(shù)去檢查bean中是否存在其他重載方法了,避免了一定的性能損耗。
BeanDefinition注冊到Spring容器中
對BeanDefinition最后的一輪檢查,說白了就是看下是否有異常的配置,并且設(shè)置一些參數(shù)提高創(chuàng)建bean時的效率。
接下來,我們繼續(xù)回到方法registerBeanDefinition中:

可以看到,beanDefinitionMap根據(jù)beanName獲取BeanDefinition,我們看下beanDefinitionMap是什么:

可以看到,beanDefinitionMap就是一個ConcurrentHashMap類型的Map,我們之前或多或少都聽說過Spring容器就是一個Map,確實,beanDefinitionMap就是大名鼎鼎Spring容器,Spring容器從本質(zhì)上來說就是一個Map。
因為beanDefinitionMap是成員變量,難免會有并發(fā)安全問題,所以這里使用多線程安全的ConcurrentHashMap作為Spring的容器。
我們繼續(xù)往后看:

可以看到,在BeanDefinition第一次來注冊時,從beanDefinitionMap中肯定是獲取不到任何東西的。
而且,BeanDefinition對應(yīng)的bean也還沒來得及創(chuàng)建,可以看到,第一次直接將beanName及對應(yīng)的beanDefinition設(shè)置到beanDefinitionMap中,同時將beanName記錄到beanDefinitionNames中。
假設(shè)剛才注冊的bean名稱為beanName1,這個時候如果又有一個bean過來注冊,并且bean的名稱也為beanName1,那會發(fā)生什么事情呢?會不會覆蓋之前注冊的beanName1對應(yīng)的value值呢?
我們接著來看下代碼:

可以看到,首先在if分支上有一個方法isAllowBeanDefinitionOverriding在約束著,通過方法的名稱我們可以知道,它是用來判斷beanDefinitionMap中的元素是否可以被覆蓋的。
如果方法isAllowBeanDefinitionOverriding返回結(jié)果為true,也就是允許相同名稱BeanDefinition覆蓋Spring容器的Map,可以看到就會將當(dāng)前bean的名稱beanName1,以及相應(yīng)的BeanDefinition設(shè)置到beanDefinitionMap中了。
當(dāng)然,這一切取決于方法isAllowBeanDefinitionOverriding方法返回的結(jié)果是什么,我們進去看下:

可以看到方法isAllowBeanDefinitionOverriding的返回結(jié)果,取決于成員變量allowBeanDefinitionOverriding,我們再看下allowBeanDefinitionOverriding:

可以看到,成員變量allowBeanDefinitionOverriding的默認值為true。
也就是說在默認情況下,如果出現(xiàn)相同名稱的多個bean來注冊,在Spring容器對應(yīng)的beanDefinitionMap中是允許被覆蓋的,所以這也在暗示我們在配置bean的時候,盡量不要出現(xiàn)相同名稱的bean,否則會被覆蓋。
最后一點收尾的代碼,我們也來看下:

可以看到,如果發(fā)現(xiàn)當(dāng)前不是這個名稱beanName第一次來注冊,或者當(dāng)前beanName還沒有創(chuàng)建相應(yīng)的單例時,將會調(diào)用方法resetBeanDefinition重置和beanName相關(guān)的一系列緩存。
當(dāng)然,這些都是一些很細節(jié)代碼了,比如,這里都已經(jīng)涉及到單例對象等和bean的加載相關(guān)的一些特性了,后面我們在分析bean的加載時,會看到各種各樣和bean單例創(chuàng)建相關(guān)的一系列緩存。
BeanDefinition別名alias的注冊
最后,我們再回到上一個方法中看下:

可以看到,現(xiàn)在就剩最后一個環(huán)節(jié)了也就是注冊bean的別名,也就是從BeanDefinitionHolder中獲取之前解析到的別名,然后依次遍歷注冊它們。
我們到方法registerAlias中看下:

可以看到,如果需要注冊的別名alias,和bean的名稱name相同,這就沒必要注冊了,并且會從aliasMap中刪除該別名。
其中,aliasMap的key為bean的別名,value為bean在Spring容器中的實際名稱,也就是我們之前看到的beanName。
如果alias和name不相同,此時會先從aliasMap中,根據(jù)alias獲取registeredName,如果registeredName和name相同,這就意味著名稱為name的別名已經(jīng)被其他的bean注冊了,就不需要重復(fù)注冊了。
我們繼續(xù)往下看:

如果registeredName和name不相同,而且因為根據(jù)別名alias從aliasMap成功獲取了一個非空的bean的名稱registeredName了,這就意味著別名alias已經(jīng)注冊了一個bean名稱為registeredName了。
此時,如果方法allowAliasOverriding返回false,也就是不允許別名被覆蓋,也就是說一個別名alias只能注冊一個bean的名稱不能注冊多個bean的名稱,事與愿違,這個時候直接就會拋異常了。
另外,我們可以看到還有一個方法checkForAliasCircle:

它主要是為了檢查是否存在別名循環(huán)的問題,比如,別名alias1對應(yīng)bean的名稱為name1,但是,如果同時還存在別名alias1對應(yīng)bean的名稱為name2,而name2又是bean name1的別名。
相當(dāng)于同時存在:alias1到name1,alias1到name2,name2再到name1這兩個關(guān)系,出現(xiàn)了兩個起點和重點都相同的循環(huán)了,這樣就造成了別名循環(huán)的問題就會拋異常。
現(xiàn)在,BeanDefinition注冊到Spring容器的核心環(huán)節(jié)已經(jīng)結(jié)束了,我們來看下:

最后,當(dāng)BeanDefinition注冊到Spring容器之后會調(diào)用fireComponentRegistered方法,發(fā)布一個BeanDefinition已經(jīng)被注冊的事件通知而已,當(dāng)然,一般我們都不需要監(jiān)聽這方面的事件。
總結(jié)
好了,今天的知識點,我們就講到這里了,我們來總結(jié)一下吧。
一張圖來梳理下當(dāng)前的流程:

這一節(jié),我們找到BeanDefinition注冊的入口,發(fā)現(xiàn)了Spring容器其實就是一個Map,將bean注冊到Spring容器中的過程很簡單,就是以bean的名稱為key,以bean對應(yīng)的BeanDefinition為value注冊到beanDefinitionMap這個Map中。
另外,考慮到多線程安全問題,Spring容器對應(yīng)的Map也就是beanDefinitionMap,其實是多線程安全類型的ConcurrentHashMap。
到這里為止,初級的Spring容器也就是XmlBeanFactory的初始化環(huán)節(jié)已經(jīng)結(jié)束,XmlBeanFactory畢竟是初級的Spring容器,但是ApplicationContext這樣的高級容器相比于BeanFactory而言,擴展了哪些更高級別的功能呢?
接下來的章節(jié),我們好好來看下ApplicationContext的初始化是怎樣的。