前面幾篇介紹了對XML配置文件的解析,接下來就是bean的載入過程。bean的載入實現比bean的解析要複雜的多

bf.getBean功能調用的源碼如下:

以上載入過程的步驟大致如下

1、轉換對應beanName,這裡的beanName可能是別名,也可能是factoryBean,需要一些列的解析,這些解析內容包括如下內容

alpha 去除FactoryBean的修飾符,如果name="&aa",那麼會首先去除&而使name="aa"

eta 取指定alias所表示的最終beanName,例如別名A指向名稱為B的bean則返回B,若別名A指向別名B,別名B又指向別名C的bean則返回C

2、嘗試從緩存中載入單例。

單例在spring的同一個容器內只會被創建一次,後續再獲取bean,就直接從單例緩存中獲取了。這裡只是嘗試載入,首先嘗試從緩存中載入,如果載入不成功則再次嘗試從singletonFactories中載入。因為在創建單例bean的時候會存在依賴注入的情況,而在創建依賴的時候為了避免循環依賴,spring創建bean的原則是不等bean創建完成就會將創建bean的ObjectFactory提早曝光加入到緩存中,一旦下個bean創建時候需要依賴上個bean則直接使用ObjectFactory

3、bean的實例化

如果從緩存中得到了bean的原始狀態,則需要對bean進行實例化。這裡有必要強調一下,緩存中記錄的只是最原始的bean狀態,並不一定是我們最終想要的bean。假如我們需要對工廠bean進行處理,那麼這裡得到的騎士是工廠bean的初始狀態,但是我們真正需要的是工廠bean中定義的factory-method方法中返回的bean,而getObjectForBeanInstance就是完成這個工作的。

4、原型模式的依賴檢查

只有在單例情況下才會嘗試解決循環依賴,如果存在A中有B的屬性,B中有A的屬性,那麼當依賴注入的時候,就會產生當A還未創建完的時候因為對於B的創建再次返回創建A,造成循環依賴,也就是情況:isPrototypeCurrentlyInCreation(beanName)判斷true。

5、檢測parentBeanFactory

從代碼上看,如果緩存沒有數據的話直接轉到父類工廠上去載入了,這是為什麼呢?

這裡有一個很重要的判斷條件 parentBeanFactory!=null&&containsBean Definition(beanName),parentBeanFactory!=null。parentBeanFactory如果為空,則其他一切都是浮雲,這個沒什麼說的,但是!containsBeanDefinition(beanName)就比較重要了,它是在檢測如果當前載入的XML配置文件中不包含beanName所對應的配置,就只能到parentBeanFactory去嘗試下了,然後再去遞歸的調用getBean方法。

6、將存儲XML配置文件的GernericBeanDefinition轉換為RootBeanDefinition。

因為從XML配置文件中讀取到的Bean信息是存儲在GernericBeanDefinition中的,但是所有的Bean後續處理都是針對於RootBeanDefinition的,所以這裡需要進行一個轉換,轉換的同事如果父類bean不為空的話,則會一併合併父類的屬性

7、尋找依賴

因為bean的初始化過程中很可能會用到某些屬性,而某些屬性很可能是動態配置的,並且配置成依賴於其他的bean,那麼這個時候就有必要先載入依賴的bean,所以,在spring的載入順序中,在初始化某一個bean的時候首先會初始化這個bean所對應的依賴

8、針對不同的scope進行bean的創建

我們都知道,在spring中存在著不同的scope,其中默認的是singleton,但是還有些其他的配置諸如prototype、request之類的。在這個步驟中,spring會根據不同的配置進行不同的初始化策略

9、類型轉換

程序到這裡返回bean後已經基本結束了,通常對該方法的調用參數requiredType是為空的,但是可能會存在這樣的情況,返回的bean其實是個String,但是requiredType卻傳入Integer類型,那麼這時候本步驟就會起作用了,它的功能是將返回的bean轉換為requiredType所指定的類型。當然,String轉換為Integer是最簡單的一種轉換,在spring中提供了各種各樣的轉換器,用戶也可以自己擴展轉換器來滿足需求。

