1. 程式人生 > >曹工說Spring Boot原始碼(7)-- Spring解析xml檔案,到底從中得到了什麼(上)

曹工說Spring Boot原始碼(7)-- Spring解析xml檔案,到底從中得到了什麼(上)

寫在前面的話

相關背景及資源:

曹工說Spring Boot原始碼(1)-- Bean Definition到底是什麼,附spring思維導圖分享

曹工說Spring Boot原始碼(2)-- Bean Definition到底是什麼,咱們對著介面,逐個方法講解

曹工說Spring Boot原始碼(3)-- 手動註冊Bean Definition不比遊戲好玩嗎,我們來試一下

曹工說Spring Boot原始碼(4)-- 我是怎麼自定義ApplicationContext,從json檔案讀取bean definition的?

曹工說Spring Boot原始碼(5)-- 怎麼從properties檔案讀取bean

曹工說Spring Boot原始碼(6)-- Spring怎麼從xml檔案裡解析bean的

工程程式碼地址 思維導圖地址

工程結構圖:

概要

大家看到這個標題,不知道心裡有答案了沒?大家再想想,xml檔案裡都有什麼呢?

這麼一想,spring的xml檔案裡,內容真的很多,估計很多元素你也沒配置過,尤其是這兩年新出來的程式設計師,估計都在吐槽了,現在不都是註解了嗎,誰還用xml?但其實,不管是xml,還是註解,都是配置資訊,只是不同的表現形式而已,看過我前面幾講的同學,應該知道,我們用json、properties檔案寫過bean的配置資訊。

所以,具體形式不重要,xml和註解只是最常用的兩種表達方式罷了,我們這次就以xml為例來講解。

xml中,其實還是很有條理的,各種元素,都按照namespace分得明明白白的,我列了個表格如下:

namespace element
util constant、property-path、list、set、map、properties
context property-placeholder、property-override、annotation-config、component-scan、load-time-weaver、spring-configured、mbean-export、mbean-server
beans import、bean、alias
task annotation-driven、scheduler、scheduled-tasks、executor
cache advice、annotation-driven
aop config、scoped-proxy、aspectj-autoproxy

大家看到了嗎,spring其實對xml的支援才是最全面的,註解有的,xml基本都有。作為一個工作了6年的碼農,我發現好多元素我都沒配置過,更別說熟悉其內在原理了。但是呢,我們還是不能忘記了今天的標題,這麼多元素,難道沒有什麼共性嗎?spring解析這些元素,到底都是怎麼實現的呢,且不說這些元素怎麼生效,讀了東西總需要地方存起來吧,那,是怎麼存放的呢?

我們會挑選一些元素來講解。我們本講,先講解spring採用的xml解析方式;再從util這個namespace開始,挑了constant這個元素進行深入講解。

spring中所採用的xml解析方式

上一講,我們講了,spring是怎麼解析xml元素的,我今天想辦法從spring原始碼裡,把它用來解析xml的主幹程式碼提取了一下,基本就是下面這樣的,比如針對如下xml檔案,我們打算遍歷一遍:

test-xml-read.xml:
<?xml version="1.0" encoding="UTF-8"?>
<f:table xmlns:f="http://www.w3school.com.cn/furniture"
         xmlns:t="http://www.w3school.com.cn/t">
    <f:name>African Coffee Table</f:name>
    <f:width>80</f:width>
    <f:length>120</f:length>

    <t:abc></t:abc>
</f:table>

那麼,spring裡的程式碼骨架,大概如下:

package org.springframework.bootstrap.sample;

import lombok.extern.slf4j.Slf4j;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.*;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;

@Slf4j
public class XmlSimpleUse {

    public static void main(String[] args) {
        //讀取xml檔案
        URL url = Thread.currentThread().getContextClassLoader()
                .getResource("test-xml-read.xml");
        InputStream inputStream = url.openStream();
        //將流轉變為InputSource,在後續xml解析使用
        InputSource inputSource = new InputSource(inputStream);
        DocumentBuilderFactory factory = createDocumentBuilderFactory();

        DocumentBuilder docBuilder = factory.newDocumentBuilder();
        // 可選,設定實體解析器,其實就是:你可以自定義去哪裡載入xsd/dtd檔案
        docBuilder.setEntityResolver(new EntityResolver() {
            @Override
            public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
                return null;
            }
        });
        // 設定回撥處理器,當解析出現錯誤時,(比如xsd裡指定了不能出現a元素,然後xml裡出現了a元素)
        docBuilder.setErrorHandler(null);
        //解析xml檔案,獲取到Document,代表了整個檔案
        Document document = docBuilder.parse(inputSource);
        // 獲取根元素
        Element root = document.getDocumentElement();
        log.info("root is {}",root);
        
