Spring自定義標籤的實現

NO IMAGE

概述自定義標籤創建組件AbstractSingleBeanDefinitionParser 實現方式定義 XSD 文件Parser 類Handler 類Spring.handlers和Spring.schemas創建測試配置文件測試BeanDefinitionParser 實現方式定義 XSD 文件Parser 類Handler 類Spring.handlers和Spring.schemas創建測試配置文件自定義屬性自定義子標籤總結參考文獻

概述

前景:經常使用一些依賴於 Spring 的組件時,發現可以通過自定義配置 Spring 的標籤來實現插件的注入,例如數據庫源的配置,Mybatis 的配置等。那麼這些 Spring 標籤是如何自定義配置的?學習 Spring 標籤的自定義配置為以後實現分佈式服務框架做技術儲備。

技術分析:Spring 的標籤配置是通過 XML 來實現的,通過 XSD(xml Schema Definition)來定義元素,屬性,數據類型等。

Spring 在解析 xml 文件中的標籤的時候會區分當前的標籤是四種基本標籤(import、alias、bean和beans)還是自定義標籤,如果是自定義標籤,則會按照自定義標籤的邏輯解析當前的標籤。另外,即使是 bean 標籤,其也可以使用自定義的屬性或者使用自定義的子標籤。本文將對自定義標籤和自定義屬性的使用方式進行講解,並且會從源碼的角度對自定義標籤和自定義屬性的實現方式進行講解。

自定義標籤

擴展 Spring 自定義標籤配置一般需要以下幾個步驟:

  1. 創建一個需要擴展的組件
  2. 定義一個 XSD 文件,用於描述組件內容
  3. 創建一個實現 AbstractSingleBeanDefinitionParser 接口的類,又或者創建一個實現 BeanDefinitionParser 接口的類,用來解析 XSD 文件中的定義和組件定義。這兩種實現方式對應不同的 XSD 文件配置方式。
  4. 創建一個 Handler,繼承 NamespaceHandlerSupport ,用於將組件註冊到 Spring 容器
  5. 編寫 Spring.handlers 和 Spring.schemas 文件

下面就按照上面的步驟來實現一個自定義標籤組件。

創建組件

Car.java

public class Car {
    private int maxSpeed ;
    private double price ;
    private String brand ;
    private String color;

    public Car() {
        System.out.println("調用Car類的無參構造函數");
    }

    public int getMaxSpeed() {
        return maxSpeed;
    }

    public void setMaxSpeed(int maxSpeed) {
        this.maxSpeed = maxSpeed;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    @Override
    public String toString() {
        return "Car{" +
                "maxSpeed=" + maxSpeed +
                ", price=" + price +
                ", brand='" + brand + '\'' +
                ", color='" + color + '\'' +
                '}';
    }
}

AbstractSingleBeanDefinitionParser 實現方式

定義 XSD 文件

該文件命名為 org.xsd

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

    <xsd:element name="car">
        <xsd:complexType>
            <xsd:attribute name="id" type="xsd:string" />
            <xsd:attribute name="maxSpeed" type="xsd:integer" />
            <xsd:attribute name="price" type="xsd:double" />
            <xsd:attribute name="brand" type="xsd:string" />
            <xsd:attribute name="color" type="xsd:string" />
        </xsd:complexType>
    </xsd:element>

</xsd:schema>

在上述 XSD 文件中描述了一個新的 targetNamespace,並在這個空間裡定義一個 name 為 car 的 element。car 裡面有五個 attribute, 需要注意的是,其中四個屬性與我們的 Car 對象的屬性沒有直接的關係,這裡只是一個 XSD文件的聲明,以表徵Spring 的 application.xml 文件中使用當前命名空間時可以使用的標籤屬性。命名是否一致根據個人喜好來,id 屬性相當於標籤的 id 屬性,用來標識每個自定義標籤。

Parser 類

定義一個 Parser 類,該類繼承 AbstractSingleBeanDefinitionParser ,並實現 getBeanClass()doParse() 兩個方法,主要是用於解析 XSD 文件中的定義和組件定義。

public class CarBeanDefinitionParser extends AbstractSimpleBeanDefinitionParser {

