Spring源碼閱讀之編寫自定義標籤

基於昨天的那篇文章,我們今天來聊一個比較簡單輕鬆點的話題,今天我們來實現一個自定義的標籤。

先明確下整個項目的結構,網上有很多關於自定義標籤的實現方式,但是教程包結構不清晰,導致測試無法正常進行,博主自己也栽了一個坑,爲了讓朋友們可以快速驗證結果,我們首先來介紹一下包結構。

第一個工程包結構,該工程的作用是爲了實現一個自定義標籤。

第二個工程是爲了驗證自定義標籤是否能夠正常工作。

這裏說一下爲什麼創建了兩個工程,博主就是在這裏遇到了一個坑,最開始博主在一個工程下實現了標籤並且去做驗證,遇到了一個比較怪異的報錯,如下,大概意思就是說 'mytag:annotation' 找不到,苦思冥想命名定義了爲什麼會找不到呢?朋友們混個眼熟,後面會講爲啥。

Caused by: org.xml.sax.SAXParseException; lineNumber: 10; columnNumber: 42; cvc-complex-type.2.4.c: 通配符的匹配很全面, 但無法找到元素 'mytag:annotation' 的聲明。
at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.createSAXParseException(ErrorHandlerWrapper.java:203)
at

  1. 清楚了包結構,我們先來介紹第一個工程涉及文件的作用

  • MyNamespaceDefinitionParse:用於解析標籤的具體實現

  • MyNamespaceHandler:命名空間處理器,用於註冊標籤對應的解析器

  • MyTagBean:自定義BeanClass

  • spring.handlers:指定使用的命名空間處理器,即指定MyNamespaceHandler

  • spring.schemas:用於指定.xsd文件位置的

  • resource-1.0.xsd:用於定義標籤,也可理解成規範標籤編寫規則的

  1. 我們先來介紹下resource-1.0.xsd文件內容。

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            xmlns="http://www.zxz.com/resource"
            targetNamespace="http://www.zxz.com/resource">
    <!-- 定義一個類型,該類型內可以指定一個或多個標籤 -->
    <xsd:complexType name="annotationType">
        <!-- 定義屬性,名字叫id,可以理解成我們定義bean標籤的時候寫的 id 那個屬性 -->
        <xsd:attribute name="id" type="xsd:ID">
            <xsd:annotation>
                <xsd:documentation><![CDATA[ The unique identifier for a bean. ]]></xsd:documentation>
            </xsd:annotation>
        </xsd:attribute>
        <!-- 定義屬性,,名字叫name -->
        <xsd:attribute name="name" type="xsd:string">
            <xsd:annotation>
                <xsd:documentation><![CDATA[ The name of bean. ]]></xsd:documentation>
            </xsd:annotation>
        </xsd:attribute>
        <!-- 定義屬性,名字叫myPackage -->
        <xsd:attribute name="myPackage" type="xsd:string">
            <xsd:annotation>
                <xsd:documentation><![CDATA[ The scan package. ]]></xsd:documentation>
            </xsd:annotation>
        </xsd:attribute>
    </xsd:complexType>
    <!-- 定義一個標籤,名字叫annotation,對應的類型是我們上面定義的annotationType類型 -->
    <!-- 即annotation標籤包含了 id、name、myPackage 三個屬性 -->
    <xsd:element name="annotation" type="annotationType">
        <xsd:annotation>
            <xsd:documentation><![CDATA[ The annotation config ]]></xsd:documentation>
        </xsd:annotation>
    </xsd:element>
</xsd:schema>

  1. spring.schemas文件內容如下,用於指定resource-1.0.xsd文件的位置,左邊爲 key,右邊爲該文件在本地的位置,如果該文件在本地找不到會嘗試去遠程去找,即左邊 key 指定的遠程位置去獲取。

http\://www.zxz.com/resource/resource-1.0.xsd=/resource-1.0.xsd

  1. spring.handlers文件內容如下,用於指定自定義標籤的解析器位置,左邊爲 key,右邊爲該文件在本地的位置。

http\://www.zxz.com/resource=com.zxz.resource.MyNamespaceHandler

  1. MyTagBean.java文件,設置了 name myPackage兩個屬性,注意這個字段名和resource-1.0.xsd文件定義的名字實際上沒有什麼直接聯繫,只是名字相同而已。

package com.zxz.resource;

