天天看點

spring IOC初始化過程

· IoC是如何工作的?

· Resource定位

· 載入BeanDefinition

· 将BeanDefiniton注冊到容器

如圖1所示,通過ApplicationContext建立Spring容器,該容器會讀取配置檔案"/beans.xml",并統一管理由該檔案中定義好的bean執行個體對象,如果要擷取某個bean執行個體,使用getBean方法就行了。假設将User配置在beans.xml檔案中,之後不需使用new User()的方式建立執行個體,而是通過ApplicationContext容器來擷取User的執行個體。

spring IOC初始化過程

圖1 通過spring容器建立執行個體

下面就來刨析建立IoC容器經曆的幾個階段:Resource定位、載入BeanDefinition、将BeanDefiniton注冊到容器。

Resource是Spring中用于封裝I/O操作的接口。在建立Spring容器時,會去通路XML配置檔案,還可以通過檔案類型、二進制流、URL等方式通路資源。這些都可以了解為Resource,其體系結構如圖2所示:

· FileSystemResource:以檔案絕對路徑進行資源通路。

· ClassPathResourcee:以類路徑的方式通路資源。

· ServletContextResource:web應用根目錄的方式通路資源。

· UrlResource:通路網絡資源的實作類。  

· ByteArrayResource: 通路位元組數組資源的實作類。

spring IOC初始化過程

圖2 Resouce資源通路類型

那麼這些類型在Spring中是如何通路的呢?Spring提供了ResourceLoader接口用于實作不同的Resource加載政策,該接口的執行個體對象中可以擷取一個resource對象。如圖3所示,在ResourceLoader接口中隻定義了兩個方法:

spring IOC初始化過程

圖3 ResourceLoader接口中的兩個方法

注:ApplicationContext的所有實作類都實作RecourceLoader接口,是以可以直接調用getResource(參數)擷取Resoure對象。不同的ApplicatonContext實作類使用getResource方法取得的資源類型不同,例如:FileSystemXmlApplicationContext.getResource擷取的就是FileSystemResource執行個體;ClassPathXmlApplicationContext.getResource擷取的就是ClassPathResource執行個體;

XmlWebApplicationContext.getResource擷取的就是ServletContextResource執行個體,另外像不需要通過xml直接使用注解@Configuation方式加載資源的AnnotationConfigApplicationContext等等。

在資源定位過程完成以後,就為資源檔案中的bean的載入創造了I/O操作的條件,如何讀取資源中的資料将會在下一步介紹的BeanDefinition的載入過程中描述。

BeanDefinition是一個資料結構,BeanDefinition是根據resource對象中的bean來生成的。bean會在Spring IoC容器内部以BeanDefintion的形式存在,IoC容器對bean的管理和依賴注入的實作是通過操作BeanDefinition來完成的。BeanDefinition就是Bean在IoC容器中的存在形式。

由于Spring的配置檔案主要是XML格式,一般而言會使用到AbstractXmlApplicationContext類進行檔案的讀取,如圖4所示,該類定義了一個名為loadBeanDefinitions(DefaultListableBeanFactory beanFactory) 的方法用于擷取BeanDefinition。

方法體内會new一個BeanDefinitionReader對象,然後将生成的執行個體傳入loadBeanDefintions方法。

圖4 AbstractXmlApplicationContext

接下來以XmlBeanDefinitionReader對象載入BeanDefinition為例,如圖5所示,調用loadBeanDefinitions方法傳入對象,分别加載configResources(定位到的resource資源位置)和configLocation(本地配置檔案的位置)。也就是将使用者定義的資源以及容器本身需要的資源全部加載到reader中。

spring IOC初始化過程

圖5 loadBeanDefinitions方法

順着看reader中的loadBeanDefinitions方法,該方法override了AbstractBeanDefinitionReader類,父接口的BeanDefinitionReader。方法體中,将所有資源全部加載,并且交給AbstractBeanDefinitionReader的實作子類處理這些resource。

spring IOC初始化過程

圖6 reader 中的loadBeanDefinitions

如圖7所示,BeanDefinitionReader接口定義了 int loadBeanDefinitions(Resource resource)方法。

圖7 BeanDefinitionReader接口定義的方法。

此時回到XmlBeanDefinitionReader上來,它主要針對XML方式的Bean進行讀取,XmlBeanDefinitionReader主要是實作了AbstractBeanDefinitionReader抽象類,而該類繼承與BeanDefinitionReader,主要實作的方法也是來自于BeanDefinitionReader 的loadBeanDefintions(Resource)方法。