    @Override
    protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
        String brand = element.getAttribute("brand");
        String color = element.getAttribute("color");
        double price = Double.valueOf(element.getAttribute("price"));
        int maxSpeed = Integer.valueOf(element.getAttribute("maxSpeed"));

        if(StringUtils.hasText(brand)){
            builder.addPropertyValue("brand",brand);
        }
        if(StringUtils.hasText(color)){
            builder.addPropertyValue("color",color);
        }
        builder.addPropertyValue("price",price);
        builder.addPropertyValue("maxSpeed",maxSpeed);

    }

    @Override
    protected Class<?> getBeanClass(Element element) {
        return Car.class;
    }
}
Handler 類
public class MyNamespaceHandler extends NamespaceHandlerSupport {
    @Override
    public void init() {
        registerBeanDefinitionParser("car",new CarBeanDefinitionParser());
    }
}
Spring.handlers和Spring.schemas

編寫 Spring.handlers 和 Spring.schemas 文件,默認位置放在工程的META-INF文件夾下。

Spring.handlers

http\://hresh.com/schema/org=com.msdn.schema.MyNamespaceHandler

Spring.schemas

http\://hresh.com/schema/org.xsd=META-INF/org.xsd

而 Spring 加載自定義的大致流程是遇到自定義標籤然後 就去 Spring.handlers 和 Spring.schemas 中去找對應的 handler 和 XSD ,默認位置是 META-INF 下,進而有找到對應的handler以及解析元素的 Parser ,從而完成了整個自定義元素的解析,也就是說 Spring 將向定義標籤解析的工作委託給了 用戶去實現。

創建測試配置文件

經過上面幾個步驟,就可以使用自定義的標籤了。在 xml 配置文件中使用如下:

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

    <myTag:car id="car2" price="56000" maxSpeed="240" brand="寶馬" color="銀色" />
</beans>

xmlns:myTag 表示 myTag 的命名空間是 http://hresh.com/schema/org

測試
@Test
public void otherGetBean(){
    ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");

    Car car = (Car) context.getBean("car2");
    System.out.println(car);
}

BeanDefinitionParser 實現方式

定義 XSD 文件

該文件命名為 soa.xsd。

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

    <xsd:element name="xxx" >
        <xsd:complexType>
            <xsd:complexContent>
                <xsd:extension base="beans:identifiedType">
                    <xsd:attribute name="maxSpeed" type="xsd:integer" />
                    <xsd:attribute name="price" type="xsd:double" />
                    <xsd:attribute name="brand" type="xsd:string" />
                    <xsd:attribute name="color" type="xsd:string" />
                </xsd:extension>
            </xsd:complexContent>
        </xsd:complexType>
    </xsd:element>

</xsd:schema>

在上述 XSD 文件中描述了一個新的 targetNamespace,並在這個空間裡定義一個 name 為 xxx 的 element。xxx裡面有四個 attribute,對應 Car 類中包含的屬性。由於<xsd:extension base="beans:identifiedType">標籤的緣故,默認帶有 id 屬性,所以不需要另外添加。

Parser 類

定義一個 Parser 類,該類實現 BeanDefinitionParser,並實現 構造方法parse() 兩個方法。主要是用於解析 XSD 文件中的定義和組件定義。

public class CarParser implements BeanDefinitionParser {
    private Class<?> beanclass;

    public CarParser(Class<?> beanclass) {
        this.beanclass = beanclass;
    }

    @Override
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        RootBeanDefinition beanDefinition = new RootBeanDefinition();
        beanDefinition.setBeanClass(beanclass);
        beanDefinition.setLazyInit(false);

        String brand = element.getAttribute("brand");
        String color = element.getAttribute("color");
        double price = Double.valueOf(element.getAttribute("price"));
        int maxSpeed = Integer.valueOf(element.getAttribute("maxSpeed"));

        beanDefinition.getPropertyValues().add("brand",brand);
        beanDefinition.getPropertyValues().add("color",color);
        beanDefinition.getPropertyValues().add("price",price);
        beanDefinition.getPropertyValues().add("maxSpeed",maxSpeed);
        BeanDefinitionRegistry beanDefinitionRegistry = parserContext.getRegistry();
//        beanDefinitionRegistry.registerBeanDefinition(beanclass.getName(),beanDefinition);//註冊bean到BeanDefinitionRegistry中

