WsztRush

xml之schema

在网络传输中JSON和XML是最长用的两种数据格式,JSON的特点是短小、简单,但是除了这点以外就完全不能跟XML比了,所以涉及到配置方面还是优先考虑XML吧!

但是裸奔的XML并不好用,比如我们打出来Jar包给别人用,需要他们自己在Spring配置中添加:

<bean class="xxxxxx"/>

功能简单的时候是没有问题的,当你做的东西比较复杂的时候就会变成:

<bean class="xxx">
    <property name="aaa" value="aaa"/>
    <property name="bbb" value="bbb"/>
    <property name="ccc" value="ccc"/>
</bean>

除非在你的WILE里面写的非常清楚应用用哪个class,需要设置哪些property,哪些是必填的等等等,不然没人知道该怎么写,而更好的解决办法是编写schema来定义XML的规则!

命名空间(xmlns)

我们在配置Spring的时候经常会这么写:

<beans:beans xmlns:beans="http://www.springframework.org/schema/beans">
    <beans:import resource="xxx"/>
</beans:beans>

其中http://www.springframework.org/schema/beans就是一个命名空间,而xmlns:beans相当于设置了命名空间的一个代号,在使用时beans:import就可以表示使用该命名空间中的import元素。

可以不写:beans来表示默认就用该命名空间,那么配置就更简单了:

<beans xmlns="http://www.springframework.org/schema/beans">
    <import resource="xxx"/>
</beans>

在schema中由下面三个属性来控制命名空间的行为:

  1. targetNamespace:目标命名空间
  2. elementFormDefault:unqualified/qualified
  3. attributeFormDefault:unqualified/qualified

当设置unqualified时schema中除了根元素以外,其他的元素都是没有命名空间的,在使用的时候需要将其命名空间设置为空:

<easydt:easydt xmlns:easydt="http://www.cainiao.com/schema/easydt">
    <provider xmlns=""/><!-- 注意这里 -->
</easydt:easydt>

而设置为qualified时schema中定义的所有元素都属于targetNamespace所定义的命名空间:

<easydt:easydt xmlns="http://www.cainiao.com/schema/easydt">
    <provider/><!-- 看这里 -->
</easydt:easydt>

显然用qualified看起来更简单一些,不过也是看情况的。

定义元素

完整的schema的定义如下:

<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.w3school.com.cn"
xmlns="http://www.w3school.com.cn"
elementFormDefault="qualified">
    在这里定义元素和属性
</xs:schema>

其目的就是配置出来一堆的elementattribute来约束XML的行为,简单来说

<yyy xxx="xxx"/>

其中:yyy是element、xxx是属性!最简单的元素如下:

<easydt:a>123</easydt:a>

对应的配置如下:

<xs:element name="a" type="xs:integer"/>

设置type为integer之后会对内容进行检查,如果不是数字则报错,另外可以通过simpleType对其扩展来实现更复杂的限定:

<xs:element name="age">
    <xs:simpleType>
        <xs:restriction base="xs:integer">
            <xs:minInclusive value="0"/>
            <xs:maxInclusive value="100"/>
        </xs:restriction>
    </xs:simpleType>
</xs:element>

向元素中添加子元素、属性之后就不是一个简单元素,而是一个复杂元素,可以用complexType定义其类型:

<xs:element name="note">
    <xs:complexType>
        <xs:attribute name="app" type="xs:string"/>
    </xs:complexType>
</xs:element>

对应的XML的配置为<easydt:note app="123"/>,子节点的定义也很简单:

<xs:element name="note">
    <xs:complexType>
        <xs:sequence>
            <xs:element name="a" type="xs:integer"/>
            <xs:element name="b" type="xs:integer"/>
        </xs:sequence>
    </xs:complexType>
</xs:element>

对应的XML的配置为<note><a>1</a><b>2</b></note>,其中sequence的作用是

组中的元素以指定的顺序出现在包含元素中,每个子元素可以出现0次到任意次

当然还有其他的方式:

指示器 含义
all 子元素可以按照任意顺序出现,且每个子元素必须只出现一次
choice 随便添加子元素,可以使用maxOccurs来设置可添加子元素的数目
attributeGroup 属性组
group 元素组

元素的类型是非常复杂的,不同的类型之间很可能有一些定义是可以重用的,我们可以定义一些基础的类型,然后使用extension对其进行扩展可以得到:

<xs:complexType name="baseInfo">
    <xs:sequence>
        <xs:element name="id" type="xs:string"/>
    </xs:sequence>
</xs:complexType>
<xs:complexType name="fullpersoninfo">
    <xs:complexContent>
        <xs:extension base="baseInfo">
            <xs:sequence>
                <xs:element name="name" type="xs:string"/>
            </xs:sequence>
        </xs:extension>
    </xs:complexContent>
</xs:complexType>

其他元素的可以在这里查看使用方法~~

当上面这些不能满足你的需求时,可以使用anyanyAttribute来允许用户配置没有在schema中定义过的东西,然后在解析的阶段进行处理!

解析

在Spring中定义解析需要用下面两个文件来配置(需要放在META-INF目录,Spring会自动加载):

  1. spring.schemas:命名空间对应的schemas配置的位置
  2. spring.handlers:命名空间对应的解析类

来看个例子:

// spring.schemas
http\://www.cainiao.com/schema/easydt/easydt.xsd=META-INF/easydt.xsd
// spring.handlers
http\://www.cainiao.com/schema/easydt=com.cainiao.easydt.client.springTag.EasyDtNamespaceHandler

NamespaceHandlerSupport中定义了遇到对应的元素的时候应该使用Parser:

public class EasyDtNamespaceHandler extends NamespaceHandlerSupport {
	public void init() {
		registerBeanDefinitionParser("easydt", new EasyDtBeanDefinitionParser());
	}
}

然后用AbstractBeanDefinitionParser中拿到配置信息并使用addPropertyValue来定义BeanDefinition:

public class EasyDtBeanDefinitionParser extends AbstractSingleBeanDefinitionParser{
	protected Class<EasyDt> getBeanClass(Element element) {
		return EasyDt.class;
	}
	protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
		builder.addPropertyValue("domain", element.getAttribute("domain"));
	}
}

关于BeanDefinition的载入和解析的过程可以看这里,具体的解析工作是交给BeanDefinitionParserDelegate来完成的,如果子元素不是简单元素可以调用parseCustomElement来完成解析:

builder.addPropertyValue("provider",
    parserContext.getDelegate().parseCustomElement(
        DomUtils.getChildElementByTagName(element, "provider"),
        builder.getRawBeanDefinition()));

想更灵活地在Spring中玩耍XML还是要多看看Bean的解析过程。

总结

用这些最基本的用法基本可以搞定大部分的自定义schema的需求,对于复杂的还需要深入去研究。