spring IOC初始化過程

圖8 XmlBeanDefinitionReader 繼承關系圖

如圖9所示,讀取Bean之後就是加載Bean的過程了,XmlBeanDefinitionReader中的doLoadBeanDefinitions方法主要來處理加載Bean的工作。首先對資源進行驗證,然後從資源對象中加載Document對象,使用了documentLoader中的loadDocument方法,然後跟上registerBeanDefinitions對文檔對應的resource進行注冊,也就是将XML檔案中的Bean轉換成容器中的BeanDefinition。

spring IOC初始化過程

圖9 doLoadBeanDefinitions方法

如圖10所示,接下來就是registerBeanDefinitions方法了,它主要對Spring Bean語義進行轉化,變成BeanDefintion類型。首先擷取DefaultBeanDefinitionDocumentReader執行個體,然後擷取容器中的bean數量,通過documentReader中的registerBeanDefinitions方法進行注冊和轉化工作。

spring IOC初始化過程

圖10 registerBeanDefintions

順着上面的思路繼續往下,在DefaultBeanDefinitionDocumentReader 中的registerBeanDefinitions 方法如圖11所示,其擷取document的根結點然後順勢通路所有的子節點。同時把處理BeanDefinition的過程委托給BeanDefinitionParserDelegate對象來完成。

spring IOC初始化過程

圖11 DefaultBeanDefinitionDocumentReader 中的registerBeanDefinitions 方法

BeanDefinitionParserDelegate類主要負責BeanDefinition的解析,這裡涉及到JDK和CGLIB動态代理的知識,這裡留一個懸念我們後面的章節會深入介紹。BeanDefinitionParserDelegate代理類會完成對符合Spring Bean語義規則的處理,比如<bean></bean>、<import></import>、<alias><alias/>等的檢測。如圖12 所示,就是BeanDefinitionParserDelegate代理類中的parseBeanDefinitions方法,用來對XML檔案中的節點進行解析。通過周遊import标簽節點調用importBeanDefinitionResource方法對其進行處理,然後接着便利bean節點調用processBeanDefinition對其處理。

spring IOC初始化過程

圖12 parseBeanDefinitions 方法

如圖13 再看parseBeanDefinitions方法中調用的parseDefaultElement方法,顧名思義它是對節點元素進行處理的。從方法體的語句可以看出它對import标簽、alias标簽、bean标簽進行了處理。每類标簽對應不同的BeanDefinition的處理方法。

spring IOC初始化過程

圖13 parseDefaultElement 方法

在parseDefaultElement調用的衆多方法中,我們選取processBeanDefinition方法給大家講解,如圖14 所示,該方法是用來處理Bean的。首先通過delegate的parseBeanDefinitionElement方法傳入節點資訊,擷取該Bean對應的name和alias。然後通過BeanDefinitionReaderUtils中的registerBeanDefinition方法對其進行容器注冊,也就是将Bean執行個體注冊到容器中進行管理。最後,發送注冊事件。

spring IOC初始化過程

圖14 processBeanDefinition 方法

至此完成了BeanDefinition的加載工作。

在加載了Bean之後,就需要将其注冊到容器中盡心管理。如圖15所示,Bean會被解析成BeanDefinition并與BeanName、Alias一同封裝到BeanDefinitionHolder類中, 之後beanFactory.registerBeanDefinition(beanName, bdHolder.getBeanDefinition()),注冊到DefaultListableBeanFactory.beanDefinitionMap中。如果用戶端需要擷取Bean對象,Spring容器會根據注冊的BeanDefinition資訊進行執行個體化。

spring IOC初始化過程

圖15 registerBeanDefinition

DefaultListableBeanFactory實作了上面調用BeanDefinitionRegistry接口的 registerBeanDefinition( beanName,  bdHolder.getBeanDefinition())方法。如圖16所示,這一部分的主要邏輯是向DefaultListableBeanFactory對象的beanDefinitionMap中存放beanDefinition,也就是說beanDefinition都放在beanDefinitionMap中進行管理。當初始化容器進行bean初始化時,在bean的生命周期分析裡必然會在這個beanDefinitionMap中擷取beanDefition執行個體。

spring IOC初始化過程

圖16 registerBeanDefinition方法

主要介紹了Spring IOC容器初始化的過程,包括Resource定位:通過檔案路徑、類路徑、web路徑等方式擷取Bean資訊;載入BeanDefinition:介紹的是如何将Bean載入到IoC中形成BeanDefinition的整個過程;将BeanDefinition注冊到容器。