        String id = element.getAttribute("id");
        beanDefinitionRegistry.registerBeanDefinition(id,beanDefinition);
        return beanDefinition;
    }

}
Handler 類
public class MyNamespaceHandler extends NamespaceHandlerSupport {
    @Override
    public void init() {
        registerBeanDefinitionParser("xxx",new CarParser(Car.class));
    }
}
Spring.handlers和Spring.schemas

編寫 Spring.handlers 和 Spring.schemas 文件,默認位置放在工程的META-INF文件夾下。

Spring.handlers

http\://hresh.com/schema/soa=com.msdn.schema.MyNamespaceHandler

Spring.schemas

http\://hresh.com/schema/soa.xsd=META-INF/soa.xsd
創建測試配置文件
<?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:myTag="http://hresh.com/schema/soa"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://hresh.com/schema/soa http://hresh.com/schema/soa.xsd">

    <myTag:xxx id="car2" price="56000" maxSpeed="240" brand="寶馬" color="銀色" />
</beans>

測試代碼同上。

項目整體文件目錄如下:

Spring自定義標籤的實現

自定義屬性

自定義屬性的定義方式和自定義標籤非常相似,其主要也是進行命名空間和轉換邏輯的定義。假設我們有一個 User 對象,我們需要使用自定義標籤為其添加一個描述屬性。如下是 User 對象的定義:

public class User {
    private String name;
    private String desc;

    public User() {
        System.out.println("user無參構造方法");
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

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

這裡我們自定義 desc 屬性,對應的 XSD 文件定義如下:

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema
        xmlns="http://hresh.com/schema/user"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        targetNamespace="http://hresh.com/schema/user"
        elementFormDefault="qualified">

    <xsd:attribute name="desc" type="xsd:string" />

</xsd:schema>

需要注意的是,和自定義標籤不同的是,自定義標籤是將處理邏輯註冊到 parsers 對象中,這裡自定義屬性是將處理邏輯註冊到 attributeDecorators 中。如下 UserDefinitionDecorator 的邏輯:

public class UserDefinitionDecorator implements BeanDefinitionDecorator {
    @Override
    public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder beanDefinitionHolder, ParserContext parserContext) {
        String desc = ((Attr)node).getValue();
        beanDefinitionHolder.getBeanDefinition().getPropertyValues().add("desc",desc);
        return beanDefinitionHolder;
    }
}

可以看到,對於 desc 的處理邏輯就是獲取當前定義的屬性的值,由於知道其是當前標籤的一個屬性,因而可以將其強轉為一個 Attr 類型的對象,並獲取其值,然後將其添加到指定的 BeandDefinitionHolder 中。這裡需要注意的是,自定義標籤繼承的是 AbstractSingleBeanDefinitionParser 類,實際上是實現的 BeanDefinitionParser 接口,而自定義屬性實現的則是 BeanDefinitionDecorator 接口。

對應 Handler 類的定義如下:

public class MyNamespaceHandler extends NamespaceHandlerSupport {
    @Override
    public void init() {
//        registerBeanDefinitionParser("xxx",new UserParser(User.class));
        registerBeanDefinitionDecoratorForAttribute("desc",new UserDefinitionDecorator());
    }

}

編寫 Spring.handlers 和 Spring.schemas 文件,默認位置放在工程的META-INF文件夾下。

Spring.handlers

http\://hresh.com/schema/user=com.msdn.schema.MyNamespaceHandler

Spring.schemas

http\://hresh.com/schema/user.xsd=META-INF/user-desc.xsd

最後配置 XML 文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans 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"
       xmlns:myTag="http://hresh.com/schema/user">

    <bean id="user" class="com.msdn.bean.User" myTag:desc="a good boy">
        <property name="name" value="hresh" />
    </bean>

</beans>

測試代碼如下:

@Test
public void parseTest(){
    ApplicationContext context = new ClassPathXmlApplicationContext("application_context.xml");

    User user = (User) context.getBean("user");
    System.out.println(user);
}

執行結果為:

user無參構造方法
User{name='hresh', desc='a good boy'}

自定義子標籤

對於自定義子標籤的使用,其與自定義標籤的使用非常相似,不過需要注意的是,根據對自定義屬性的源碼解析,我們知道自定義子標籤並不是自定義標籤,自定義子標籤只是起到對其父標籤所定義的 bean 的一種裝飾作用,因而自定義子標籤的處理邏輯定義與自定義標籤主要有兩點不同:

