大家好,本文给大家简单介绍一下Elastic-Job 是如何自定义标签与与Spring 依赖注入无缝整合
Elastic-Job 自定义Spring标签与Spring 依赖注入无缝整合
文 | 宋小生
10 自定义Spring标签与Spring 依赖注入无缝整合
10.1 简介
为方便使用ElasticJob进行开发,ElasticJob作为一款优秀的分布式调度中间件对外提供可用于Spring框架的自定义的标签来进行调度作业的配置,
使用调度作业的系统可以减少程序设计的复杂性,将注意力集中在自己的业务上,那如何使用Spring来开发自定标签呢,Spring2.0开始,Spring提供XML Schema可扩展机制,
用户可以自定义XML Schema文件,并自定义XML Bean解析器,集成到Spring IOC容器中。
主要需要如下过程:
1) 自定义标签属性的配置
①编写XML模式定义文件,文件后缀为.xsd (用于描述和验证自定义reg和job标签的文档结构)。
②META-INF/spring.schemas指定xsd文件位置。
2) 自定义标签的解析
③编写NamespaceHandler 自定义标签解析,命名空间处理(这里有
代码语言:javascript复制RegNamespaceHandler和JobNamespaceHandler两种)和编写BeanDefinitionParser用于命名空间下的Bean xml标签解析为BeanDefinition。
④META-INF/spring.handlers 为指定命名空间配置对应的标签处理类型。
3) 使用自定义标签
⑤最后项目配置中引入相关自定义标签配置来使用。
XML Schema 语言也称作 XML Schema 定义(XML Schema Definition,XSD)。
10.2 注册中心自定义XSD文件配置与说明
在这里我们以自定义注册中心配置标签来说明:
在Spring中引用的标签如下:
代码语言:javascript复制 <reg:zookeeper id="regCenter" server-lists="${serverLists}" namespace="${namespace}" base-sleep-time-milliseconds="${baseSleepTimeMilliseconds}" max-sleep-time-milliseconds="${maxSleepTimeMilliseconds}" max-retries="${maxRetries}" />
①META-INF/spring.schemas指定xsd文件位置
Spring 能够非必须地使用需要Internet访问的默认 EntityResolver 来检索模式文件。如果在此属性文件中指定映射,Spring将在类路径中搜索模式 。
首先在项目资源根目录下创建META-INF/spring.schemas文件来指定xsd文件位置,这个文件在Spring容器启动时候会进行扫描自动读取内容如果这个文件不存在,我们在Spring配置文件代码中引用了对应的xsd文件则默认的xml解析会从网络上下载,spring.schemas在Spring中怎么解析的可以看下PluggableSchemaResolver类型的实现源码。
spring.schemas配种中主要引入了两个变量,下面是META-INF/spring.schemas中的配置:
代码语言:javascript复制http://www.dangdang.com/schema/ddframe/reg/reg.xsd=META-INF/namespace/reg.xsd
http://www.dangdang.com/schema/ddframe/job/job.xsd=META-INF/namespace/job.xsd
在Spring配置文件如何引用呢可以看下xsi:schemaLocation,下面是项目配置文件中的引用:
代码语言:javascript复制 xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.dangdang.com/schema/ddframe/reg
http://www.dangdang.com/schema/ddframe/reg/reg.xsd
http://www.dangdang.com/schema/ddframe/job
http://www.dangdang.com/schema/ddframe/job/job.xsd
">
②编写XML模式定义文件,文件后缀为.xsd
我们就以注册的标签模式定义文件举例:那xsd文件是什么我们可以看下定义:
XSD是指XML结构定义 ( XML Schemas Definition )
XML Schema 是DTD的替代品。XML Schema语言也就是XSD。
XML Schema描述了XML文档的结构。可以用一个指定的XML Schema来验证某个XML文档,以检查该XML文档是否符合其要求。文档设计者可以通过XML Schema指定一个XML文档所允许的结构和内容,并可据此检查一个XML文档是否是有效的。XML Schema本身是一个XML文档,它符合XML语法结构。可以用通用的XML解析器解析它。
一个XML Schema会定义:文档中出现的元素、文档中出现的属性、子元素、子元素的数量、子元素的顺序、元素是否为空、元素和属性的数据类型、元素或属性的默认和固定值。
XSD是DTD替代者的原因,一是据将来的条件可扩展,二是比DTD丰富和有用,三是用XML书写,四是支持数据类型,五是支持命名空间。
XSD文件的后缀名为.xsd。
我们来看下注册中心的模式定义文件META-INF/namespace/reg.xsd如下:
代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.dangdang.com/schema/ddframe/reg"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:beans="http://www.springframework.org/schema/beans"
targetNamespace="http://www.dangdang.com/schema/ddframe/reg"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xsd:import namespace="http://www.springframework.org/schema/beans"/>
<xsd:element name="zookeeper">
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="beans:identifiedType">
<xsd:attribute name="server-lists" type="xsd:string" use="required" />
<xsd:attribute name="namespace" type="xsd:string" use="required" />
<xsd:attribute name="base-sleep-time-milliseconds" type="xsd:string" />
<xsd:attribute name="max-sleep-time-milliseconds" type="xsd:string" />
<xsd:attribute name="max-retries" type="xsd:string" />
<xsd:attribute name="session-timeout-milliseconds" type="xsd:string" />
<xsd:attribute name="connection-timeout-milliseconds" type="xsd:string" />
<xsd:attribute name="digest" type="xsd:string" />
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
</xsd:schema>
针对这个xml我们我们来说几个概念:
XML Namespace (xmlns) 属性
XML 命名空间属性被放置于元素的开始标签之中,并使用以下的语法:
xmlns:namespace-prefix="namespaceURI"
当命名空间被定义在元素的开始标签中时,所有带有相同前缀的子元素都会与同一个命名空间相关联。
用于标示命名空间的地址不会被解析器用于查找信息。其惟一的作用是赋予命名空间一个惟一的名称。不过,很多公司常常会作为指针来使用命名空间指向实际存在的网页,这个网页包含关于命名空间的信息。
targetNamespace用于定义当前元素所属的目标命名空间,在这里我们定义了标签元素Zookeeper和一些基本属性。
10.2 注册中心自定义NamespaceHandler的配置与说明
指定命名空间的标签定义好了,那标签中的元素与元素属性具体该如何处理可以看下:spring.handlers文件,一共有两行如下:
代码语言:javascript复制
http://www.dangdang.com/schema/ddframe/reg=com.dangdang.ddframe.job.lite.spring.reg.handler.RegNamespaceHandler
http://www.dangdang.com/schema/ddframe/job=com.dangdang.ddframe.job.lite.spring.job.handler.JobNamespaceHandler
在这里每行都是用=号隔开的KEY ,VALUE数据其中等号前面的KEY为命名空间URI,等号后面的为当前命名空间对应的命名空间处理类型,
Spring源码中如何解析这个文件可以看下DefaultNamespaceHandlerResolver类型源码。
我们还以自定义注册中心标签的解析来作为参考:
代码语言:javascript复制
http://www.dangdang.com/schema/ddframe/reg=com.dangdang.ddframe.job.lite.spring.reg.handler.RegNamespaceHandler
命名空间http://www.dangdang.com/schema/ddframe/reg的标签解析类型为RegNamespaceHandler
NamespaceHandler 命名空间处理 :RegNamespaceHandler
具体源码如下:
代码语言:javascript复制
/**
* 注册中心的命名空间处理器.
*
* @author zhangliang
*/
public final class RegNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("zookeeper", new ZookeeperBeanDefinitionParser());
}
}
RegNamespaceHandler类型处理器继承了NamespaceHandlerSupport类型并重写了init方法。
NamespaceHandlerSupport类型的作用是什么呢:
在Spring中NamespaceHandlerSupport是一个支持实现自定义命名空间处理的类型。单个节点的解析和装饰分别通过BeanDefinitionParser和BeanDefinitionDecorator策略接口完成。
自定义命名空间处理类型可以调用registerBeanDefinitionParser和registerBeanDefinitionDecorator方法,来将自定义标签的解析加入
NamespaceHandlerSupport中的解析器集合中。更详细的Spring实现细节可以看下Spring源码。
代码语言:javascript复制
重写的init方法说明
在构造器执行之后,解析任何自定义元素之前被调用,我们可以在init方法中调用NamespaceHandlerSupport类型中的registerBeanDefinitionParser方法来注册当前命名空间元素的Bean解析器,在这个示例中我们为元素Zookeeper设置了自定义元素解析器ZookeeperBeanDefinitionParser。
那接下来我们可以来看下ZookeeperBeanDefinitionParser是如何解析Zookeeper标签的属性的:
代码语言:javascript复制
/**
* 基于Zookeeper注册中心的命名空间解析器.
*
* @author caohao
*/
public final class ZookeeperBeanDefinitionParser extends AbstractBeanDefinitionParser {
@Override
protected AbstractBeanDefinition parseInternal(final Element element, final ParserContext parserContext) {
BeanDefinitionBuilder result = BeanDefinitionBuilder.rootBeanDefinition(ZookeeperRegistryCenter.class);
result.addConstructorArgValue(buildZookeeperConfigurationBeanDefinition(element));
result.setInitMethodName("init");
return result.getBeanDefinition();
}
private AbstractBeanDefinition buildZookeeperConfigurationBeanDefinition(final Element element) {
BeanDefinitionBuilder configuration = BeanDefinitionBuilder.rootBeanDefinition(ZookeeperConfiguration.class);
configuration.addConstructorArgValue(element.getAttribute("server-lists"));
configuration.addConstructorArgValue(element.getAttribute("namespace"));
addPropertyValueIfNotEmpty("base-sleep-time-milliseconds", "baseSleepTimeMilliseconds", element, configuration);
addPropertyValueIfNotEmpty("max-sleep-time-milliseconds", "maxSleepTimeMilliseconds", element, configuration);
addPropertyValueIfNotEmpty("max-retries", "maxRetries", element, configuration);
addPropertyValueIfNotEmpty("session-timeout-milliseconds", "sessionTimeoutMilliseconds", element, configuration);
addPropertyValueIfNotEmpty("connection-timeout-milliseconds", "connectionTimeoutMilliseconds", element, configuration);
addPropertyValueIfNotEmpty("digest", "digest", element, configuration);
return configuration.getBeanDefinition();
}
private void addPropertyValueIfNotEmpty(final String attributeName, final String propertyName, final Element element, final BeanDefinitionBuilder factory) {
String attributeValue = element.getAttribute(attributeName);
if (!Strings.isNullOrEmpty(attributeValue)) {
factory.addPropertyValue(propertyName, attributeValue);
}
}
}
自定义Bean解析器通过继承AbstractBeanDefinitionParser类型并重写方法parseInternal方法来解析具体属性,当前Spring命名空间元素解析时候时会通过调用我们注册的命名空间解析器的parseInternal来获取Bean定义对象。
我们自定义Bean解析的目的就是通过自定义代码方式将xml中配置的元素属性设置到Bean的建模对象元数据中以此来构造BeanDefinition建模对象来让Spring为我们创建对象。
解析元素的过程主要是为ZookeeperConfiguration类型设置值和将配置类型ZookeeperConfiguration赋值给ZookeeperRegistryCenter构造器,最后设置ZookeeperRegistryCenter类型的Bean的初始化方法为init,ZookeeperRegistryCenter的init方法会进行连接Zookeeper操作,在Bean创建之后执行init方法,这与我们手动创建Zookeeper相关对象并手动初始化是一致的,只不过这里交给力Spring来做,手动创建Zookeeper配置相关对象可以参考前面的文章《Elastic-Job2.1.5源码-调度注册中心的设计原理》。
- END -