從原始碼看Spring中IOC容器的實現(二):IOC容器的初始化

xdCao發表於2019-04-28

初始化的三個主要過程

Spring中IOC容器的初始化是由refresh()方法來啟動的,該方法標誌著IOC容器的正式啟動。具體來說,這個啟動包括BeanDefinition的Resource定位、載入和註冊三個基本過程。

注意:這裡的初始化不包含Bean依賴注入的實現

Spring將上述三個過程分離,使用相應的ResourceLoader、BeanDefinitionReader等模組,這樣設計可以讓使用者更加靈活地對這三個過程進行剪裁或擴充套件,

  1. Resoure定位:即BeanDefinition的資源定位,由ResourceLoader通過統一的Resource介面來完成,Resource對各種形式的BeanDefinition的使用都提供了統一介面。
  2. BeanDefinition的載入:這個載入過程是把使用者定義好的Bean表示成IOC容器內部的資料結構,即BeanDefinition。
  3. BeanDefinition的註冊:通過呼叫BeanDefinitionRegistry介面的實現來完成。這個註冊過程把載入過程中解析得到的BeanDefinition向IOC容器中註冊,實際是注入到一個HashMap中。

BeanDefinition:

上一篇文章我們介紹了Spring提供的基本IOC容器的介面定義和實現.在這些Spring提供的基本IOC容器的介面定義和實現的基礎上,Spring通過定義BeanDefinition來管理基於Spring的應用中的各種物件以及他們之間的相互依賴關係。

BeanDefinition抽象了我們對Bean的定義,是讓容器起作用的主要資料型別。對IOC容器來說,BeanDefinition就是對依賴反轉模式中管理的物件依賴關係的資料抽象,也是容器實現依賴反轉功能的核心資料結構,依賴反轉功能都是圍繞對這個BeanDefinition的處理來完成的。

Resource定位

從原始碼看Spring中IOC容器的實現(二):IOC容器的初始化

以FileSystemXmlApplicationContext為例。其通過getResourceByPath方法載入xml,返回一個Resource。這裡使用了模板方法模式。

在FileResourceLoader中可以找到同樣的方法,而該方法來自於FileResourceLoader重寫其父類DefaultResourceLoader,該方法在ResourceLoader介面中定義,在FileResourceLoader和DefaultResourceLoader中都提供了各自的實現。而其呼叫在DefaultResourceLoader的getResource方法中:

從原始碼看Spring中IOC容器的實現(二):IOC容器的初始化

這裡確實是模板方法模式,子類覆蓋父類實現。而通過查詢該方法的呼叫,首先是看FileSystemXmlApplicationContext的loadBeanDefinitions方法,該方法在AbstractXmlApplicationContext中實現:

從原始碼看Spring中IOC容器的實現(二):IOC容器的初始化

此時已經在AbstractXmlApplicationContext中注入了對應的BeanDefinitionReader,再呼叫reader的loadBeanDefinitions方法,追進去看AbstractBeanDefinitionReader類,在這裡實現了:

從原始碼看Spring中IOC容器的實現(二):IOC容器的初始化

這裡可以看出在BeanDefinitionReader中注入了一個ResourceLoader,此處可以看到對ResourceLoader呼叫了getResource方法,這樣就可以完成對該方法的整個追溯過程了

要注意到FileSystemXmlApplicationContext本身就是一個DefaultResourceLoader的子類,其實FileSystemXmlApplicationContext中的getResource就是給父類作為模板方法呼叫。

從原始碼看Spring中IOC容器的實現(二):IOC容器的初始化

那麼FileSystemXmlApplicationContext是在什麼地方定義了BeanDefinition的讀入器BeanDefinitionReader並完成BeanDefinition的讀入呢?

BeanDefinition的載入

先回到IOC容器的初始化入口,也就是看一下refresh方法,這個方法的最初是在FileSystemXmlApplicationContext的建構函式中被呼叫的,它的呼叫標誌著容器初始化的開始,這些初始化物件就是BeanDefinition資料。

從原始碼看Spring中IOC容器的實現(二):IOC容器的初始化

對容器的啟動來說,refresh是一個很重要的方法。該方法在AbstractApplicationContext類中找到,它詳細的描述了整個ApplicationContext的初始化過程。

從原始碼看Spring中IOC容器的實現(二):IOC容器的初始化

從原始碼看Spring中IOC容器的實現(二):IOC容器的初始化

我們去看FileSystemXmlApplicationContext的基類AbstractRefreshableApplicationContext是怎樣實現的。 首先看他的refreshBeanFactory方法:

從原始碼看Spring中IOC容器的實現(二):IOC容器的初始化

首先建立其需要注入的DefaultListableBeanFactory,接著呼叫loadBeanDefinitions來載入資源進入其所持有的BeanFactory,而這個方法在該類中是一個空實現,留給子類去做對應的實現。 而在AbstractXMLApplicationContext中,我們找到了對應的具體實現:

從原始碼看Spring中IOC容器的實現(二):IOC容器的初始化

這裡可以清楚的看到讀入器的建立和配置,這裡AbstractXMLApplicationContext將DefaultListableBeanFactory作為引數注入了BeanDefinitionReader,但注意,其注入是以BeanDefinitionRegistry介面的形式:

從原始碼看Spring中IOC容器的實現(二):IOC容器的初始化

父類AbstractBeanDefinitionReader構造器:

從原始碼看Spring中IOC容器的實現(二):IOC容器的初始化