  1. NamespaceHandler.init()方法中註冊自定義子標籤的處理邏輯時需要使用registerBeanDefinitionDecorator(String, BeanDefinitionDecorator) 方法;
  2. 自定義子標籤的處理邏輯需要實現的是 BeanDefinitionDecorator 接口,方法實現不同,其餘部分的使用都和自定義標籤一致。

這裡我就把 Parser 類和 Handler 類的代碼實現列出來。

public class MyNamespaceHandler extends NamespaceHandlerSupport {
    @Override
    public void init() {
//        registerBeanDefinitionParser("xxx",new UserParser(User.class));//自定義標籤
//        registerBeanDefinitionDecoratorForAttribute("desc",new UserDefinitionDecorator());//自定義屬性
        registerBeanDefinitionDecorator("node",new UserDefinitionDecorator(User.class));//自定義子標籤
    }

}
public class UserDefinitionDecorator implements BeanDefinitionDecorator {
    private Class<?> beanclass;

    public UserDefinitionDecorator(Class<?> beanclass) {
        this.beanclass = beanclass;
    }

    @Override
    public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder beanDefinitionHolder, ParserContext parserContext) {
//        String desc = ((Attr)node).getValue();
//        beanDefinitionHolder.getBeanDefinition().getPropertyValues().add("desc",desc);
//        return beanDefinitionHolder;

        BeanDefinition beanDefinition = beanDefinitionHolder.getBeanDefinition();

        String name = ((Element)node).getAttribute("name");
        String value = ((Element)node).getAttribute("value");

        beanDefinition.getPropertyValues().add(name,value);
        return beanDefinitionHolder;
    }
}

還有 descTag.xsd 文件

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema
        xmlns="http://hresh.com/schema/descTag"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        xmlns:beans="http://www.springframework.org/schema/beans"
        targetNamespace="http://hresh.com/schema/descTag"
        elementFormDefault="qualified">
    <xsd:import namespace="http://www.springframework.org/schema/beans" />

    <!--<xsd:complexType name="elementname1complexType">
        <xsd:attribute name="name" type="xsd:string">
            <xsd:annotation>
                <xsd:documentation><![CDATA[ The elementname1 name. ]]></xsd:documentation>
            </xsd:annotation>
        </xsd:attribute>
        <xsd:attribute name="value" type="xsd:string">
            <xsd:annotation>
                <xsd:documentation><![CDATA[ The elementname1 name. ]]></xsd:documentation>
            </xsd:annotation>
        </xsd:attribute>
    </xsd:complexType>

    <xsd:element name="node" type="elementname1complexType">
        <xsd:annotation>
            <xsd:documentation><![CDATA[ elementname1的文檔 ]]></xsd:documentation>
        </xsd:annotation>
    </xsd:element>-->

    <xsd:element name="node" >
        <xsd:complexType>
            <xsd:complexContent>
                <xsd:extension base="beans:identifiedType">
                    <xsd:attribute name="name" type="xsd:string" />
                    <xsd:attribute name="value" type="xsd:string" />
                </xsd:extension>
            </xsd:complexContent>
        </xsd:complexType>
    </xsd:element>

</xsd:schema>

文件中的兩種定義方式都可以,第二種更加簡潔一些。

總結

本文主要對自定義標籤,自定義屬性和自定義子標籤的使用方式進行了講解,有了對自定義標籤的理解,我們可以在 Spring 的 xml 文件中根據自己的需要實現自己的處理邏輯。另外需要說明的是,Spring 源碼中也大量使用了自定義標籤,比如 Spring 的 AOP 的定義,其標籤為。我們知道,Spring 默認只會處理import、alias、bean 和 beans 四種標籤,對於其餘的標籤,如我們所熟知的事務處理標籤,這些都是使用自定義標籤實現的。

參考文獻

https://my.oschina.net/zhangxufeng/blog/1815705

相關文章

Android源碼分析OkHttp5線程調度

一次Docker容器內大量殭屍進程排查分析

SpringIoC自定義標籤解析

學習element源碼實現自己的表單控件