当需要为系统提供可配置化支持的时候。一般的做法会使用原生态的方式去解析定义好的XML文件,然后转化为配置对象。但是这种方法比较繁琐。Spring提供了可扩展的Schema的支持,这是一个不错的这种方案。自定义扩展的标签主要有一下几步:
- 1.创建一个需要扩展的组件
- 2.定义一个XSD文件描述组件内容
- 3.创建一个文件,实现BeanDefinitionParser接口,用来解析XSD文件中的定义和组件定义
- 4.创建一个Handler文件,扩展自NamespaceHandlerSupport,目的是将组建注册到Spring容器
- 5.编写Spring.handlers和Spring.schemas文件
<meta charset="utf-8">
实际操作如下:
目录结构为
(1)创建一个普通的POJO
(2)定义一个XSD文件描述组件内容
在XSD文件中描述了一个新的targetNamespace,并在这个空间中定义了一个name为user的element,user有3个属性,id,userName和email且都是string类型。这三个类主要用于验证spring配置文件中自定义格式。
(3)创建一个文件,实现BeanDefinitionParser接口,用来解析XSD文件中的定义和组件定义
(4)创建一个Handler文件,扩展自NamespaceHandlerSupport,目的是将组件注入到Spring容器
(5)编写Spring.handlers和Spring.schemas文件。默认位置是在工程的/META-INF/文件下,也可以通过Spring的扩展或者修改源码的方式改变路径
Spring.handlers内容如下
http\://www.acy.com/acy/schema/user=MyNameSpaceHandler
Spring.schemas文件内容如下
http\://www.acy.com/acy/schema/user.xsd=META-INF/user.xsd
到这里,自定义的配置就结束了,而Spring加载自定义的大致流程就是遇到自定义标签然后就去Spring.handlers和Spring.schemas文件中去找对应的handler和XSD,进而找到嘴硬的handler以及解析元素的Parser,从而完成整个自定义元素的解析。
(6)创建测试配置文件,在配置文件中,引入对应的命名空间以及XSD文件,然后就可以使用自定义标签了
这里的test:user需要这么写的原因是因为xmlns:test指向的是"http://www.acy.com/acy/schema/user",这个指向的是NamespaceHandler的映射,如果写的是xmlns:myBean那么就是<myBean:user 同理也可以注册多个标签
(7)测试
很多支持spring的框架,都是用这种方式对spring进行适配的。我们用dubbo这个框架来举例,自定义标签的使用。
在dubbo-config模块里面有一个dubbo-config-spring的字模块,在这个里面就是利用了spring的扩展的Schema的支持。
下面按照上面步骤进行解析扩展:
(1)创建一个需要扩展的组件;
&esmp;个人认为这个扩展的组建就是AbstractConfig类,这个类在dubbp-config-api模块中,这个一个基础的抽象类,这个类有很多个子类,这里展示部分子类
再展示部分AbstractMethodConfig 的配置项,用过dubbo的应该熟悉这些属性
public abstract class AbstractMethodConfig extends AbstractConfig {
private static final long serialVersionUID = 1L;
// timeout for remote invocation in milliseconds
protected Integer timeout;
// retry times
protected Integer retries;
// max concurrent invocations
protected Integer actives;
// load balance
protected String loadbalance;
// whether to async
protected Boolean async;
// whether to ack async-sent
protected Boolean sent;
// the name of mock class which gets called when a service fails to execute
protected String mock;
// merger
protected String merger;
// cache
protected String cache;
// validation
protected String validation;
// customized parameters
protected Map<String, String> parameters;
.....
}
(2)定义一个XSD文件描述组件内容
这个文件就是dubbo.xsd文件,这个里面定义了标签的使用规范,列举部分内容,这个文件如果知道xsd对应的写法会清楚点
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:tool="http://www.springframework.org/schema/tool"
xmlns="http://dubbo.apache.org/schema/dubbo"
targetNamespace="http://dubbo.apache.org/schema/dubbo">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<xsd:import namespace="http://www.springframework.org/schema/beans"/>
<xsd:import namespace="http://www.springframework.org/schema/tool"/>
<xsd:annotation>
<xsd:documentation>
<![CDATA[ Namespace support for the dubbo services provided by dubbo framework. ]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType name="abstractMethodType">
<xsd:attribute name="timeout" type="xsd:string" default="0">
<xsd:annotation>
<xsd:documentation><![CDATA[ The method invoke timeout. ]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="retries" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[ The method retry times. ]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="actives" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[ The max active requests. ]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="connections" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
<![CDATA[ The exclusive connections. default share one connection. ]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
.....
(3)创建一个文件,实现BeanDefinitionParser接口,用来解析XSD文件中的定义和组件定义
这里的实现BeanDefinitionParser接口的是DubboBeanDefinitionParser类,里面对不同的config进行了不同的处理逻辑,主要是按照对应的解析的bean的类型,来判断处理的逻辑,大家可以看看
public class DubboBeanDefinitionParser implements BeanDefinitionParser {
private static final Logger logger = LoggerFactory.getLogger(DubboBeanDefinitionParser.class);
private static final Pattern GROUP_AND_VERION = Pattern.compile("^[\\-.0-9_a-zA-Z]+(\\:[\\-.0-9_a-zA-Z]+)?$");
private final Class<?> beanClass;
private final boolean required;
public DubboBeanDefinitionParser(Class<?> beanClass, boolean required) {
this.beanClass = beanClass;
this.required = required;
}
@SuppressWarnings("unchecked")
private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) {
//这是需要注册Class到beanDefinition包含的BeanClass属性中
RootBeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setBeanClass(beanClass);
beanDefinition.setLazyInit(false);
//element中包含的是application标签中的信息
String id = element.getAttribute("id");
if ((id == null || id.length() == 0) && required) {
String generatedBeanName = element.getAttribute("name");
if (generatedBeanName == null || generatedBeanName.length() == 0) {
if (ProtocolConfig.class.equals(beanClass)) {
generatedBeanName = "dubbo";
} else {
generatedBeanName = element.getAttribute("interface");
}
}
if (generatedBeanName == null || generatedBeanName.length() == 0) {
generatedBeanName = beanClass.getName();
....
(4)创建一个Handler类,扩展自NamespaceHandlerSupport,目的是将组件注入到Spring容器
这里的Handler类就是DubboNamespaceHandler类
public class DubboNamespaceHandler extends NamespaceHandlerSupport {
static {
Version.checkDuplicate(DubboNamespaceHandler.class);
}
@Override
public void init() {
registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser());
}
}
(5)编写Spring.handlers和Spring.schemas文件。默认位置是在工程的/META-INF/文件下,也可以通过Spring的扩展或者修改源码的方式改变路径
&esmp;这里的handlers文件就是spring.handlers文件。里面内容比较简单,定义了对应的解析类的位置
http\://dubbo.apache.org/schema/dubbo=org.apache.dubbo.config.spring.schema.DubboNamespaceHandler
http\://code.alibabatech.com/schema/dubbo=org.apache.dubbo.config.spring.schema.DubboNamespaceHandler