        //獲取根元素下的每個child元素
        NodeList nodeList = root.getChildNodes();
        for (int i = 0; i < nodeList.getLength(); i++) {
            Node node = nodeList.item(i);
            if (node instanceof Element) {
                Element ele = (Element) node;
                log.info("ele:{}",ele);
            }
        }
    }
    
    protected static DocumentBuilderFactory createDocumentBuilderFactory() {

        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setValidating(true);
        // Enforce namespace aware for XSD...
        factory.setNamespaceAware(true);

        return factory;
    }
}

輸出如下:

21:38:19.638 [main] INFO  o.s.bootstrap.sample.XmlSimpleUse - root is [f:table: null]
21:38:19.653 [main] INFO  o.s.bootstrap.sample.XmlSimpleUse - ele:[f:name: null]
21:38:19.653 [main] INFO  o.s.bootstrap.sample.XmlSimpleUse - ele:[f:width: null]
21:38:19.653 [main] INFO  o.s.bootstrap.sample.XmlSimpleUse - ele:[f:length: null]
21:38:19.654 [main] INFO  o.s.bootstrap.sample.XmlSimpleUse - ele:[t:abc: null]

大家可以看上面的demo程式碼,沒有依賴任何spring的類,基本還原了spring解析xml時的大體過程,在spring中多出來的細節部分,主要有兩處:

自定義entityResolver

docBuilder.setEntityResolver,這個部分,我們上面是預設實現。

大家看我們前面的xml,有一定了解的同學可能知道,前面定義了兩個namespace,語法一般是下面這樣的:

xmlns:namespace-prefix="namespaceURI"

所以,我們這邊的兩個namespace,字首分別是f、t,內容分別是:

http://www.w3school.com.cn/furniture、http://www.w3school.com.cn/t

但是,我們一般xml檔案是有格式要求的,比如spring裡,比如這個名稱空間下,可以定義什麼元素,這都是定死了的:

那,這個約束是在哪裡呢?在namespaceURI 對應的dtd/xsd等檔案中。

像上面截圖這樣,就是:

這一句,定義一個名稱空間
xmlns:context="http://www.springframework.org/schema/context" 
    
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
// 下面這個,你要當成key/value來理解,key就是:http://www.springframework.org/schema/context,
    //value,就是對應的xsd檔案
                http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"

有了上面的基礎知識,再來說那個介面:

public interface EntityResolver {

    // 一般傳入的systemId即為後邊這樣的:http://www.springframework.org/schema/context/spring-context.xsd
    public abstract InputSource resolveEntity (String publicId,
                                               String systemId)
        throws SAXException, IOException;

}

這個介面呢,就是讓我們自定義一個方法,來解析外部xml實體,一般傳入的引數如下:

即,publicId為null,systemId為xsd的uri,這個uri一般是可以通過網路獲取的,比如:

http://www.springframework.org/schema/context/spring-context.xsd

但是,spring是自定義了自己的entityResolver,實現類為:org.springframework.beans.factory.xml.ResourceEntityResolver

這個類,會在本地尋找對應的xsd檔案,主要邏輯就是去查詢classpath下的META-INF/spring.schemas,我們可以看看spring-beans包內的該檔案:

spring為什麼要自定義EntityResolver呢,spring為啥要在本地找呢,原因是:

如果不自定義,jdk的dom解析類,就會直接使用http://www.springframework.org/schema/context/spring-context.xsd這個東西,去作為URL,建立socket網路連線來獲取。而部分環境,比如生產環境,基本是外網隔離的,你這時候是沒辦法去下載這個xsd檔案的,豈不是就沒法校驗xml檔案的語法、格式了嗎?

所以,spring要將這個外部的xsd引用,轉為在classpath下的查詢。

