Spring原始碼解析--《SPRING技術內幕:深入解析Spring架構與設計原理》讀書筆記(一):IOC容器初始化過程
通過閱讀相關章節內容,Spring中IOC容器的載入中,我們需要了解下列幾個概念:
- Resource:是一個定位、訪問資源的抽象介面,包含了多種資源操作的基礎方法定義,如
getInputStream()
、exists()
、isOpen()
、getDescription()
等。- BeanDefinition:POJO物件在IOC容器中的抽象,通過此資料結構,使IOC容器能方便地對POJO物件進行管理,其中可以設定Bean的一些屬性,如:
scope
、beanClassName
、lazyInit
、dependentsOn
等。- BeanFactory:基礎容器定義介面,Spring IOC 容器的一個最基礎的行為定義的介面,定義了一個IOC容器所需要的最基礎的行為規範,包括:
getBean()
等。- ApplicationContext:高階容器定義介面,在BeanFactory的基礎上,添加了一些擴充套件功能,如,
ResoruceLoader
、MessageSource
、ApplicationEventPublisher
等。
一、以程式設計的方式使用IOC容器
ClassPathResource res = new ClassPathResource("bean.xml");
DefaultListableBeanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader();
reader.loadBeanDefinition(res);
從上面的程式碼清單可看出,程式設計方式使用一個IOC容器的步驟主要如下:
- 建立IOC配置檔案的抽象資源,包含對BeanDefinition的定義
- 建立BeanFactory
- 建立一個載入BeanDefinition的讀取器,通過一個回撥配置給BeanFactory
- 從定義好的資源位置讀取配置資訊,具體解析過程由DefinitionReader來完成。
完成這些步驟後,就可以直接使用IOC容器了。
二、ApplicationContext容器設計原理
應用上下文的主要功能都在Abstract__ApplicationContext
基類中實現了,而一個具體的應用上下文只需要實現和它自身設計相關的兩個功能:
1、啟動IOC容器的refresh()
(通常在建構函式中)過程。
2、從檔案系統中載入Bean的定義資源,使用getResourceByPath()
得到資源定位,如FileSystemXmlApplicationContext
中使用getResourceByPath
獲取一個FileSystemResource
。
三、IOC容器初始化過程
由refresh()
啟動,整個過程可以拆分為三個部分:
- BeanDefinition的Resource定位
- BeanDefinition的Resource載入
- BeanDefinition的Resource註冊
下述過程以FileSystemApplicationContext
為例,分析IOC容器的初始化過程。
1、 Resource定位
由ResourceLoader
通過統一的Resource
介面完成,對各種形式的BeanDefinition提供了同一介面。此過程類似於容器尋找資料的過程。
資源定位的整個過程可總結為:
- FileSystemXmlApplicationContext建構函式呼叫
AbstractApplicationContext
中的refresh()
方法;- refresh中呼叫
AbstractApplicationContext
中的obtainFreshBeanFactory()
方法;- obtainFreshBeanFactory中呼叫
AbstractRefreshableApplicationContext
中的refreshBeanFactory()
方法;- 在
refreshBeanFactory()
中包含下述幾步操作:
- 判斷若已經建立了BeanFactory,則銷燬並關閉它;
- 通過
createBeanFactory()
建立一個DefaultListableBeanFactory
,呼叫AbstractRefreshableApplicationContext
中的loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
,此為一抽象方法,用於將BeanDefinition裝入BeanFactory中,其具體實現委託給一個或多個bean definition readerAbstractBeanDefinitionReader
中的一種過載方法loadBeanDefinitions(String location, Set<Resource> actualResources)
,其通過判斷ResourceLoader型別,使用不同的getResource()方法獲取Resource- 以
DefaultResourceLoader
中的getResource()
為例,其中根據傳入的loacation字串的格式進行分別處理:1、若以”/”開頭,作為相對路徑處理2、若以”classpath:”開頭,作為類路徑處理3、若為URL資源路徑,作為URL資源路徑處理4、若都不是,則委託給子類的getResourceByPath(location)
方法實現。其中上述所有處理都是對path進行解析,返回一個Resource物件;
定位完成後為BeanDefinition的載入創造了I/O操作的條件,但是資料還沒有開始讀入。
2、BeanDefinition載入
把使用者定義好的Bean表示成IOC容器內部的資料結構,即BeanDefinition
。下面,以DefaultListableBeanFactory
為例,分析IOC容器完成BeanDefinition載入的過程。
BeanDefinition載入的過程可分為兩個部分:通過XML解析器得到document物件
和按照Spring的bean規則解析document物件
1)通過XML解析器得到document物件
refresh()
方法詳細地描述了整個ApplicationContext的初始化過程,如BeanFactory的更新
、MessageSource和PostProcessor的註冊
等。
createBeanFactory()
方法中呼叫的loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
方法啟動對BeanDefinition的載入,它是一個抽象方法,用於將BeanDefinition裝入BeanFactory中,其具體實現委託給一個或多個bean definition reader實際載入過程在
AbstractXmlApplicationContext
中實現:
- 初始化讀取器XmlBeanDefinitionReader
- 然後通過回撥方式將讀取器在IOC容器中設定好
- 啟動讀取器完成BeanDefinition在IOC容器中的載入
上面過程呼叫的
loadBeanDefinitions(beanDefinitionReader)
具體實現內容如下:
- 獲取BeanDefinition資訊的Resource定位
- 呼叫
XmlBeanDefinitionReader
讀取,具體載入過程委託給BeanDefinitionReader
完成- 使用
XmlBeanDefinitionReader
載入BeanDefinition到容器中
reader.loadBeanDefinitions
中開始進行BeanDefinition的載入,此時,其XmlBeanDefinitionReader
父類AbstractbeanDefinitionReader
已經為BeanDefinition的載入做好了準備:
- 上面的
`loadBeanDefinitions(Resource resource)
是一個介面方法,具體實現在XmlBeanDefinitionReader
中:
- 得到代表XML檔案的Resource,其中封裝了對XML檔案的I/O操作,使讀取器可以在開啟I/O流後得到XML檔案物件
- 按照Spring的Bean定義規則開啟這個XML文件樹進行解析,解析過程交給
BeanDefinitionParserDelegate
來完成
ps: 獲取Document物件的過程在DefaultDocumentLoader
中實現。
ps:
上述過程使用的是XML方式定義的BeanDefinition,故使用XmlBeanDefinitionReader
,若使用其他方式的BeanDefinition,則需要使用對應種類的BeanDefinitionReader
來完成載入工作
2)按照Spring的bean規則解析document物件
Spring的BeanDefinition按照Spring的Bean語義要求進行解析並轉化為容器內部資料結構的過程(同時包含對載入的Bean數量進行統計)由registerBeanDefinitions(doc, resource)
完成。
- 使用
documentReader
按照Spring的Bean規則document物件,此處使用預設設定的DefaultBeanDefinitionDocumentReader
進行解析
ps:下述過程由本人蔘照4.3.11原始碼進行總結,與原書中有差異,感興趣的讀者可自行獲取4.3.11原始碼進行驗證。
registerBeanDefinitions(Document doc, XmlReaderContext readerContext)
中呼叫doRegisterBeanDefinitions(Element root)
根據root下的element註冊bean
- 呼叫
parseBeanDefinitions(root, this.delegate)
解析document中root級別目錄要素:"import"
、"alias"
、"bean"
。
ps:此處我們著重看預設元素中<bean>
元素的解析過程。其他元素解析感興趣的同學可以自行查閱spring4.3.11原始碼中DefaultBeanDefinitionDocumentReader
內容
BeanDefinitionParserDelegate
中的parseCustomElement(Element ele)
解析自定義元素,此處就不做詳細跟蹤研究了,感興趣的看官可以自行去原始碼中研究DefaultBeanDefinitionDocumentReader
中的parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate)
解析預設元素
processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate)
完成BeanDefinition的處理,並將處理結果交由BeanDefinitionHolder
物件持有,BeanDefinitionHolder
物件還持有其他與BeanDefinition
物件使用相關的資訊,如:Bean的名字
、別名集合
等
BeanDefinitionParserDelegate
中的parseBeanDefinitionElement(Element ele, BeanDefinition containingBean)
完成具體的Spring BeanDefinition解析
parseBeanDefinitionElement(
對BeanDefinition中定義的元素進行詳細處理,如:
Element ele, String beanName, BeanDefinition containingBean)attribute值處理
ps:這裡我們跟蹤一下bean中property屬性的解析處理,從而瞭解解析元素的具體操作parsePropertyElements(Element beanEle, BeanDefinition bd)
獲取bean下property元素並啟動解析過程
parsePropertyElement(Element ele, BeanDefinition bd)
開啟value(結果由PropertyValue持有)和meta的詳細解析,並將解析結果轉載到BeanDefinition中
parsePropertyValue(Element ele, BeanDefinition bd, String propertyName)
解析property元素的值:value、ref、子元素
parsePropertySubElement(Element ele, BeanDefinition bd, String defaultValueType)
解析property下的子元素
- 這裡以解析list為例,看看對於property下的子元素的解析過程
其中具體的List中元素的解析如下
至此,xml檔案中定義的BeanDefinition就被整個載入到IOC容器中,並在容器中建立了資料對映,即將POJO抽象到了IOC容器中。從而,可以以AbstractBeanDefinition為入口,讓IOC容器執行索引
、查詢
、操作
。以上工作使IOC容器大致完成了管理Bean物件的資料準備工作(初始化過程)。由於依賴注入
在此時還沒有發生,在IOC的BeanDefinition中存在的還只是一些靜態配置資訊,若要完全發揮容器作用,需要完成下述資料向容器註冊的過程。
3、向IOC容器註冊BeanDefinition
通過呼叫DefaultListableBeanFactory
中實現BeanDefinitionRegistry
介面的registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
方法完成。此過程把載入過程中解析得到的BeanDefinition向IOC容器進行註冊。
__ps:__IOC容器內部將BeanDefinition注入到DefaultListableBeanFactory
中建立的一個hashMap中:
/** Map of singleton and non-singleton bean names, keyed by dependency type */
private final Map<Class<?>, String[]> allBeanNamesByType = new ConcurrentHashMap<Class<?>, String[]>(64);
其中註冊的具體實現如下:
+ 完整的程式碼清單我這裡就不截取了,只放幾個關鍵點的程式碼截圖
- 檢查ioc容器中是否已有同名BeanDefiniton註冊:
- 正常註冊一個bean的過程:
至此,IOC容器初始化的過程就全部完成了,此時,在使用的IOC容器DefinitionBeanFactory中已經建立了整個Bean的配置資訊,而且這些BeanDefinition已經可以被容器使用,他們都在beanDefinitionmap裡被檢索使用。容器的作用就是對這些資訊進行處理和維護。這些資訊是容器建立依賴反轉的基礎。
注意:此處所說的IOC容器初始化過程中不包括Bean依賴注入的實現。在Spring IOC的設計中,Bean的定義載入和依賴注入是兩個獨立的過程。依賴注入一般發生在應用第一次通過getBean向容器索取Bean的時候。但是若配置了lazyinit屬性,Bean的依賴注入在IOC容器初始化時就完成了。