Spring原始碼解析02:Spring IOC容器之XmlBeanFactory啟動流程分析和原始碼解析

你好,舊時光發表於2020-05-18

一. 前言

Spring容器主要分為兩類BeanFactory和ApplicationContext,後者是基於前者的功能擴充套件,也就是一個基礎容器和一個高階容器的區別。本篇就以BeanFactory基礎容器介面的預設實現類XmlBeanFactory啟動流程分析來入門Spring原始碼的學習。

二. 概念要點

1. 概念定義

  • BeanDefinition:Bean後設資料描述,Bean在Spring IOC容器中的抽象,是Spring的一個核心概念
  • DefaultListableBeanFactory : Spring IOC容器的實現,可以作為一個獨立使用的容器, Spring IOC容器的始祖
  • XmlBeanFactory:繼承自DefaultListableBeanFactory,與其不同點在於XmlBeanFactory中使用了自定義的XML讀取器XmlBeanDefinitionReader,實現了個性化的BeanDefinitionReader讀取
  • ApplicationContext: 高階容器定義介面,基於BeanFactory新增了擴充套件功能,如ResourceLoader、MessageSource、ApplicationEventPublisher等

2. 糟糕!XmlBeanFactory被廢棄了

對Spring有些瞭解的應該XmlBeanFactory已經過時了。沒錯,本篇要講的XmlBeanFactory在Spring3.1這個很久遠版本就開始過時了。

@deprecated as of Spring 3.1 in favor of {@link DefaultListableBeanFactory}

取而代之的寫法如下

ClassPathResource resource=new ClassPathResource("spring-config.xml");
DefaultListableBeanFactory beanFactory=new DefaultListableBeanFactory();
XmlBeanDefinitionReader beanDefinitionReader=new XmlBeanDefinitionReader(beanFactory);
beanDefinitionReader.loadBeanDefinitions(resource);

概括DefaultListableBeanFactory + XmlBeanDefinitionReader 取代了 XmlBeanFactory容器的建立和初始化,可以聯想到通過組合的方式靈活度是比XmlBeanFactory高的,針對不同的資源讀取組合的方式只需替換不同的讀取器BeanDefinitionReader就可以了,而XmlBeanFactory中的讀取器XmlBeanDefinitionReader限定其只能讀取XML格式的檔案資源,所以至於XmlBeanFactory被廢棄的原因可想而知。

3. XmlBeanFactory?!XML,你會XML解析嗎?

<?xml version="1.0" encoding=" UTF-8" standalone="yes"?><root>
<code>0</code>
<message>呼叫成功</message>
</root>

附上DOM4J解析的程式碼

String xml="<?xml version=\"1.0\" encoding=\" UTF-8\" standalone=\"yes\"?><root>\n" +
                "<code>0</code>\n" +
                "<message>呼叫成功</message>\n" +
                "</root>";
Document document = DocumentHelper.parseText(xml);
Element root = document.getRootElement();
String code = root.elementText("code");
String message =root.elementText("message");

XML文件被解析成DOM樹,其中Document是整個DOM的根節點,root為根元素,由根元素一層一層向下解析element元素,容器啟動解析XML流程就是這樣。

三. XmlBeanFactory啟動流程分析

XmlBeanFactory容器啟動就兩行程式碼

ClassPathResource resource = new ClassPathResource("spring-config.xml");
XmlBeanFactory beanFactory = new XmlBeanFactory(resource);

怎麼樣?看似簡單就證明了Java封裝的強大,但背後藏了太多。 這裡就送上XmlBeanFactory啟動流程圖,對應的就是上面的兩行程式碼。

這是啥?!看得頭暈的看官老爺們別急著給差評啊(瑟瑟發抖中)。這裡精簡下,想快速理解的話直接看精簡版後的,想深入到細節建議看下上面的時序圖。 整個就是bean的載入階段。通過解析XML中的標籤元素生成beanDefinition註冊到beanDefinitionMap中。

四. XmlBeanFactory啟動原始碼解析