自定義元素解析邏輯

這部分,大家再看下之前的骨架程式碼:

Document document = docBuilder.parse(inputSource);
        Element root = document.getDocumentElement();
        log.info("root is {}",root);
        NodeList nodeList = root.getChildNodes();
        for (int i = 0; i < nodeList.getLength(); i++) {
            Node node = nodeList.item(i);
            //遍歷每個元素,我們這裡只是簡單輸出
            if (node instanceof Element) {
                Element ele = (Element) node;
                log.info("ele:{}",ele);

            }
        }

骨架程式碼裡,遍歷每個元素,在spring裡,遍歷到每個ele時,要去判斷對應的namespace,如果是預設的,交給xxx處理;如果不是預設的,要根據namespace找到對應的namespacehandler,具體大家可以看看上一節:

曹工說Spring Boot原始碼(6)-- Spring怎麼從xml檔案裡解析bean的

我們在前面說了,本講只先挑一個元素來講解,即

util-constant元素詳解

用法

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:util="http://www.springframework.org/schema/util"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                http://www.springframework.org/schema/util  http://www.springframework.org/schema/util/spring-util.xsd">

    <util:constant id="chin.age" static-field=
            "java.sql.Connection.TRANSACTION_SERIALIZABLE"/>

</beans>

以上,即定義了一個常量bean,這個bean的值,就是static-field中指定的,這裡是java.sql.Connection#TRANSACTION_SERIALIZABLE,值為8。

/**
     * A constant indicating that
     * dirty reads, non-repeatable reads and phantom reads are prevented.
     * This level includes the prohibitions in
     * <code>TRANSACTION_REPEATABLE_READ</code> and further prohibits the
     * situation where one transaction reads all rows that satisfy
     * a <code>WHERE</code> condition, a second transaction inserts a row that
     * satisfies that <code>WHERE</code> condition, and the first transaction
     * rereads for the same condition, retrieving the additional
     * "phantom" row in the second read.
     */
    int TRANSACTION_SERIALIZABLE     = 8;

我們的測試程式碼如下:

package org.springframework.utilnamespace;

import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.util.MyFastJson;

import java.util.List;
import java.util.Map;

@Slf4j
public class TestConstant {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"classpath:util-namespace-test-constant.xml"},false);
        context.refresh();

        Map<String, Object> map = context.getDefaultListableBeanFactory().getAllSingletonObjectMap();
        log.info("singletons:{}", JSONObject.toJSONString(map));

        List<BeanDefinition> list =
                context.getBeanFactory().getBeanDefinitionList();
        MyFastJson.printJsonStringForBeanDefinitionList(list);

//        Object bean = context.getBean("chin.age");
//        System.out.println("bean:" + bean);

    }
}

注意,這裡,我們沒有呼叫getBean等方法,我們只是簡單地,採用json輸出了其bean definition,輸出如下:

{
        "abstract":false,
        "autowireCandidate":true,
        "autowireMode":0,
        // bean的class,好像是個factory,不是個int啊,我們那個bean,按理說,是int型別的
        "beanClass":"org.springframework.beans.factory.config.FieldRetrievingFactoryBean",
        "beanClassName":"org.springframework.beans.factory.config.FieldRetrievingFactoryBean",
        "constructorArgumentValues":{
            "argumentCount":0,
            "empty":true,
            "genericArgumentValues":[],
            "indexedArgumentValues":{}
        },
        "dependencyCheck":0,
        "enforceDestroyMethod":true,
        "enforceInitMethod":true,
        "lazyInit":false,
        "lenientConstructorResolution":true,
        "methodOverrides":{
            "empty":true,
            "overrides":[]
        },
        "nonPublicAccessAllowed":true,
        "primary":false,
        "propertyValues":{
            "converted":false,
            "empty":false,
             // 這個是我們給這個常量bean設定的值,被放在了property
            "propertyValueList":[
                {
                    "converted":false,
                    "name":"staticField",
                    "optional":false,
                    "value":"java.sql.Connection.TRANSACTION_SERIALIZABLE"
                }
            ]
        },
        "prototype":false,
        "qualifiers":[],
        "resolvedAutowireMode":0,
        "role":0,
        "scope":"",
        "singleton":true,
        "synthetic":false
    }