FactoryBean的使用

一般情況下,spring通過反射機制利用bean的class屬性指定實現類來實例化bean。在某些情況下,實例化bean過程比較複雜,如果按照傳統的方式,則需要在<bean>中提供大量的配置信息,配置方式的靈活性是受限的,這是採用編碼的方式可能會得到一個簡單的方案。spring為此提供了一個org.Springframework.bean.factory.FactoryBean的工廠類介面,用戶可以通過實現該介面定製實例化bean的邏輯。

FactoryBean介面對於Spring框架來說佔有重要的地位spring自身就提供了70多個FactoryBean的實現。它們隱藏了實例化一些複雜bean的細節,給上層應用帶來了便利

該介面定義了3個方法

T getObject():返回由FactoryBean創建的bean實例,如果isSingleton()返回true,則該實例會放到spring容器中單實例緩存池中

boolean isSingleton():返回由FactoryBean創建的bean實例的作用域是singleton還是prototype

Class<T>getObjectType():返回FactoryBean創建的bean類型

當配置文件中<bean>的class屬性配置的實現類是FactoryBean時,通過getBean方法返回的不是FactoryBean本身,而是FactoryBean#getObject()方法所返回的對象,相當於FactoryBean#getObject()代理了getBean()方法。如果使用傳統方式配置下面Car的<bean>時,Car的每個屬性分別對應一個<property>元素標籤。

public class Car{

private int maxSpeed;

private String brand;

private double price;

}

如果用FactoryBean的方式實現就會靈活一些

有了這個CarFactoryBean後,就可以在配置文件中使用下面這種自定義的配置方式配置CarBean了

<bean id="car" class="factorybean.CarFactoryBean" carInfo="超級跑車,400,200000"/>

當調用getBean("car")時,Spring通過反射機制發現CarFactoryBean實現了FactoryBean的介面,這時spring容器就調用介面方法CarFactoryBean#getObject()方法返回。如果希望獲取carFactoryBean的實例,則需要在使用getBean(beanName)方法時在beanName前顯示的加上&前綴,例如getBean("&car")

BeanFactory與FactoryBean的區別

BeanFactory,以Factory結尾,表示它是一個工廠類(介面),用於管理Bean的一個工廠。在Spring中,BeanFactory是IOC容器的核心介面,它的職責包括:實例化、定位、配置應用程序中的對象及建立這些對象間的依賴。Spring為我們提供了許多易用的BeanFactory實現,XmlBeanFactory就是常用的一個

FactoryBean,以Bean結尾,表示它是一個Bean,不同於普通Bean的是:它是實現了FactoryBean<T>介面的Bean,根據該Bean的ID從BeanFactory中獲取的實際上是FactoryBean的getObject()返回的對象,而不是FactoryBean本身,如果要獲取FactoryBean對象,請在id前面加一個&符號來獲取。

緩存中獲取單例bean

這個方法涉及循環依賴的檢測,以及很多變數的記錄存取。這個方法首先嘗試從singletonObjects裡面獲取實例,如果獲取不到再從earlySingletonObjects裡面獲取,如果還獲取不到,再嘗試從singletonFactories裡面獲取beanName對應的ObjectFactory,然後調用這個ObjectFactory的getObject來創建bean,並防到earlySingletonObjects裡面去,並且從singletonFactories裡面reomve掉這個ObjectFactory,而對於後續的所有內存操作都只為了循環依賴檢查時候使用,也就是在allowEarlyReference為true的情況下才會使用。這裡涉及用於存儲bean的不同的map簡單解釋如下:

singletonObjects:用於保存BeanName和創建bean實例之間的關係name-->instance

singletonFactories:用於保存BeanName和創建bean的工廠之間的關係name-->factory

