Spring自定義標籤的實現

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

概述

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

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

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

自定義標籤

擴展 Spring 自定義標籤配置通常須要如下幾個步驟:數據庫

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

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

建立組件

Car.javabash

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.xsdapp

<?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>
複製代碼

測試代碼同上。

項目總體文件目錄以下:

自定義屬性

自定義屬性的定義方式和自定義標籤很是類似,其主要也是進行命名空間和轉換邏輯的定義。假設咱們有一個 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

相關文章
相關標籤/搜索