Spring可扩展的XML Schema的自定义标签详解

JAVA herman 2512浏览 0评论
公告:“业余草”微信公众号提供免费CSDN下载服务(只下Java资源),关注业余草微信公众号,添加作者微信:xttblog,发送下载链接帮助你免费下载!
本博客日IP超过1800,PV 2600 左右,急需赞助商。
极客时间所有课程通过我的二维码购买后返现24元微信红包,请加博主新的微信号:xttblog,之前的微信号好友位已满,备注:返现
所有面试题(java、前端、数据库、springboot等)一网打尽,请关注文末小程序
视频教程免费领

有一些基于Spring的框架通常会提供他们自己特有的一些配置方式,
最常见的莫过于基于Spring的XML扩展方式, 定义一套自己风格的bean定义语法,
来让一些配置与框架的语义更加的贴近。 比如说,下面的这样的配置:

<dubbo:service id="memberService" ref="memberService.local" 
      version="${dubbo.memberService.version}" 
       interface="com.xxxx.morgan.member.MemberService" />

这篇文章主要用来记录一下,如何基于Spring来扩展自己想要的语义标签。

从Spring2.0开始,Spring提供了XML Schema可扩展机制,用户可以自定义XML Schema文件,并自定义XML Bean解析器,并集成到Spring Ioc 容器中。

完成XML自定义扩展,需要下面几个步骤

Spring自定义标签实现步骤

  1. 创建一个 XML Schema 文件,描述自定义的合法构建模块,也就是xsd文件
  2. 自定义个处理器类,并实现NamespaceHandler接口(比较容易)
  3. 自定义一个或多个解析器,实现BeanDefinitionParser接口(最关键的部分)
  4. 注册上面的组件到Spring IOC容器中

这里用一个简单的例子来说明下整个步骤:
我们需要完成一个这样的标签定义:

<calculator:calculator id="cal" operator="add" number2="234" number1="40.5" /> 

最终我们需要通过如下调用,达到输出的目的:

Calculator ca=(Calculator)ctx.getBean("cal"); 
System.out.println(ca.getOperator()+"-"+ca.getResult());

最终输出: add-274.5

自定义 XML Schema 文件

xsd描述文件

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://ivanzhangwb.com/custom/schema/calculator"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:beans="http://www.springframework.org/schema/beans"
targetNamespace="http://xttblog.com/custom/schema/calculator"
elementFormDefault="qualified" attributeFormDefault="unqualified">
    <xsd:import namespace="http://www.springframework.org/schema/beans" />
    <xsd:element name="calculator">
    <xsd:complexType>
        <xsd:complexContent>
            <xsd:extension base="beans:identifiedType">
                <xsd:attribute name="number1" type="xsd:decimal" />
                <xsd:attribute name="number2" type="xsd:decimal" />
                <xsd:attribute name="operator" type="xsd:string" />
            </xsd:extension>
        </xsd:complexContent>
    </xsd:complexType>
    </xsd:element>
</xsd:schema>

其中自定义的命名空间是http://xttblog.com/custom/schema/calculator,定义了calculator元素,属性有number1,number2,operator

实现 NamespaceHandlerSupport 接口,定义解析行为

public class CalculatorNameSpaceSupport extends NamespaceHandlerSupport {
    public void init() {
        registerBeanDefinitionParser("calculator",new CalculatorBeanDefinitorParse());
    }
}

实现 AbstractSingleBeanDefinitionParser 接口

public class CalculatorBeanDefinitorParse extends AbstractSingleBeanDefinitionParser {

@Override
protected Class getBeanClass(Element element) {
    return Calculator.class;
}

@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
    String operator = element.getAttribute("operator");
    Assert.notNull(operator, "operaotr is required.");
    if (StringUtils.hasText(operator)) {
        builder.addPropertyValue("operator", operator);
        if ("add".equalsIgnoreCase(operator)) {
            processAdd(element, builder);
        } else if ("minus".equalsIgnoreCase(operator)) {
            processMinus(element, builder);
        } else {
            throw new UnsupportedOperationException("UnSupport Operaotr");
        }
    }
}

private void processAdd(Element element, BeanDefinitionBuilder builder) {
    try {
    builder.addPropertyValue("result",new BigDecimal((String) assertAttributeNotNull(element, "number1"))
           .add(new BigDecimal((String) assertAttributeNotNull(element,"number2"))));

    } catch (Exception e) {
        throw new IllegalArgumentException("Number1/Number2 Must be Number.");
    }
}

private Object assertAttributeNotNull(Element element, String attributeName) {
    String attribute = element.getAttribute(attributeName);
    return StringUtils.hasText(attribute) ? StringUtils.trimWhitespace(attribute) : "";
}

private void processMinus(Element element, BeanDefinitionBuilder builder) {
    try {
        builder.addPropertyValue("result",
        new BigDecimal((String) assertAttributeNotNull(element, "number1")).divide(new BigDecimal(
        (String) assertAttributeNotNull(element,
        "number2"))));
    } catch (Exception e) {
        throw new IllegalArgumentException("Number1/Number2 Must be Number.");
    }
}

}

注册 handler 和 schema

为了让Spring在解析xml的时候能够感知到我们的自定义元素,我们需要把namespaceHandler和xsd文件放到2个指定的配置文件中,这2个文件都位于META-INF目录中

文件: spring.handlers

http\://xttblog.com/custom/schema/calculator=com.ivanzhangwb.calculator.parse.CalculatorNameSpaceSupport

文件:spring.schemas

http\://ivanzhangwb.com/custom/schema/calculator.xsd=META-INF/calculator.xsd

配置完成之后,我们就可以在spring配置文件中使用我们自定义的标签了。

Spring 配置文件引用自定义标签

<beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:calculator="http://ivanzhangwb.com/custom/schema/calculator"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
        http://xttblog.com/custom/schema/calculator
        http://xttblog.com/custom/schema/calculator.xsd">
    <calculator:calculator id="cal" number1="40.5" number2="234" operator="add" />
</beans>

对自定义标签进行单元测试

public class XMLSchemaCustomTest{
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        Calculator ca=(Calculator)ctx.getBean("cal");
        System.out.println(ca.getOperator()+"-"+ca.getResult());
    }
}

业余草公众号

最后,欢迎关注我的个人微信公众号:业余草(yyucao)!可加QQ1群:135430763(2000人群已满),QQ2群:454796847(已满),QQ3群:187424846(已满)。QQ群进群密码:xttblog,想加微信群的朋友,之前的微信号好友已满,请加博主新的微信号:xttblog,备注:“xttblog”,添加博主微信拉你进群。备注错误不会同意好友申请。再次感谢您的关注!后续有精彩内容会第一时间发给您!原创文章投稿请发送至532009913@qq.com邮箱。商务合作可添加助理微信进行沟通!

本文原文出处:业余草: » Spring可扩展的XML Schema的自定义标签详解