转载

Spring自定义标签的实现

概述

前景:经常使用一些依赖于 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

原文  https://juejin.im/post/5e134047e51d4541093e8d4f
正文到此结束
Loading...