按照XmlBeanFactory啟動流程的先後順序整理的關鍵性程式碼索引列表,其中一級索引為類,二級索引對應其類下的方法。符號 ---▷表示介面的實現。建議可以觀察方法索引的引數變化(資源轉換)來分析整個流程,來加深對流程的理解。

  1. XmlBeanFactory

    1. XmlBeanFactory(Resource resource)
  2. XmlBeanDefinitionReader

    1. loadBeanDefinitions(Resource resource)
    2. doLoadBeanDefinitions(InputSource inputSource, Resource resource)
    3. registerBeanDefinitions(Document doc, Resource resource)
  3. DefaultBeanDefinitionDocumentReader ---▷ BeanDefinitionDocumentReader

    1. registerBeanDefinitions(Document doc, XmlReaderContext readerContext)
    2. doRegisterBeanDefinitions(Element root)
    3. parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate)
    4. parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate)
    5. processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate)
  4. BeanDefinitionParserDelegate

    1. parseBeanDefinitionElement(Element ele)
    2. decorateBeanDefinitionIfRequired( Element ele, BeanDefinitionHolder originalDef,...)
  5. DefaultListableBeanFactory ---▷ BeanDefinitionRegistry

    1. registerBeanDefinition(String beanName, BeanDefinition beanDefinition)

好了,當你心裡對這個流程有個大概的樣子之後,這裡就可以繼續走下去。建議還是很模糊的同學自重,避免走火入魔想對我人身攻擊的。下面就每個方法重要的點一一解析,嚴格按照方法的執行先後順序來說明。

1.1 XmlBeanFactory(Resource resource)

功能概述: XmlBeanFactory的構造方法,整個容器啟動的入口,完成bean工廠的例項化和BeanDefinition載入(解析和註冊)。

/**
 * XmlBeanFactory
 **/

public XmlBeanFactory(Resource resource) throws BeansException {
    this(resource, null);
}

public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException  {
 super(parentBeanFactory); 
 // 載入(解析註冊)BeanDefinition
 this.reader.loadBeanDefinitions(resource);
}

知識點:

  1. Resource:Resource介面是Spring資源訪問策略的抽象,,而具體的資源訪問方式由其實現類完成,如類路徑資源(ClassPathResource)、檔案(FileSystemResource)、URL資源(UrlResource)、InputStream資源(InputStreamResource)、Byte陣列(ByteArrayResource),根據不同的型別的資源使用對應的訪問策略,明明白白的策略模式。

2.1 loadBeanDefinitions(Resource resource)

功能概述: 上面根據ClassPathResource資源訪問策略拿到了資源Resource,這裡將Resource進行特定的編碼處理,然後將編碼後的Resource轉換成SAX解析XML檔案所需要的輸入源InputSource。

/**
 * XmlBeanDefinitionReader
 **/

public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
 // 把從XML檔案讀取的Resource資源進行編碼處理
    return loadBeanDefinitions(new EncodedResource(resource));
}

/**
 * XmlBeanDefinitionReader
 **/

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
    ...
    // 完成resource->inputStream->inputSource轉換,使用SAX方式接收輸入源InputSource解析XML
    try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
        InputSource inputSource = new InputSource(inputStream);
        if (encodedResource.getEncoding() != null) {
            inputSource.setEncoding(encodedResource.getEncoding());
        }
        // 執行載入BeanDefinition
        return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
    }
    ...
}

知識點:

  1. XML解析兩種方式:SAX(Simple API for XML)和DOM(Document Object Model),區別在於SAX按照順序逐行讀取,查詢到符合條件即停止,只能讀不能修改,適合解析大型XML;DOM則是一次性讀取到記憶體建立樹狀結構,佔用記憶體,不僅能讀還能修改XML。Spring採用的SAX方式來解析XML。

2.2 doLoadBeanDefinitions(InputSource inputSource, Resource resource)