earlysingletonObjects:也是保存BeanName和創建bean實例之間的關係,與singletonObjects的不同之處在於,當一個單例bean被放到這裡後,那麼當bean還在創建過程中,就可以通過getBean方法獲取到了,其目的是用來檢測循環引用(我的理解:a引用b,b引用a的情況下,創建a需要b的實例,創建b需要創建a的實例,為了解決循環死鎖的問題,這時候a創建過程中先創建b實例,b實例還未創建完就把b的ObjectFactory加入到earlysingletonObjects當中,這時候其他地方需要創建b就不用等b創建完(如果等待b創建完就有可能死鎖),直接通過earlysingletonObjects中獲取,等實例真正創建完成就會把earlysingletonObjects裡面的內容刪除)。

從bean的實例中獲取對象

在getBean方法中,getObjectForBeanInstance是個高頻率使用的方法,無論是從緩存中獲得bean還是根據不同的scope策略載入bean。總之,我們得到bean的實例後要做的第一步就是調用這個方法來檢測一下正確性,其實就是用於檢查當前bean是否是FactoryBean類型的bean,如果是,那麼需要調用該bean對應的FactoryBean實例中的getObject()作為返回值

接著進入getObjectFromFactoryBean方法

這個方法里只做了一件事情,就是返回的bean如果是單例的,那就必須要保證全局唯一,同事,也因為是單例的,所以不必重複創建,可以使用緩存來提高性能,也就是說已經載入過就要記錄下來以便於下次復用,否則的話就直接獲取了。

繼續跟進doGetObjectFromFactoryBean方法

最終調用的方法就是factory.getObject()方法,和factoryBean的定義一樣。

得到最終bean後,還有個方法調用,代碼如下

在這裡,我們要了解spring獲取bean的規則中有這樣一條:儘可能保證所有bean初始化後都會調用註冊的BeanPostProcessor的postProcessAfterInitialization方法進行處理,在實際開發過程中大可以針對此特性設計自己的業務邏輯

獲取單例

之前說過從緩存中獲取單例的過程,那麼如果緩存中不存在已經載入的單例bean就需要從頭開始bean的載入過程了,而spring中使用getSingleton的重載方法實現bean的載入過程

上述代碼中其實是使用了回調方法,使得程序可以在單例創建的前後做一些準備及處理操作,而真正的獲取單例bean的方法騎士並不是在此方法中實現的,其實邏輯是在ObjectFactory類型的實例singletonFactory中實現的。而這些準備及處理操作包括如下內容

1、檢查緩存是否載入過

2、若沒有載入,則記錄beanName的正在載入狀態

3、載入單例前記錄載入狀態

beforeSingletonCreation方法做了很重要的操作:記錄載入狀態,將當前正要創建的bean記錄在緩存中,這樣便可以對循環依賴進行檢測

4、通過調用參數傳入的ObjectFactory的個體Object方法實例化bean

5、載入單例後的處理方法調用

和步驟3的記錄載入狀態相似,當bean載入結束後需要移除緩存中對該bean的正在載入狀態的記錄

6、將結果記錄至緩存並刪除載入bean過程中所記錄的各種輔助狀態

7、返回處理結果

ObjectFactory的核心部分騎士只是調用了createBean的方法,所以我們還需要到createBean方法中追尋真理

準備創建bean

以上代碼的具體步驟:

1、根據設置的class屬性或者根據className來解析Class

2、對override屬性進行標記及驗證

spring沒有override-method這樣的配置,但是spring配置中存在lookup-method和replace-method的,而這兩個配置的載入騎士就是將配置統一存放在BeanDefinition中的methodOverrides屬性里,這個函數的操作就是針對這兩個配置的

3、應用初始化前的後處理器,解析指定bean是否存在初始化前的短路操作

4、創建bean

接下來是對override屬性標記及驗證的邏輯實現

在spring配置中存在lookup-method和replace-method兩個配置功能,而這兩個配置的載入其實就是將配置統一存放在BeanDefinition中的methodOverrides屬性里,這兩個功能實現原理其實是在bean實例化的時候如果檢測到存在methodOverrides屬性,會動態地為當前bean生成代理並使用對應的攔截器為bean做增強代理,相關邏輯在bean的實例化部分詳細說

