Spring源碼閱讀筆記05:自定義xml標籤解析

  在上篇文章中,提到了在Spring中存在默認標籤與自定義標籤兩種,而且詳細分析了默認標籤的解析,本文就來分析自定義標籤的解析,像Spring中的AOP就是經過自定義標籤來進行配置的,這裏也是爲後面學習AOP原理打下基礎。html

  這裏先回顧一下,當Spring完成了從配置文件到Document的轉換並提取對應的root後,將開始全部元素的解析,而在這一過程當中便會區分默認標籤與自定義標籤兩種格式,並分別解析,能夠再看一下這部分的源碼加深理解:java

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 {
                    delegate.parseCustomElement(ele);
                }
            }
        }
    }
    else {
        delegate.parseCustomElement(root);
    }
}

  從上面的函數中也能夠看出,當Spring拿到一個元素時首先要作的是根據命名空間進行解析,若是是默認的命名空間,則使用parseDefaultElement()方法進行元素解析,不然使用parseCustomElement()方法進行解析。在本文中,全部的功能解析都是圍繞其中的那句代碼delegate.parseCustomElement(root)開展的。node

  在分析自定義標籤的解析過程以前,咱們先了解一下自定義標籤的使用過程,這裏參考spring文檔中的例子。spring

1. 自定義標籤使用

  擴展Spring自定義標籤配置大體須要如下幾個步驟:緩存

  • 定義一個XML文件來描述你的自定義標籤元素
  • 建立一個Handler,擴展自NamespaceHandlerSupport
  • 建立若干個BeanDefinitionParser的實現,用來解析XML文件中的定義
  • 將上述文件註冊到Spring中,這裏實際上是作一下配置

  接下來咱們將建立一個自定義XML元素,便於經過一個更容易的方式配置SimpleDateFormat類型的bean。配置好以後咱們能夠經過下面的方式來定義一個SimpleDateFormat類型的bean:app

<myns:dateformat id = "dateFormat" pattern = "yyyy-MM-dd HH:mm" lenient = "true"/>

1.1 編寫schema

  給Spring IoC容器建立XML擴展標籤的第一步是建立一個新的XML模式來描述對應的標籤(下面是咱們將要用來配置SimpleDateFormat對象的schema):dom

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.mycompany.com/schema/myns"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:beans="http://www.springframework.org/schema/beans"
    targetNamespace="http://www.mycompany.com/schema/myns"
    elementFormDefault="qualified"
    attributeFormDefault="unqualified">

    <xsd:import namespace="http://www.springframework.org/schema/beans"/>

    <xsd:element name="dateformat">
        <xsd:complexType>
            <xsd:complexContent>
                <xsd:extension base="beans:identifiedType">
                    <xsd:attribute name="lenient" type="xsd:boolean"/>
                    <xsd:attribute name="pattern" type="xsd:string" use="required"/>
                </xsd:extension>
            </xsd:complexContent>
        </xsd:complexType>
    </xsd:element>
</xsd:schema>

  定義了上面的schema以後,咱們就能夠直接使用元素<myns:dateformat/>來配置SimpleDateFormat類型的對象了:ide

<myns:dateformat id="dateFormat" pattern="yyyy-MM-dd HH:mm" lenient="true"/>

  若是沒有作上面的工做,咱們可能就須要經過下面的方式來配置SimpleDateFormat類型的對象了:函數

<bean id="dateFormat" class="java.text.SimpleDateFormat">
    <constructor-arg value="yyyy-HH-dd HH:mm"/>
    <property name="lenient" value="true"/>
</bean>

1.2 編寫一個BeanDefinitionParser

  這個是繼承自AbstractSingleBeanDefinitionParser,主要是用來將自定義標籤解析成BeanDefinition。工具

public class SimpleDateFormatBeanDefinitionParser extends AbstractSingleBeanDefinitionParser{
    protected Class getBeanClass(Element element) {
        return SimpleDateFormat.class;
    }
    protected void doParse(Element element, BeanDefinitionBuilder bean) {
        // this will never be null since the schema explicitly requires that a value be supplied
        String pattern = element.getAttribute("pattern");
        bean.addConstructorArg(pattern);
        // this however is an optional property
        String lenient = element.getAttribute("lenient");
        if (StringUtils.hasText(lenient)) {
            bean.addPropertyValue("lenient", Boolean.valueOf(lenient));
        }
    }
}