/**
* @author zxz
*/
public class MyTagBean {
    private String name;
    private String myPackage;

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getMyPackage() {
        return myPackage;
    }
    public void setMyPackage(String myPackage) {
        this.myPackage = myPackage;
    }

    @Override
    public String toString() {
        return "MyTagBean{" + "name='" + name + '\'' + ", myPackage='" + myPackage + '\'' + '}';
    }
}

  1. MyNamespaceHandler.java文件內容如下,其繼承了NamespaceHandlerSupport,實現了init方法。用於註冊具體annotation 標籤對應的解析器,這個和上一講的<context:component-scan>的做法是一樣的,不熟悉的朋友可以先看上一講。

package com.zxz.resource;

import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

/**
 * @author zxz
 */
public class MyNamespaceHandler extends NamespaceHandlerSupport {
    public void init() {
        registerBeanDefinitionParser("annotation", new MyNamespaceDefinitionParse());
    }
}

  1. MyNamespaceDefinitionParse.java內容如下,其實現了BeanDefinitionParse接口,來實現parse方法。

package com.zxz.resource;

import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Element;

/**
 * @author zxz
 */
public class MyNamespaceDefinitionParse implements BeanDefinitionParser {

    public BeanDefinition parse(Element element, ParserContext parserContext) {
        RootBeanDefinition beanDefinition = new RootBeanDefinition();
        // 設置beanClass
        beanDefinition.setBeanClass(MyTagBean.class);
        MutablePropertyValues mutablePropertyValues = beanDefinition.getPropertyValues();
        // 添加name屬性
        if (element.hasAttribute("name")) {
            mutablePropertyValues.addPropertyValue("name", element.getAttribute("name"));
        }
        // 添加package屬性
        if (element.hasAttribute("myPackage")) {
            mutablePropertyValues.addPropertyValue("myPackage", element.getAttribute("myPackage"));
        }
        String id = element.getAttribute("id");
        // 拿到註冊表, 註冊BeanDefinition
        parserContext.getRegistry().registerBeanDefinition(id, beanDefinition);
        return beanDefinition;
    }
}

至此,我們的自定義標籤就製作完成了,接下來打成 jar 包,在第二個測試工程中進行測試。


  1. 第二個工程文件介紹。

  • applicationContext.xml:編寫定義標籤內容

  • Test.java:測試方法

  • pom.xml:引入第一個工程的 jar 包


  1. applicationContext.xml文件內容如下,在測試啓動的時候會找 http://www.zxz.com/resource/resource-1.0.xsdhttp://www.zxz.com/resource對應的.xsd文件和對應的處理器。回答下前面的那個報錯原因,因爲在 spring 啓動的時候會先掃描本地所有的.handlers.schemas文件,如果本地找不到回去遠程找。但是從我們的報錯可以看出,應該是沒有掃描到我們自定義的兩個文件,但是我們將其打成 jar 包,在另外一個工程裏引用就可以掃到,所以我猜想 spring 啓動只掃描在 pom 文件指定的那些包下文件。

<?xml version="1.0" encoding="UTF-8"?>
<!-- xmlns:mytag="http://www.zxz.com/resource" 的標籤名,可以理解成指定一級標籤名,和 '<context:component-scan>' 的context一樣 -->
<beans xmlns:mytag="http://www.zxz.com/resource"
        xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.zxz.com/resource
                           http://www.zxz.com/resource/resource-1.0.xsd"
    <!-- http://www.zxz.com/resource 是用於指定handler類的位置 -->
    <!-- http://www.zxz.com/resource/resource-1.0.xsd 用於指定 .xsd文件的位置 -->
    <!-- 自定義標籤 -->
    <mytag:annotation id="myTagBean" name="zxz" myPackage="com.zxz.demo"/>
</beans>

  1. Test.xml文件內容如下。

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        System.out.println(context.getBean("myTagBean"));
    }
}

11. 運行測試代碼,最終輸出結果如下。

總結

以上即爲創建一個自定義標籤的流程,全部代碼都經過測試並得出預期結果。具體的運行原理我在上一篇講解<context:component-scan>標籤的源碼解析講解過了,不熟悉的朋友先看看上一篇內容。至此,我們關於標籤解析成BeanDefinition對象並將其註冊到BeanFactory的全部流程就講完了,涉及到的其他細節朋友大家自行了解,或者可以和我交流。下一篇文章我們來講講下一個比較重要的流程invokeBeanFactoryPostProcessors,希望大家和我繼續堅持下去,搞定 Spring 最核心的靈魂。


歡迎關注我,共同學習

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章