如果傳入的BeanFactory只實現了BeanDefinitionRegistry,那就注入,如果還實現了ResourceLoader,就把它作為預設的ResourceLoader,如果實現了EnvironmentCapable,會把其內部的環境也載入進來。 然後我們回過來繼續看其子類XMLBeanDefinitionReader,其過載了loadBeanDefinitions方法,不使用父類的實現

從原始碼看Spring中IOC容器的實現(二):IOC容器的初始化

這裡呼叫了doLoadBeanDefinition方法:

從原始碼看Spring中IOC容器的實現(二):IOC容器的初始化

該方法一共就兩步,一步是載入dom,另一步是從dom將BeanDefinition註冊到BeanFactory中。

從原始碼看Spring中IOC容器的實現(二):IOC容器的初始化

在註冊的方法中,是通過呼叫BeanDefinitionDocumentReader進行的,實際呼叫的是其實現類DefaultBeanDefinitionDocumentReader,在其方法中又是通過其持有的BeanDefinitionParserDelegate進行的。

從原始碼看Spring中IOC容器的實現(二):IOC容器的初始化

總結一下流程:可以看到,在初始化FileSystemXMLApplicationContext的過程中是通過呼叫IOC容器的refresh來啟動整個BeanDefinition的載入過程的,這個初始化是通過定義的XMLBeanDefinitionReader來完成。同時,我們也知道實際使用的IOC容器是DefaultListableBeanFactory,具體的Resource載入在XMLBeanDefinitionReader讀入BeanDefinition時實現。在XMLBeanDefinitionReader的實現中可以看到,是在reader.loadBeanDefinition中開始進行BeanDefinition的載入,而這時其父類AbstractBeanDefinitionReader已經為BeanDefinition的載入做好了準備。

從原始碼看Spring中IOC容器的實現(二):IOC容器的初始化

這裡呼叫的是loadBeanDefinitions(Resource res)方法,但這個方法在該類中是沒有實現的,具體實現在XMLBeanDefinitionReader中。在讀取器中,需要得到代表 xml檔案的Resource,因為這個Resource物件封裝了對xml檔案的I/O操作,所以讀取器可以在開啟IO流後得到xml的檔案物件,有了這個檔案物件後,就可以按照Spring的Bean定義規則來對這個xml的文件樹進行解析。

BeanDefinition的載入分成兩部分,首先通過呼叫xml的解析器得到document物件,但這些物件並沒有按照Spring的Bean規則進行解析。在完成通用的xml解析以後,才是按照Spring的Bean規則進行解析的地方,這個按照Spring的Bean規則進行解析的過程是在DocumentReader中實現的。這裡使用的是預設的DefaultBeanDefinitionDocumentReader。這個DefaultBeanDefinitionDocumentReader的建立是在後面的方法中完成的,然後再完成BeanDefinition的處理,處理的結果由BeanDefinitionHolder物件來持有。這個BeanDefinitionHolder除了持有BeanDefinition物件外,還持有其他與BeanDefinition的使用相關的資訊,比如Bean的名字、別名集合等。這個BeanDefinitionHolder的生成是通過對Document文件樹的內容解析來完成的,可以看到這個解析過程是由BeanDefinitionParserDelegate來實現(具體在上面的processBeanDefinition方法中)。

解析過程: 呼叫delegate的parseBeanDefinitionElement方法:

從原始碼看Spring中IOC容器的實現(二):IOC容器的初始化

在高亮的這一行進行對bean元素的詳細解析:

從原始碼看Spring中IOC容器的實現(二):IOC容器的初始化

再往裡面就是各種元素的解析了,太細了以後慢慢看。

經過這樣逐層的解析,我們在xml檔案中定義的BeanDefinition就被整個載入到了IOC容器中,並在容器中建立了資料對映。在IOC容器中建立了對應的資料結構,或者說可以看成是pojo物件在IOC容器中的抽象,這些資料結構可以以AbstractBeanDefinition為入口,讓IOC容器執行索引、查詢和操作。經過以上過程,IOC容器大致完成了管理Bean物件的資料準備工作。但是,重要的依賴注入實際上在這個時候還沒發生。現在IOC容器BeanDefinition中存在的還只是一些靜態的配置資訊。嚴格的說這時候容器還沒有完全起作用,要完全發揮容器的作用,還需完成資料向容器的註冊。

BeanDefinition的註冊

在BeanDefinition的載入和解析完成後,使用者定義的BeanDefinition資訊已經在IOC容器內建立了自己的資料結構以及相應的資料表示,但此時這些資料還不能供IOC容器直接使用,需要在IOC中對這些BeanDefinition資料進行註冊。在DefaultListableBeanFactory中,是通過一個HashMap來持有載入的BeanDefinition的,這個HashMap的定義在DefaultListableBeanFactory中可以看到。

從原始碼看Spring中IOC容器的實現(二):IOC容器的初始化

將解析得到的BeanDefinition向IOC容器中的map註冊的過程是在載入BeanDefinition完成後進行的,呼叫過程如下:

從原始碼看Spring中IOC容器的實現(二):IOC容器的初始化

具體實現: DefaultBeanDefinitionDocumentReader中:

從原始碼看Spring中IOC容器的實現(二):IOC容器的初始化

BeanDefinitionReaderUtils中:

從原始碼看Spring中IOC容器的實現(二):IOC容器的初始化

呼叫了注入的registry(即DefaultListableBeanFactory): 看DefaultListableBeanFactory中的registerBeanDefinition方法:

從原始碼看Spring中IOC容器的實現(二):IOC容器的初始化
從原始碼看Spring中IOC容器的實現(二):IOC容器的初始化

到這裡註冊就已經完成了。

相關文章