Spring源碼解析02:Spring IOC容器之XmlBeanFactory啓動流程分析和源碼解析

一. 前言

Spring容器主要分爲兩類BeanFactory和ApplicationContext,後者是基於前者的功能擴展,也就是一個基礎容器和一個高級容器的區別。本篇就以BeanFactory基礎容器接口的默認實現類XmlBeanFactory啓動流程分析來入門Spring源碼的學習。node

二. 概念要點

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這個好久遠版本就開始過期了。git

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

取而代之的寫法以下github

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被廢棄的緣由可想而知。web

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

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

附上DOM4J解析的代碼spring

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編譯源碼

相關文章
相關標籤/搜索