我們放開前面註釋的兩行程式碼:

        Object bean = context.getBean("chin.age");
        System.out.println("bean:" + bean);

output:

bean:8

有些同學估計有點蒙了,不要慌,這裡簡單說下結論,因為被解析為了一個工廠bean,這個在上面json裡也看到了,型別為:org.springframework.beans.factory.config.FieldRetrievingFactoryBean

當我們去getBean的時候,spring發現其為factory bean,就會呼叫這個工廠bean的工廠方法,去生產。

所以,這裡呢,bean是誰?是那個工廠org.springframework.beans.factory.config.FieldRetrievingFactoryBean,而不是這個工廠的產品:數字8。

具體的解析過程

根據namespaceUri獲得對應的namespaceHandler

從之前的骨架入手,spring裡也有類似的程式碼:

    DefaultBeanDefinitionDocumentReader#parseBeanDefinitions
    // 這個方法,裡面可以看到通過root得到了children,然後對children遍歷
    protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
        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)) {
                        parseDefaultElement(ele, delegate);
                    }
                    else {
                        // 這裡,如果是<util:constant>,因為不是預設名稱空間,所以走這裡。
                        delegate.parseCustomElement(ele);
                    }
                }
            }
        }
        else {
            delegate.parseCustomElement(root);
        }
    }

進入BeanDefinitionParserDelegate#parseCustomElement(org.w3c.dom.Element)

    public BeanDefinition parseCustomElement(Element ele) {
        return parseCustomElement(ele, null);
    }
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
   String namespaceUri = getNamespaceURI(ele);
   //這裡,通過namespaceUri,查詢對應的handler,我們這裡,會得到:org.springframework.beans.factory.xml.UtilNamespaceHandler
   NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
   if (handler == null) {
      error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
      return null;
   }
   // 呼叫org.springframework.beans.factory.xml.UtilNamespaceHandler,解析constant元素
   return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

NamespaceHandler概覽

好了,我們看看這個UtilNamespaceHandler

public class UtilNamespaceHandler extends NamespaceHandlerSupport {


    public void init() {
        registerBeanDefinitionParser("constant", new ConstantBeanDefinitionParser());
        registerBeanDefinitionParser("property-path", new PropertyPathBeanDefinitionParser());
        registerBeanDefinitionParser("list", new ListBeanDefinitionParser());
        registerBeanDefinitionParser("set", new SetBeanDefinitionParser());
        registerBeanDefinitionParser("map", new MapBeanDefinitionParser());
        registerBeanDefinitionParser("properties", new PropertiesBeanDefinitionParser());
    }


    private static class ConstantBeanDefinitionParser extends AbstractSimpleBeanDefinitionParser {

        @Override
        protected Class getBeanClass(Element element) {
            return FieldRetrievingFactoryBean.class;
        }

        @Override
        protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) {
            String id = super.resolveId(element, definition, parserContext);
            if (!StringUtils.hasText(id)) {
                id = element.getAttribute("static-field");
            }
            return id;
        }
    }
    ...省略
}

這裡,很明顯,UtilNamespaceHandler定義了每個元素,該由什麼類來處理。

namespaceHandler如何找到指定元素的parser

我們還是接著前面的程式碼handler.parse(Element element, ParserContext parserContext)往下看,是不是這樣吧:

父類 NamespaceHandlerSupport#parse
public BeanDefinition parse(Element element, ParserContext parserContext) {
        //這裡,繼續呼叫了本類的另一個方法來獲取Parser
        return findParserForElement(element, parserContext).parse(element, parserContext);
}

// 這裡就是上面呼叫的方法,來獲取Parser    
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
        String localName = parserContext.getDelegate().getLocalName(element);
        // 這裡的parser是一個map,map的元素就是來自於子類的init方法
        BeanDefinitionParser parser = this.parsers.get(localName);
        if (parser == null) {
            parserContext.getReaderContext().fatal(
                    "Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
        }
        return parser;
    }

這兩個方法是在父類中實現的,整體來說,NamespaceHandlerSupport中維護了一個map,裡面儲存本namespace下,具體的元素及其對應的parser。

    /**
     * Stores the {@link BeanDefinitionParser} implementations keyed by the
     * local name of the {@link Element Elements} they handle.
     */
    private final Map<String, BeanDefinitionParser> parsers =
            new HashMap<String, BeanDefinitionParser>();

