1. 程式人生 > >Spring原始碼解析--《SPRING技術內幕:深入解析Spring架構與設計原理》讀書筆記(一):IOC容器初始化過程

Spring原始碼解析--《SPRING技術內幕:深入解析Spring架構與設計原理》讀書筆記(一):IOC容器初始化過程

通過閱讀相關章節內容,Spring中IOC容器的載入中,我們需要了解下列幾個概念:

  • Resource:是一個定位、訪問資源的抽象介面,包含了多種資源操作的基礎方法定義,如getInputStream()exists()isOpen()getDescription()等。
  • BeanDefinition:POJO物件在IOC容器中的抽象,通過此資料結構,使IOC容器能方便地對POJO物件進行管理,其中可以設定Bean的一些屬性,如:scopebeanClassNamelazyInitdependentsOn等。
  • BeanFactory:基礎容器定義介面,Spring IOC 容器的一個最基礎的行為定義的介面,定義了一個IOC容器所需要的最基礎的行為規範,包括:getBean()
    等。
  • ApplicationContext:高階容器定義介面,在BeanFactory的基礎上,添加了一些擴充套件功能,如,ResoruceLoaderMessageSourceApplicationEventPublisher等。

一、以程式設計的方式使用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 reader
  • AbstractBeanDefinitionReader中的一種過載方法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的註冊等。
    refresh方法詳情
  • 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的載入做好了準備:
      父類準備載入BeanDefinition
    • 上面的`loadBeanDefinitions(Resource resource)是一個介面方法,具體實現在XmlBeanDefinitionReader中:
    • 得到代表XML檔案的Resource,其中封裝了對XML檔案的I/O操作,使讀取器可以在開啟I/O流後得到XML檔案物件
      載入XML形式的BeanDefinition
      準備讀取I/O的InputSource
    • 按照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)完成。
registerBeanDefinitions程式碼清單


  • 使用documentReader按照Spring的Bean規則document物件,此處使用預設設定的DefaultBeanDefinitionDocumentReader進行解析
    DefaultBeanDefinitionDocumentReader程式碼清單
    ps:下述過程由本人蔘照4.3.11原始碼進行總結,與原書中有差異,感興趣的讀者可自行獲取4.3.11原始碼進行驗證。

  • registerBeanDefinitions(Document doc, XmlReaderContext readerContext)中呼叫doRegisterBeanDefinitions(Element root)根據root下的element註冊bean
    doRegisterbeanDefinitions程式碼清單
  • 呼叫parseBeanDefinitions(root, this.delegate)解析document中root級別目錄要素:"import""alias""bean"
    解析element
    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的名字別名集合
    解析bean元素
  • BeanDefinitionParserDelegate中的parseBeanDefinitionElement(Element ele, BeanDefinition containingBean)完成具體的Spring BeanDefinition解析
    bean元素具體解析過程
  • parseBeanDefinitionElement(
    Element ele, String beanName, BeanDefinition containingBean)
    對BeanDefinition中定義的元素進行詳細處理,如:attribute值處理
    bean中定義元素詳細處理
    ps:這裡我們跟蹤一下bean中property屬性的解析處理,從而瞭解解析元素的具體操作
  • parsePropertyElements(Element beanEle, BeanDefinition bd)獲取bean下property元素並啟動解析過程
    獲取property元素並啟動解析
  • parsePropertyElement(Element ele, BeanDefinition bd)開啟value(結果由PropertyValue持有)和meta的詳細解析,並將解析結果轉載到BeanDefinition中
    這裡寫圖片描述
  • parsePropertyValue(Element ele, BeanDefinition bd, String propertyName)解析property元素的值:value、ref、子元素
    詳細解析property屬性
  • parsePropertySubElement(Element ele, BeanDefinition bd, String defaultValueType)解析property下的子元素
    解析property下子元素-1
    解析property下子元素-2
  • 這裡以解析list為例,看看對於property下的子元素的解析過程
    List解析過程
    其中具體的List中元素的解析如下
    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的過程:
    正常註冊bean的過程

至此,IOC容器初始化的過程就全部完成了,此時,在使用的IOC容器DefinitionBeanFactory中已經建立了整個Bean的配置資訊,而且這些BeanDefinition已經可以被容器使用,他們都在beanDefinitionmap裡被檢索使用。容器的作用就是對這些資訊進行處理和維護。這些資訊是容器建立依賴反轉的基礎
注意:此處所說的IOC容器初始化過程中不包括Bean依賴注入的實現。在Spring IOC的設計中,Bean的定義載入和依賴注入是兩個獨立的過程。依賴注入一般發生在應用第一次通過getBean向容器索取Bean的時候。但是若配置了lazyinit屬性,Bean的依賴注入在IOC容器初始化時就完成了