Spring自定义标签的实现

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 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>
复制代码


测试代码同上。


项目整体文件目录如下:

image.png


自定义属性


自定义属性的定义方式和自定义标签非常相似,其主要也是进行命名空间和转换逻辑的定义。假设我们有一个 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 的定义,其标签为 <aspectj-autoproxy />。我们知道,Spring 默认只会处理import、alias、bean 和 beans 四种标签,对于其余的标签,如我们所熟知的事务处理标签,这些都是使用自定义标签实现的。



hresh
+关注
目录
打赏
0
0
0
0
6
分享
相关文章
Spring boot整合Redis实现发布订阅(超详细)
Redis发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收信息。微信,微博,关注系统 Redis客户端可以订阅任意数量的频道
5636 0
Spring boot整合Redis实现发布订阅(超详细)
Spring Boot+Vue实现汽车租赁系统(毕设)
一、前言 汽车租赁系统,很常见的一个系统,但是网上还是以前的老框架实现的,于是我打算从设计到开发都用现在比较流行的新框架。想学习或者做毕设的可以私信联系哦!!
454 19
Spring Boot+Vue实现汽车租赁系统(毕设)
Spring Boot 项目优雅实现 Excel 导入与导出功能
背景 Excel 导入与导出是项目中经常用到的功能,在 Java 中常用 poi 实现 Excel 的导入与导出。由于 poi 占用内存较大,在高并发下很容易发生 OOM 或者频繁 fullgc,阿里基于 poi 开源了 EasyExcel 项目。
1197 0
Spring Boot 项目优雅实现 Excel 导入与导出功能
Spring Boot整合OpenOffice实现Word、Excel、PPT在线预览
Spring Boot整合OpenOffice实现Word、Excel、PPT在线预览
两种方式实现Spring 业务验证
验证在任何时候都非常关键。考虑将数据验证作为业务逻辑开发有利也有弊,Spring 认为,验证不应该只在Web 端进行处理,在服务端也要进行相应的处理,可以防止脏数据存入数据库中,从而避免为运维同学和测试同学造成更大的困扰,因为数据造成的bug会更加难以发现,而且开发人员关注点也不会放在数据本身的问题上,所以做服务端的验证也是非常有必要的。考虑到上面这些问题,Spring 提供了两种主要类型的验证:
331 0

热门文章

最新文章

AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等