我們看看,這個parser是什麼時候存了東西進去的吧?通過find usage,發現如下方法會進行put操作:

NamespaceHandlerSupport#registerBeanDefinitionParser
protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {
   this.parsers.put(elementName, parser);
}

這個方法還比較熟悉,因為在前面出現過了:

public class UtilNamespaceHandler extends NamespaceHandlerSupport {

   private static final String SCOPE_ATTRIBUTE = "scope";
    
   // 這個init方法,就是在之前通過namespaceUri來獲取對應的handler時,初始化的
   @override
   public void init() {
      registerBeanDefinitionParser("constant", new ConstantBeanDefinitionParser());
      registerBeanDefinitionParser("property-path", new PropertyPathBeanDefinitionParser());
      registerBeanDefinitionParser("list", new ListBeanDefinitionParser());
      registerBeanDefinitionParser("set", new SetBeanDefinitionParser());
      registerBeanDefinitionParser("map", new MapBeanDefinitionParser());
      registerBeanDefinitionParser("properties", new PropertiesBeanDefinitionParser());
   }    

parser實現類概述

通過上一節的講解,我們知道了UtilNamespaceHandler下的元素,及其對應的Parser。我們看看其類圖(圖小,可在單獨tab檢視):

我們先通過其實現的介面,來了解其核心功能:

package org.springframework.beans.factory.xml;

import org.w3c.dom.Element;

import org.springframework.beans.factory.config.BeanDefinition;

/**
 * 該介面主要被DefaultBeanDefinitionDocumentReader使用,來處理頂級的,非預設名稱空間下的的頂級元素(直接在<beans></beans>下)
 * Interface used by the {@link DefaultBeanDefinitionDocumentReader} to handle custom,
 * top-level (directly under {@code <beans/>}) tags.
 *
 * 實現類可以自由地通過該元素中的元資料,來轉換為任意多個BeanDefinition。(比如<context:component-scan></>)
 * <p>Implementations are free to turn the metadata in the custom tag into as many
 * {@link BeanDefinition BeanDefinitions} as required.
 * 
 * Dom解析器,通過元素所在的名稱空間,找到對應的NamespaceHandler,再從NamespaceHandler中找到對應的BeanDefinitionParser
 * <p>The parser locates a {@link BeanDefinitionParser} from the associated
 * {@link NamespaceHandler} for the namespace in which the custom tag resides.
 *
 * @author Rob Harrop
 * @since 2.0
 * @see NamespaceHandler
 * @see AbstractBeanDefinitionParser
 */
public interface BeanDefinitionParser {

    /**
     * 解析指定的element,註冊其返回的BeanDefinition到BeanDefinitionRegistry
     * (使用引數ParserContext#getRegistry()得到BeanDefinitionRegistry)
     */
    BeanDefinition parse(Element element, ParserContext parserContext);

}

大家發現介面的意義了嗎,雖然前面的類,很複雜,但我們通過介面,可以馬上知道其核心功能。在這裡,就是解析元素,獲得BeanDefinition

其實,到這裡,基本可以回答,標題所提出的問題了,Spring解析xml,得到了什麼?

從這個介面,可以知道,得到了BeanDefinition

UtilNamespaceHandler.ConstantBeanDefinitionParser

我們具體看看解析過程,這個類沒有直接實現parse方法,是在其父類實現的:

AbstractBeanDefinitionParser#parse
public final BeanDefinition parse(Element element, ParserContext parserContext) {
   // 這個方法就是個骨架,用了模板方法設計模式,
   // 1:呼叫另一個方法,獲取BeanDefinition
   AbstractBeanDefinition definition = parseInternal(element, parserContext);
   if (definition != null && !parserContext.isNested()) {
        // 2:獲得id
         String id = resolveId(element, definition, parserContext);
         // 3:獲得別名
         String name = element.getAttribute(NAME_ATTRIBUTE);
         String[] aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name));
         // 4:將beanDefinition的id、別名、definition等放進一個holder類
         BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases);
         // 5:通過parserContext,得到BeanDefinitionRegistry,註冊本bean進去
         registerBeanDefinition(holder, parserContext.getRegistry());
         if (shouldFireEvents()) {
            BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder);
            postProcessComponentDefinition(componentDefinition);
            parserContext.registerComponent(componentDefinition);
         }
   }
   return definition;
}