功能概述: 獲取DOM Document物件,XmlBeanDefinitionReader本身沒有對文件讀取的能力,而是委託給DocumentLoader的實現類DefaultDocumentLoader去讀取輸入源InputResource從而得到Document物件。獲得Document物件後,接下來就是BeanDefinition的解析和註冊。

/**
 * XmlBeanDefinitionReader
 **/

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
    throws BeanDefinitionStoreException 
{
    ...
        try {
            // inputSource->DOM Document
            Document doc = doLoadDocument(inputSource, resource);
            int count = registerBeanDefinitions(doc, resource);
            return count;
        }
    ...
}

知識點:

  1. Document是DOM的根節點,提供對文件資料訪問的入口。

2.3 registerBeanDefinitions(Document doc, Resource resource)

功能概述: 解析DOM Document成容器的內部資料介面BeanDefinition並註冊到容器內部。

/**
 * XmlBeanDefinitionReader
 **/

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
 BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
 // 已註冊的BeanDefinition的數量
    int countBefore = getRegistry().getBeanDefinitionCount();
 // DOM Document->BeanDefinition,註冊BeanDefinition至容器
    documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
    // 返回此次註冊新增的BeanDefinition數量
 return getRegistry().getBeanDefinitionCount() - countBefore;
}

知識點:

  1. BeanDefinitionDocumentReader是介面,實際完成對DOM Document解析的是其預設實現類DefaultBeanDefinitionDoucumentReade。
  2. createReaderContext(resource)建立XmlReaderContext上下文,包含了XmlBeanDefinitionReader讀取器和NamespaceHandlerResolver 名稱空間解析器。

3.1 registerBeanDefinitions(Document doc, XmlReaderContext readerContext)

功能概述: 在此之前一直 是XML載入解析的準備階段,在獲取到Document物件之後就開始真正的解析了。

/**
 * DefaultBeanDefinitionDocumentReader
 **/

public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
 this.readerContext = readerContext;
 doRegisterBeanDefinitions(doc.getDocumentElement());
}

3.2 doRegisterBeanDefinitions(Element root)

功能概述: 解析Element,DefaultBeanDefinitionDoucumentReader同樣不具有解析Element的能力,委託給BeanDefinitionParserDelegate執行。

/**
 * DefaultBeanDefinitionDocumentReader
 **/

protected void doRegisterBeanDefinitions(Element root) {
    BeanDefinitionParserDelegate parent = this.delegate;
    this.delegate = createDelegate(getReaderContext(), root, parent);
    ...
    // 解析前處理,留給子類實現
 preProcessXml(root);
 parseBeanDefinitions(root, this.delegate);
    // 解析後處理,留給子類實現
 postProcessXml(root);

 this.delegate = parent;
}

知識點:

  1. preProcessXml和postProcessXml裡程式碼是空的,這兩個方法是面向子類設計的,設計模式中的模板方法,也可以說是Spring的一個擴充套件點,後面有機會可以深入下細節。

3.3 parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate)

功能概述: 判別XML中Bean的宣告標籤是預設的還是自定義的,執行不同的解析邏輯。對於根節點或者子節點是預設名稱空間採用parseDefaultElement,否則使用parseCustomElement對自定義名稱空間解析。

/**
 * DefaultBeanDefinitionDocumentReader
 **/

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate){
 // 對beans的處理
    if (delegate.isDefaultNamespace(root)) {
  NodeList nl = root.getChildNodes();
  for (int i = 0; i < nl.getLength(); i++) {
   Node node = nl.item(i);
   if (node instanceof Element) {
    Element ele = (Element) node;
    if (delegate.isDefaultNamespace(ele)) {
                    // 對預設的Bean標籤解析
     parseDefaultElement(ele, delegate);
    }
    else {
                    // 對自定義的Bean標籤解析
     delegate.parseCustomElement(ele);
    }
   }
  }
 }
 else {
  delegate.parseCustomElement(root);
 }
}