1.3 編寫一個NamespaceHandler

  這個是繼承自NamespaceHandlerSupport,主要是將上面的BeanDefinitionParser註冊到Spring容器:

public class MyNamespaceHandler extends NamespaceHandlerSupport{
    
    public void init() {
        registerBeanDefinitionParser("dateformat", new SimpleDateFormatBeanDefinitionParser());
    }

}

1.4 編寫Spring.handlers和Spring.schemas文件

  這兩個文件默認位置是在工程資源目錄的/META-INF/文件夾下,內容以下(注意要改爲本身的包名):

META-INF/spring.handlers
http\://www.mycompany.com/schema/myns=spring.customElement.MyNamespaceHandler

META-INF/spring.schemas
http\://www.mycompany.com/schema/myns/myns.xsd=spring/customElement/myns.xsd

1.5 自定義標籤使用示例

  使用自定義的擴展標籤和使用Spring提供的默認標籤是相似的,能夠按照以下配置一個SimpleDateFormat類型的bean:

<?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:myns="http://www.mycompany.com/schema/myns"
    xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.mycompany.com/schema/myns http://www.mycompany.com/schema/myns/myns.xsd">

    <!-- as a top-level bean -->
    <myns:dateformat id="defaultDateFormat" pattern="yyyy-MM-dd HH:mm" lenient="true"/>
</beans>

  配置好以後能夠測試一下:

public static void main(String[] args) {
    XmlBeanFactory xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("customElement.xml"));
    SimpleDateFormat myTestBean = (SimpleDateFormat)xmlBeanFactory.getBean("defaultDateFormat");
    System.out.println( "now time --- "+ myTestBean.format(new Date()));
}

// 輸出結果:
now time --- 2020-03-07 20:37

2. 自定義標籤解析

  瞭解了自定義標籤的使用以後,咱們來探究一下自定義標籤的解析過程。接着文章開頭提到的,咱們要從BeanDefinitionParserDelegate的parseCustomElement()方法開始:

public BeanDefinition parseCustomElement(Element ele) {
    return parseCustomElement(ele, null);
}

public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
    // 獲取對應的名稱空間
    String namespaceUri = getNamespaceURI(ele);
    // 根據命名空間找到對應的NamespaceHandler
    NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
    if (handler == null) {
        error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
        return null;
    }
    // 調用自定義的NamespaceHandler進行解析
    return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

  這裏能夠看出對自定義標籤進行解析的思路是根據Element獲取對應的名稱空間,而後根據名稱空間獲取對應的處理器,最後根據用戶自定義的處理器進行解析,但是看起來簡單,實現起來就不是這麼簡單了,先來看一下名稱空間的獲取吧。

2.1 獲取標籤的名稱空間

  自定義標籤的解析是從名稱空間的提取開始的,不管是區分默認標籤和自定義標籤,仍是區分自定義標籤對應的不一樣處理器,都是以標籤所提供的名稱空間爲基礎的。至於如何提取對應元素的名稱空間,已經有現成的實現可供使用,spring中是直接調用org.w3c.dom.Node提供的相應方法來完成名稱空間的提取:

public String getNamespaceURI(Node node) {
    return node.getNamespaceURI();
}

2.2 獲取自定義標籤處理器

  有了名稱空間,就能夠此來提取對應的NamespaceHandler了,這項工做是由下面這句代碼來完成的:

NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);

  這裏readerContext的getNamespaceHandlerResolver()方法返回的實際上是DefaultNamespaceHandlerResolver,因此咱們直接進入其resolve()方法中往下看:

public NamespaceHandler resolve(String namespaceUri) {
    // 獲取全部已經配置的handler映射
    Map<String, Object> handlerMappings = getHandlerMappings();
    // 根據名稱空間找到對應的處理器信息
    Object handlerOrClassName = handlerMappings.get(namespaceUri);
    if (handlerOrClassName == null) {
        return null;
    }
    else if (handlerOrClassName instanceof NamespaceHandler) {
        // 已經作過解析,直接從緩存讀取
        return (NamespaceHandler) handlerOrClassName;
    }
    else {
        // 未作過解析,則返回的是類路徑,須要重新加載
        String className = (String) handlerOrClassName;
        try {
            // 使用反射加載類
            Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
            if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
                throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
                        "] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
            }
            // 初始化類
            NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
            // 調用自定義的初始化方法
            namespaceHandler.init();
            // 記錄在緩存
            handlerMappings.put(namespaceUri, namespaceHandler);
            return namespaceHandler;
        }
        catch (ClassNotFoundException ex) {
            throw new FatalBeanException("NamespaceHandler class [" + className + "] for namespace [" +
                    namespaceUri + "] not found", ex);
        }
        catch (LinkageError err) {
            throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace [" +
                    namespaceUri + "]: problem with handler class file or dependent class", err);
        }
    }
}

  上面函數中的流程仍是比較清晰的,在前面的自定義標籤使用示例中有說到,若是要使用自定義標籤,須要在Spring.handlers文件中配置名稱空間與名稱空間處理器的映射關係。只有這樣,Spring才能根據映射關係找到匹配的處理器。

  而尋找匹配的處理器就是在上面函數中實現的,當獲取到自定義的NamespaceHandler以後就能夠進行處理器初始化並解析了。這裏咱們再回憶一下前面自定義標籤示例中,對於名稱空間處理器的內容(咱們在其init()方法中註冊了一個解析器)。

  在上面的代碼中,獲取到自定義名稱空間處理器後會立刻執行其init()方法來進行自定義BeanDefinitionParser的註冊。固然在init()中能夠註冊多個標籤解析器,如<myns:A、<myns:B等,使得myns的名稱空間中能夠支持多種標籤解析。

  註冊好以後,名稱空間處理器就能夠根據標籤的不一樣來調用不一樣的解析器進行解析。根據上面的函數和以前的例子,咱們基本能夠判斷getHandlerMappings()的主要功能就是讀取Spring.handlers配置文件並將配置文件緩存在map中:

private Map<String, Object> getHandlerMappings() {
    // 若是沒有被緩存則開始進行緩存
    if (this.handlerMappings == null) {
        synchronized (this) {
            if (this.handlerMappings == null) {
                try {
                    // this.handlerMappingsLocation在構造函數中已經被初始化爲:META-INF/Spring.handlers
                    Properties mappings =
                            PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
                    if (logger.isDebugEnabled()) {
                        logger.debug("Loaded NamespaceHandler mappings: " + mappings);
                    }
                    Map<String, Object> handlerMappings = new ConcurrentHashMap<String, Object>(mappings.size());
                    // 將Properties格式文件合併到Map格式的handlerMappings中
                    CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
                    this.handlerMappings = handlerMappings;
                }
                catch (IOException ex) {
                    throw new IllegalStateException(
                            "Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", ex);
                }
            }
        }
    }
    return this.handlerMappings;
}

  這裏是藉助工具類PropertiesLoaderUtils對Spring.handlers配置文件進行了讀取,而後將讀取的內容放到緩存中並返回。

2.3 標籤解析

  獲取到解析器以及要解析的元素後,Spring將解析工做委託給自定義解析器來解析,即下面代碼所完成的:

return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));

  此時咱們拿到的handler實際上是咱們自定義的MyNamespaceHandler了,可是咱們前面並無實現parse()方法,因此這裏這個應該是調用的父類中的parse()方法,看一下NamespaceHandlerSupport中的parse()方法:

public BeanDefinition parse(Element element, ParserContext parserContext) {
    // 尋找解析器並進行解析操做
    return findParserForElement(element, parserContext).parse(element, parserContext);
}

private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
    // 獲取元素名稱,也就是<myns:dateformat中的dateformat,在上面示例中,localName爲dateformat
    String localName = parserContext.getDelegate().getLocalName(element);
    // 根據dateformat找到對應的解析器,也就是在registerBeanDefinitionParser("dateformat", new SimpleDateFormatBeanDefinitionParser());
    // 註冊的解析器
    BeanDefinitionParser parser = this.parsers.get(localName);
    if (parser == null) {
        parserContext.getReaderContext().fatal(
            "Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
    }
    return parser;
}

  首先是尋找元素對應的解析器,而後調用其parse()方法。結合咱們前面的示例,其實就是首先獲取在MyNamespaceHandler類中的init()方法中註冊對應的SimpleDateFormatBeanDefinitionParser實例,並調用其parse()方法進行進一步解析,一樣這裏parse()方法咱們前面是沒有實現的,咱們也試着從其父類找一下:

public final BeanDefinition parse(Element element, ParserContext parserContext) {
    AbstractBeanDefinition definition = parseInternal(element, parserContext);
    if (definition != null && !parserContext.isNested()) {
        try {
            String id = resolveId(element, definition, parserContext);
            if (!StringUtils.hasText(id)) {
                parserContext.getReaderContext().error(
                        "Id is required for element '" + parserContext.getDelegate().getLocalName(element)
                                + "' when used as a top-level tag", element);
            }
            String[] aliases = new String[0];
            String name = element.getAttribute(NAME_ATTRIBUTE);
            if (StringUtils.hasLength(name)) {
                aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name));
            }
            // 將AbstractBeanDefinition轉換爲BeanDefinitionHolder並註冊
            BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases);
            registerBeanDefinition(holder, parserContext.getRegistry());
            if (shouldFireEvents()) {
            // 須要通知監聽器則進行處理
                BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder);
                postProcessComponentDefinition(componentDefinition);
                parserContext.registerComponent(componentDefinition);
            }
        }
        catch (BeanDefinitionStoreException ex) {
            parserContext.getReaderContext().error(ex.getMessage(), element);
            return null;
        }
    }
    return definition;
}

  這裏雖是對自定義配置進行解析,可是能夠看到大部分的代碼是用來將解析後的AbstractBeanDefinition轉化爲BeanDefinitionHolder並將其註冊,這點與解析默認標籤是相似的,真正去作解析的事情實際上是委託給了parseInternal()函數。而在parseInternal()中也並非直接調用自定義的doParse()函數,而是先進行一系列的數據準備,包括對beanClass、scope、lazyInit等屬性的準備:

@Override
protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
    String parentName = getParentName(element);
    if (parentName != null) {
        builder.getRawBeanDefinition().setParentName(parentName);
    }
    // 獲取自定義標籤中的class,此時會調用自定義解析器中的getBeanClass()方法
    Class<?> beanClass = getBeanClass(element);
    if (beanClass != null) {
        builder.getRawBeanDefinition().setBeanClass(beanClass);
    }
    else {
        // 若子類沒有重寫getBeanClass方法則會嘗試檢查子類是否重寫getBeanClassName()方法
        String beanClassName = getBeanClassName(element);
        if (beanClassName != null) {
            builder.getRawBeanDefinition().setBeanClassName(beanClassName);
        }
    }
    builder.getRawBeanDefinition().setSource(parserContext.extractSource(element));
    if (parserContext.isNested()) {
        // 若存在父類則使用父類的scope屬性
        builder.setScope(parserContext.getContainingBeanDefinition().getScope());
    }
    if (parserContext.isDefaultLazyInit()) {
        // 配置延遲加載
        builder.setLazyInit(true);
    }
    // 調用子類重寫的doParse方法進行解析
    doParse(element, parserContext, builder);
    return builder.getBeanDefinition();
}

// 這裏就是調用前面示例中咱們本身寫的doParse()方法
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
    doParse(element, builder);
}

  到這裏就完成了對自定義標籤轉換成BeanDefinition的整個過程了,回顧一下整個過程,在咱們定義的SimpleDateFormatBeanDefinitionParser中咱們只是作了與本身業務邏輯相關的部分,剩下的包括建立BeanDefinition以及進行相應默認屬性的設置,Spring都幫咱們默認實現了,咱們固然也能夠本身來完成這一過程,好比AOP就是這樣作的,可是本文仍是用最簡單的方式來作一個說明。

3. 總結

  其實從Spring對自定義標籤的解析中也能夠體會到Spring的可擴展式設計思路,經過暴露一些接口,咱們就可以方便地實現本身的個性化業務,不只如此,Spring本身即是這項功能的踐行者,像AOP、事務都是經過這種方式來定製對應的標籤來完成配置需求的。

  到這裏咱們已經完成了Spring中所有的解析工做的學習,也就是說到這裏咱們已經學習了Spring將bean從配置文件加載到內存的完整過程,接下來的任務即是若是使用這些bean,這纔是IoC容器的重頭戲,後面會詳細學習的。

相關文章
相關標籤/搜索