我們上面,第一步的註釋那裡,說用了模板設計模式,因為這個parseInternal是個抽象方法:

protected abstract AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext);

具體的實現,還在子類,鑑於這個類的層次有點深,我們再看看類圖:

這個parseInternal就在`AbstractSingleBeanDefinitionParser:

// 這個方法也足夠簡單,就是構造一個BeanDefinition,用了builder設計模式。
protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
   
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
        String parentName = getParentName(element);
        // ConstantBeanDefinitionParser覆蓋了這個方法
        Class<?> beanClass = getBeanClass(element);
        if (beanClass != null) {
            builder.getRawBeanDefinition().setBeanClass(beanClass);
        }
        else {
            String beanClassName = getBeanClassName(element);
            if (beanClassName != null) {
                builder.getRawBeanDefinition().setBeanClassName(beanClassName);
            }
        }
        builder.getRawBeanDefinition().setSource(parserContext.extractSource(element));
        if (parserContext.isDefaultLazyInit()) {
            // Default-lazy-init applies to custom bean definitions as well.
            builder.setLazyInit(true);
        }
        // 子類.AbstractSimpleBeanDefinitionParser重寫了這個方法
        doParse(element, parserContext, builder);
        return builder.getBeanDefinition();
    }

這裡的getBeanClass,在ConstantBeanDefinitionParser被重寫了,返回了一個工廠類class:

private static class ConstantBeanDefinitionParser extends AbstractSimpleBeanDefinitionParser {

        @Override
        protected Class getBeanClass(Element element) {
            return FieldRetrievingFactoryBean.class;
        }
        ...
}

doParse,也在ConstantBeanDefinitionParser的父類中AbstractSimpleBeanDefinitionParser進行了重寫:

protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
        NamedNodeMap attributes = element.getAttributes();
        // 這裡,獲取element下的屬性
        for (int x = 0; x < attributes.getLength(); x++) {
            Attr attribute = (Attr) attributes.item(x);
            if (isEligibleAttribute(attribute, parserContext)) {
                String propertyName = extractPropertyName(attribute.getLocalName());
                 // 通過attribute.getValue()獲取屬性值,propertyName為屬性名,加入到BeanDefinition
                builder.addPropertyValue(propertyName, attribute.getValue());
            }
        }
        postProcess(builder, element);
    }

回頭看看我們的xml:

<util:constant id="chin.age" static-field=
        "java.sql.Connection.TRANSACTION_SERIALIZABLE"/>

再看看我們debug時,此時的beanDefinition:

這裡,獲取了beanDefinition後,就是進入到本小節開始的地方,去進行beanDefinition的註冊了。

總的來說,的解析,得到了一個BeanDefinition,其class型別為:

public class FieldRetrievingFactoryBean
        implements FactoryBean<Object>, BeanNameAware, BeanClassLoaderAware, InitializingBean

這就是一個工廠bean。工廠bean在後續怎麼被使用的,留待下一篇。

總結

本篇原始碼在:

https://gitee.com/ckl111/spring-boot-first-version-learn/tree/master/all-demo-in-spring-learning/spring-xml-demo/src/main/java/org/springframework/utilnamespace中的TestConstant.java

xml的解析demo在:

https://gitee.com/ckl111/spring-boot-first-version-learn/tree/master/all-demo-in-spring-learning/spring-xml-demo/src/test/java/org/springframework/bootstrap/sample

我發現,一個東西,自己看懂可能還行,相對容易點,但是要把這個東西寫出來,卻是一個大工程。。。看似簡單的元素解析,你要把它講清楚,還真的要點篇幅,哈哈,所以,這也是為什麼本篇比較長的原因。

總的來說,再次回答標題,spring到底得到了什麼,得到了beanDefinition,本篇裡,只得到了一個beanDefinition,還是工廠型別的;後面,我們會看到其他多種多樣的元素解析方式。

ok,就到這裡,如果大家覺得有幫助,記得點贊