知識點:

  1. Spring中XML有兩大類Bean的宣告標籤

    1. 預設宣告標籤:

      <bean id="userService" class="com.fly4j.service.impl.UserServiceImpl"/>
    2. 自定義宣告標籤: ·

      <tx: annotation-driven />
  2. 怎麼區分是預設名稱空間還是自定義名稱空間?

    通過node.getNamespaceURI()獲取名稱空間並和Spring中固定的名稱空間http://www.springframework.org/schema/beans進行比對,如果一致則預設,否則自定義。

3.4 parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate)

功能概述: 上面說到Spring標籤包括預設標籤和自定義標籤兩種。解析這兩種方式分別不同。以下就預設標籤進行說明BeanDefinitionParseDelegate對Bean標籤元素的解析過程。

/**
 * DefaultBeanDefinitionDocumentReader
 **/

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
    if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
        // import標籤
  importBeanDefinitionResource(ele);
 }
 else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
        // alias標籤
  processAliasRegistration(ele);
 }
 else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
        // bean標籤
  processBeanDefinition(ele, delegate);
 }
 else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
  // beans標籤
  doRegisterBeanDefinitions(ele);
 }
}

知識點:

  1. Spring的4種預設標籤舉例:

    1. import

       <import resource="spring-config.xml"/>
    2. alias

      <bean id="userService" class="com.fly4j.service.impl.UserServiceImpl"></bean>
      <alias name="userService" alias="user" />
    3. bean

      <beans>
       <bean id="userService" class="com.fly4j.service.impl.UserServiceImpl"></bean>
      </beans>
    4. beans

      <beans>
       <bean id="userService" class="com.fly4j.service.impl.UserServiceImpl"></bean>
      </beans>

3.5 processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate)

功能概述: 在4中預設標籤當中,其中核心的是對bean標籤的解析。以下就對bean標籤的解析來看解析過程。

/**
 * DefaultBeanDefinitionDocumentReader
 **/

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate){
 BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); // 參考4.1原始碼
 if (bdHolder != null) {
  bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); // 參考4.2原始碼
  try {
   BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); // 參考5.1原始碼
  }
  ...
  // Send registration event.
  getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
 }
}

流程解讀:

  1. DefaultBeanDefinitionDocumentReader委託BeanDefinitionParseDelegate的parseBeanDefinitionElement方法進行標籤元素的解析。解析後返回BeanDefinitionHolder例項bdHolder,bdHolder例項包含了配置檔案中的id、name、alias之類的屬性。
  2. 返回的bdHolder不為空時,標籤元素如果有自定義屬性和自定義子節點,還需要再次對以上兩個標籤解析。具體邏輯參考4.2小節原始碼。
  3. 解析完成後,對bdHolder進行註冊,使用BeanDefinitionReaderUtils.registerBeanDefinition()方法。具體邏輯參考5.1小節原始碼。
  4. 發出響應事件,通知相關監聽器,bean已經解析完成。

4.1 registerBeanDefinition(String beanName, BeanDefinition beanDefinition)

功能概述: 通過BeanDefinitionParseDelegate對Bean標籤的解析,解析得到id和name這些資訊封裝到BeanDefinitionHolder並返回。

/**
 * BeanDefinitionParseDelegate
 **/

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
    return parseBeanDefinitionElement(ele, null);
}
/**
 * BeanDefinitionParseDelegate
 **/

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
        // 解析id屬性
  String id = ele.getAttribute(ID_ATTRIBUTE);
        // 解析name屬性
  String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

  List<String> aliases = new ArrayList<>();
  if (StringUtils.hasLength(nameAttr)) {
   String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
   aliases.addAll(Arrays.asList(nameArr));
  }

  String beanName = id;
  if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
   beanName = aliases.remove(0);
  }

  if (containingBean == null) {
   checkNameUniqueness(beanName, aliases, ele);
  }

  AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
    if (beanDefinition != null) {
   ...
   String[] aliasesArray = StringUtils.toStringArray(aliases);
   return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
  }

4.2 decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder originalDef, @Nullable BeanDefinition containingBd)

功能概述: 4.1是對預設標籤的屬性解析,那如果標籤有自定義屬性和自定義子節點,這時就要通過decorateBeanDefinitionIfRequired解析這些自定義屬性和自定義子節點。