這裡要提到的是,對於方法的匹配來講,如果一個類中存在若干個重載方法,那麼在函數調用及增強的時候還需要根據參數類型進行匹配,來最終確認當前調用的待地是哪個函數,但是,spring將一部分匹配工作在這裡完成了,如果當前類中的方法只有一個,那麼就設置重載該方法沒有被重載,這樣在後續調用的時候便可以直接使用找到的方法,而不需要進行方法的參數匹配驗證了,而且還可以提前對方法存在性進行驗證,可謂一箭雙鵰

實例化的前置處理

在真正調用doCreate方法創建bean的實例前使用了這個方法對BeanDefinigition中的屬性做些前置處理。當然,無論其中是否有相應的uoji實現我們都可以理解,因為真正邏輯實現前後留有處理函數也是可擴展的一種體現,但是,這並不是最重要的,在函數中還提供了一個短路判斷,這才送最關鍵的if(bean!=null) return bean;

當經過前置處理後返回的結果如果不為空,那麼會直接略過後續的bean的創建而直接返回結果。這一特性雖然很容易被忽略,但是卻起著至關重要的作用,我們熟知的AOP功能就是基於這裡的判斷的。

此方法中最吸引我們的是兩個方法applyBeanPostProcessorsBeforeInstantiation以及applyBeanPostProcessorsAfterInitialization。這兩個方法實現的非常簡單,無非是對後處理器中的所有InstantiationAwareBeanPostProcessor類型的後處理器進行postProcessBeforeInstantiation方法和BeanPostProcessor的postProcessAfterInitialization方法的調用

實例化前的後處理器應用

bean的實例化前調用,也就是將AbstractBeanDefinition轉換為BeanWrapper前的處理。給子類一個修改BeanDefinition的機會,也就是說當程序經過這個方法後,bean可能已經不是我們認為的bean了,而是或許成為了一個經過處理的代理bean,可能是通過cglib生成的,也可能是通過其他技術生成的,我們需要知道,在bean的實例化前會調用後處理器的方法進行處理

實例化後的後處理器應用

在講解從緩存中獲取單例bean的時候提到過,spring中的規則是在bean的初始化後儘可能保證將註冊的後處理器的postProcessAfterInitializtion方法應用到該bean中,因為如果返回的bean不為空,那麼便不會再次經歷普通bean的創建過程,所以只能在這裡應用後處理器的postProcessAfterInitializtion方法。

關於循環依賴,可以看Spring循環依賴這篇文章

當經理過resolveBeforeInstantiation方法後,程序有兩個選擇,如果創建了代理或者說重寫了InstantiationAwareBeanPostProcessor的postProcessBeforeInstantiation方法並在方法postProcessBeforeInstantiation中改變了bean,則直接返回就可以了,否則需要進行常規bean的創建。而這常規bean的創建就是在doCreateBean中完成的。

我們看看整個函數的概要思路

1、如果是單例則需要首先清除緩存

2、實例化bean,將BeanDefinition轉換為BeanWrapper

轉換是一個複雜的過程,但是我們可以嘗試概括大致的功能

如果存在工廠方法則使用工廠方法進行初始化

一個類有多個構造函數,每個構造函數都有不同的參數,所以需要根據參數鎖定構造函數並進行初始化

如果即不存在工廠方法也不存在帶有參數的構造函數,則使用默認的構造函數進行bean的實例化

3、MergedBeanDefinitionPostProcessor的應用

bean合併後的處理,Autowired註解正是通過此方法實現諸如類型的預解析

4、依賴處理

5、屬性填充。將所有屬性填充至bean的實例中

6、循環依賴檢查

之前有提到過,在Spring中解決循環依賴只對單例有效,而對於prototype的bean,Spring沒有好的解決辦法,唯一要做的就是拋出異常。在這個步驟裡面會檢測已經載入的bean是否已經出現了依賴循環,並判斷是否需要拋出異常。

7、註冊DisposableBean

如果配置了destroy-method,這裡需要註冊以便於在銷毀時候調用

8、完成創建並返回

可以看到上面的步驟非常的繁瑣,每一步驟都使用了大量的代碼來完成其功能,最複雜也是最難以理解的當屬循環依賴的處理。

未完待續。。。。


推薦閱讀:
相关文章