简述
为什么需要自定义Schema?
很多时候我们在使用Spring的过程中会定义一些配置,最简单的比方说定义< bean,使用< aop或者< mvc标签,用过dubbo的小伙伴可能还会用很多的< dubbo:service这样的配置,通常这样的文件要求开发者按照特定的格式、特定的标签去配置需要的内容,这样的文件结构有很多好处:
- 可更容易地描述允许的文档内容
- 可更容易地验证数据的正确性
- 可更容易地定义数据约束(data facets)
- 可更容易地定义数据模型(或称数据格式)
- 可更容易地在不同的数据类型间转换数据
- 可使用 XML 编辑器来编辑 Schema 文件
- 可使用 XML 解析器来解析 Schema 文件
- 可以更容易地保护数据通信
- …
如何自定义Schema?
(1)简单的做法可以直接基于 Spring的标准 Bean 来配置,但配置较为复杂或者需要更多丰富控制的时候,会显得非常笨拙。
(2)使用原生态的方式去解析定义好的 xml 文件,然后转化为配置对象,这种方式当然可以解决所有问题,但实现起来比较繁琐,特别是是在配置非常复杂的时候,解析工作是一个不得不考虑的负担。
(3)Spring 提供了可扩展 Schema 的支持,这是一个不错的折中方案,完成一个自定义配置一般需要以下步骤:
- 设计配置属性和 JavaBean
- 编写 XSD 文件
- 编写 NamespaceHandler 和 BeanDefinitionParser
- 编写 spring.handlers 和 spring.schemas 串联起所有部件
- 在 Bean 文件中应用
基于Spring的Schema扩展案例
(1)编写需要解析的bean:
public class ProviderParameter implements Serializable {
private String interfaceName;
private String host;
private Integer port;
public String getInterfaceName() {
return interfaceName;
}
public void setInterfaceName(String interfaceName) {
this.interfaceName = interfaceName;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public Integer getPort() {
return port;
}
public void setPort(Integer port) {
this.port = port;
}
}
(2)编写XSD文件provider.xsd,并将文件放在META-INF下
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema
xmlns="http://syn.test.com/schema/provider"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:beans="http://www.springframework.org/schema/beans"
targetNamespace="http://syn.test.com/schema/provider">
<xsd:import namespace="http://www.springframework.org/schema/beans" />
<xsd:element name="providerParameter">
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="beans:identifiedType">
<xsd:attribute name="interfaceName" type="xsd:string" />
<xsd:attribute name="host" type="xsd:string" />
<xsd:attribute name="port" type="xsd:int" />
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
</xsd:schema>
(3)编写 NamespaceHandler 和 BeanDefinitionParser
NamespaceHandler:
public class ProviderNameSpaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
// TODO Auto-generated method stub
registerBeanDefinitionParser("providerParameter", new ProviderBeanDefinitionParser());
}
}
BeanDefinitionParser:
public class ProviderBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
protected Class getBeanClass(Element element) {
return ProviderParameter.class;
}
protected void doParse(Element element, BeanDefinitionBuilder bean) {
String interfaceName = element.getAttribute("interfaceName");
String host = element.getAttribute("host");
String port = element.getAttribute("port");
if (StringUtils.hasText(port)) {
bean.addPropertyValue("port", Integer.parseInt(port));
}
if (StringUtils.hasText(interfaceName)) {
bean.addPropertyValue("interfaceName", interfaceName);
}
if (StringUtils.hasText(host)) {
bean.addPropertyValue("host", host);
}
}
}
(4)编写 spring.handlers 和 spring.schemas 串联起所有部件,将这两个文件放在META-INF下
spring.handlers,用来解析XSD文件:
http://syn.test.com/schema/provider=basic.schema.ProviderNameSpaceHandler
spring.schemas:
http://syn.test.com/schema/provider/provider.xsd=META-INF/provider.xsd
(5)编写基于Spring的测试用例
Spring配置:
需要说明的是xml:provider用来指定自定义的schema,定义了这个下文中就可以使用< provider开头的标签。
用例编写:
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
"classpath:config/*.xml"
})
public class TestCase {
@Test
public void test() {
ApplicationContext ctx = new ClassPathXmlApplicationContext("config/spring-test.xml");
ProviderParameter p = (ProviderParameter)ctx.getBean("provider");
System.out.println("=====" + p);
}
}
以上就是一个简单的基于Spring的Schema扩展。整体的流程可以跑通,但是因为还没有专门针对Schema学习过所以对于Schema中的一些配置的写法还有很多问题,先留下两个主要的记录下来:
(1)xmlns和targetnamespace的区别
(2)Spring按照什么样的规则寻找Schema,目前是把xmlns,targetnamespace,以及spring配置文件中使用同样的链接可以访问成功,如果后面需要变化应该如何处理?