/**
 * BeanDefinitionParseDelegate
 **/

public BeanDefinitionHolder decorateBeanDefinitionIfRequired(
    Element ele, BeanDefinitionHolder originalDef, @Nullable BeanDefinition containingBd)
 
{

    BeanDefinitionHolder finalDefinition = originalDef;

    // Decorate based on custom attributes first.
    // 首先對自定義屬性解析和裝飾
    NamedNodeMap attributes = ele.getAttributes();
    for (int i = 0; i < attributes.getLength(); i++) {
        Node node = attributes.item(i);
        finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
    }

    // Decorate based on custom nested elements.
    // 裝飾標籤下的自定義子節點
    NodeList children = ele.getChildNodes();
    for (int i = 0; i < children.getLength(); i++) {
        Node node = children.item(i);
        if (node.getNodeType() == Node.ELEMENT_NODE) {
            finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
        }
    }
    return finalDefinition;
}

5.1 registerBeanDefinition(String beanName, BeanDefinition beanDefinition)

功能概述: 通過beanName註冊BeanDefinition至BeanDefinitionMap中去,至此完成Bean標籤的解析,轉換成Bean在容器中的資料結構BeanDefinition.

/**
 * BeanDefinitionReaderUtils
 **/

public static void registerBeanDefinition(
   BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)

  throws BeanDefinitionStoreException 
{

 // Register bean definition under primary name.
    // 使用beanName註冊BeanDefinition
 String beanName = definitionHolder.getBeanName();
  
 registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

 // Register aliases for bean name, if any.
    // 使用別名註冊BeanDefiniion
 String[] aliases = definitionHolder.getAliases();
 if (aliases != null) {
  for (String alias : aliases) {
   registry.registerAlias(beanName, alias);
  }
 }
}

/**
 * BeanDefinitionParseDelegate
 **/

public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
   throws BeanDefinitionStoreException 
{
    ...
    BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
 if (existingDefinition != null) {
     ...
     this.beanDefinitionMap.put(beanName, beanDefinition);
 }else{
     if (hasBeanCreationStarted()) {
   // beanDefinitionMap是全域性變數,會存在併發訪問問題
   synchronized (this.beanDefinitionMap) {
    this.beanDefinitionMap.put(beanName, beanDefinition);
    List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
    updatedDefinitions.addAll(this.beanDefinitionNames);
    updatedDefinitions.add(beanName);
    this.beanDefinitionNames = updatedDefinitions;
    removeManualSingletonName(beanName);
   }
  }
  else {
   // Still in startup registration phase
   this.beanDefinitionMap.put(beanName, beanDefinition);
   this.beanDefinitionNames.add(beanName);
   removeManualSingletonName(beanName);
  }
  this.frozenBeanDefinitionNames = null;
 }
    ...
}

五. 結語

總結Spring IOC基礎容器XmlBeanFactory的啟動流程概括如下:

  1. 執行XmlFactoryBean構造方法,執行載入BeanDefinition方法。
  2. 使用XmlBeanDefinitionReader讀取器將資源Resource解析成DOM Document物件。
  3. 使用DefaultBeanDefinitionDocumentReader讀取器從Document物件解析出 Element。
  4. 通過BeanDefinitionParserDelegate將Element轉換成對應的BeanDefinition。
  5. 實現BeanDefinitionRegistry註冊器將解析好的BeanDefinition註冊到容器中的BeanDefitionMap裡去

本篇就XmlBeanFactory容器啟動流程分析和原始碼解析兩個角度來對Spring IOC容器有個基礎的認識。有了這篇基礎,下篇就開始對Spring的擴充套件容器ApplicationContext進行分析。最後希望大家看完都有所收穫,可以的話給個關注,感謝啦。

六. 附錄

附上我編譯好的Spring原始碼,版本是當前最新版本5.3.0,歡迎star

spring-framework-5.3.0編譯原始碼

相關文章