04-Spring初級容器初始化:忽略指定接口自動裝配功能

開篇
上一節(jié),我們了解了Resource是Spring對所有資源的一個抽象,不同來源的資源對應(yīng)不同的Resource實現(xiàn)類,并且,我們也看到不同Resource實現(xiàn)類加載資源的方式都是不一樣的。
那現(xiàn)在,當(dāng)Spring容器獲取到資源之后,下一步會干些什么事情呢?這一節(jié),我們繼續(xù)來看下,這一節(jié)主要分為以下幾個環(huán)節(jié):
1.首先,我們會隨著XmlBeanFactory的構(gòu)造方法一路跟進(jìn)去看下里面都有哪些邏輯
2.根據(jù)我們看到的代碼,先來認(rèn)識下Spring中的感知接口又是什么
3.然后我們會來演示下感知接口一般都是如何使用的,先直觀體驗一下
4.最后我們再結(jié)合源碼分析一下,為什么Spring需要忽略這些感知接口
這里需要跟大家多提一點的就是,在開始Spring的源碼分析之前,建議大家都在本地搭建好Spring源碼環(huán)境,因為受到專欄圖片截圖的局限性,很難給大家還原一個第一人稱的源碼視角。
大家可以在觀看了圖文源碼的講解之后,再結(jié)合自己本地環(huán)境的源碼,順著主流程再串一下分析或者debug,這樣的學(xué)習(xí)效果會更好一點。
初探ignoreDependencyInterface方法
我們繼續(xù)看到之前的demo代碼:

可以看到,當(dāng)applicationContext.xml經(jīng)過ClassPathResource封裝之后,會作為XmlBeanFactory的構(gòu)造方法參數(shù)傳遞進(jìn)去,現(xiàn)在,我們就順著XmlBeanFactory的構(gòu)造方法來看一下。
進(jìn)入到XmlBeanFactory的構(gòu)造方法之后,可以看到調(diào)用了XmlBeanFactory另外一個構(gòu)造方法 XmlBeanFactory(Resource, BeanFactory):

其中,參數(shù)parentBeanFactory的值默認(rèn)為null。
而且,我們可以預(yù)感到XmlBeanFactory初始化時,核心邏輯應(yīng)該在this.reader.loadBeanDefinitions(resource)這行代碼中,但是XmlBeanFactory首先調(diào)用了super方法,也就是父類的構(gòu)造方法。
所以,我們先來看下XmlBeanFactory的父類構(gòu)造方法中,會干些什么事情:

我們從XmlBeanFactory的構(gòu)造方法開始,一路跟進(jìn)到到父類AbstractAutowireCapableBeanFactory的構(gòu)造方法時才發(fā)現(xiàn)了點東西,如下圖所示:

可以看到,這個時候會通過ignoreDependencyInterface方法設(shè)置了一些類,而且設(shè)置這些類之前還會再次調(diào)用super父類構(gòu)造方法,我們簡單看下:

還好,AbstractAutowireCapableBeanFactory的父類AbstractBeanFactory的無參構(gòu)造方法中,其實什么事也沒干。
所以,現(xiàn)在最讓人困惑的地方就在于,ignoreDependencyInterface方法是干什么的呢?為什么在初始化XmlBeanFactory的時候就要調(diào)用它呢?
Spring中的感知接口又是什么呢?
現(xiàn)在,我們繼續(xù)看下ignoreDependencyInterface方法,如下圖所示:

可以看到,在AbstractAutowireCapableBeanFactory的構(gòu)造方法中,連續(xù)調(diào)用了三次的ignoreDependencyInterface方法,分別設(shè)置了BeanNameAware.class、BeanFactoryAware.class和BeanClassLoaderAware.class這三個類。
我們到ignoreDependencyInterface方法中看下,如下圖所示:

很簡單,就是將這三個類都放到了ignoredDependencyInterfaces中,而ignoredDependencyInterfaces其實就是一個Set集合:

現(xiàn)在讓人疑惑的點還挺多的,首先BeanNameAware.class、BeanFactoryAware.class和BeanClassLoaderAware.class這三個類分別是什么呢?ignoredDependencyInterfaces方法的作用又是什么呢?
簡單看了下發(fā)現(xiàn),BeanNameAware.class、BeanFactoryAware.class和BeanClassLoaderAware.class這個三個類都是接口,并且都是繼承接口Aware,如下圖所示:

我們可以看到,這個三個接口類中都有自己的方法。
細(xì)心的讀者可能發(fā)現(xiàn)了,這三個接口中的方法名稱,和相應(yīng)類的名稱極度的相似,其實,Aware接口也稱為感知接口,當(dāng)bean實現(xiàn)了這些感知接口時,Spring在實例化這些bean的時候,就會調(diào)用感知接口中的方法注入相應(yīng)的數(shù)據(jù)。
我們可以用之前的Student類來演示一下:

可以看到,Student類實現(xiàn)了BeanNameAware接口,這樣的話,當(dāng)Student類在初始化時,Spring就會調(diào)用setBeanName方法,注入當(dāng)前bean的名稱了。
applicationContext.xml配置文件和相應(yīng)的測試類,和之前一樣:


然后,我們運(yùn)行一下看下結(jié)果:

可以看到,BeanNameAware接口的方法setBeanName被調(diào)用,并且name的值,就是xml中的bean標(biāo)簽里配置的id屬性值student。
可能有些同學(xué)會有些疑惑,Student類為什么實現(xiàn)了BeanNameAware接口就可以得到beanName呢?setBeanName方法又是在什么時候調(diào)用的呢?
其實,大家暫時可以不用太糾結(jié),后面我們講到bean的實例化時,就可以看到Spring內(nèi)部會調(diào)用這些感知接口的方法,為實現(xiàn)感知接口的bean注入相應(yīng)的數(shù)據(jù)了。
ignoreDependencyInterface方法是干什么的呢?
了解完這三個感知接口之后,我們再來看下ignoreDependencyInterface方法到底有什么作用,如下圖所示:

通過方法上的注釋,我們大概可以知道ignoreDependencyInterface方法的功能,就是在自動裝配時忽略指定接口的依賴,簡單看注釋確實還看不出什么,我們直接來看下ignoredDependencyInterfaces集合在代碼中的作用吧。
簡單找了下發(fā)現(xiàn),ignoredDependencyInterfaces集合只在方法isExcludedFromDependencyCheck中被調(diào)用了,我們來看下:

根據(jù)isExcludedFromDependencyCheck方法的注釋,初步的意思就是明確一個bean中的屬性是否要從依賴檢查中排除掉。
也就是說你有一個bean,bean中的某個屬性是否能被注入對應(yīng)的依賴,還得要看你這個屬性對應(yīng)的類是否實現(xiàn)了BeanNameAware、BeanFactoryAware、BeanClassLoaderAware這些接口。
但是,這也只是我們的初步猜測,我們還得要到AutowireUtils類中的isSetterDefinedInInterface方法中,進(jìn)一步分析一下:

可以看到,在isSetterDefinedInInterface方法中,簡單來說就是判斷兩點:
一是bean的屬性對應(yīng)的類,是否實現(xiàn)了BeanNameAware、BeanFactoryAware或BeanClassLoaderAware中的某個接口;
二是這個bean屬性對應(yīng)的setter方法,在這三個感知接口中是否也存在相同的方法。
如果同時滿足以上兩點的話,方法isSetterDefinedInInterface就會返回true,Spring在自動裝配也就是創(chuàng)建這個bean時,就不會給該屬性注入值了。
這個概念可能會有點晦澀,我們可以通過一個簡單的案例來幫助我們理解一下,比如我們創(chuàng)建一個bean如BeanNameAwareImpl,并且實現(xiàn)接口BeanNameAware:

可以看到,之所以讓String類型的字段名稱為beanName,就是要模擬出beanName的setter方法為setBeanName,這樣的話,就同時滿足setBeanName方法既是beanName屬性的setter方法,同時該方法在BeanNameAware感知接口中也存在。
所以的話,同時滿足了這兩個條件之后,再根據(jù)我們剛才的分析,也就是Spring不會為bean BeanNameAwareImpl中的beanName屬性注入任何的值。
然后,我們在applicationContext.xml中,配置BeanNameAwareImpl這個bean:

可以看到的是,我們特意為beanName字段,設(shè)置值 “beanName”,可以預(yù)料到的是,“beanName”這個值,也就不能自動裝配到BeanNameAwareImpl中的beanName屬性中了。
然后,我們通過BeanFactoryDemo,從容器中獲取beanNameAwareImpl這個bean:

運(yùn)行一下,可以看到打印結(jié)果為:

果然,我們可以發(fā)現(xiàn),就算在applicationContext.xml中為beanName屬性設(shè)置了“beanName”,最終也不能注入到bean的屬性beanName中,反而得到的結(jié)果是beanNameAwareImpl。
而這個結(jié)果,其實就是Spring內(nèi)部調(diào)用了BeanNameAware接口的setBeanName方法,為BeanNameAwareImpl設(shè)置了beanName屬性的值。
分析到這里也就真相大白了,也就是說如果一個bean實現(xiàn)了BeanName、BeanFactoryAware或BeanClassLoaderAware接口的話,那這個bean中的屬性如果想要通過Spring進(jìn)行自動裝配賦值的話,這個屬性對應(yīng)的setter方法,就不能和感知接口中的方法相同。
如果相同的話,Spring就不會為該屬性自動裝配賦值,而是讓Spring內(nèi)部調(diào)用這些感知接口的方法,來為這些屬性設(shè)置值。
這也是這些感知接口存在的意義,畢竟,bean都實現(xiàn)了這些感知接口了,而感知接口恰好已經(jīng)通過方法ignoreDependencyInterface添加到忽略感知接口的集合中了,這就相當(dāng)于這些屬性的賦值權(quán)利交給Spring內(nèi)部來決定了。
Spring這樣的設(shè)計其實也有一定的合理性,比如你實現(xiàn)了BeanNameAware接口,對應(yīng)的beanName屬性的值,當(dāng)然就是當(dāng)前這個bean在Spring容器中的名稱啊。
此時,如果你從外部的xml或者注解中注入了一個其它的名稱,Spring理所應(yīng)當(dāng)就會忽略掉這個外來值的自動裝配了,確保bean名稱的唯一性。
總結(jié)
好了,今天的知識點,我們就講到這里了,我們來總結(jié)一下吧。
第一,通過一路跟進(jìn)XmlBeanFactory的構(gòu)造方法,發(fā)現(xiàn)首先會通過方法ignoreDependencyInterface,設(shè)置一系列的感知接口,并且了解了一下感知接口的類繼承體系。
第二,然后,我們了解了bean如果實現(xiàn)了這些感知接口,Spring就會在bean實例化時調(diào)用這些感知接口中的方法,為bean注入bean的名稱BeanName、容器BeanFactory或者是bean的類加載器BeanClassLoader這些資源。
第三,接著我們通過對源碼的分析了解到,原來ignoreDependencyInterface方法的作用,是為了讓那些實現(xiàn)了感知接口的bean屬性只能由Spring容器賦值,而不是可以人為的從外部隨意的注入進(jìn)來。