简介
平时我们基于 XML定义Bean 格式如下:
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.bytebeats.mybatis3.mapper.trade" />
<property name="sqlSessionFactoryBeanName" value="tradeSqlSessionFactory" />
</bean>
那如果我们想扩展XML Bean的定义和配置呢?如下所示:
<myns:dateformat id="dateFormat"
pattern="yyyy-MM-dd HH:mm"
lenient="true"/>
此时你就需要了解一下 Spring’s extensible XML configuration mechanism。
官方介绍如下:
Since version 2.0, Spring has featured a mechanism for schema-based extensions to the basic Spring XML format for defining and configuring beans. This section is devoted to detailing how you would go about writing your own custom XML bean definition parsers and integrating such parsers into the Spring IoC container.
To facilitate the authoring of configuration files using a schema-aware XML editor, Spring’s extensible XML configuration mechanism is based on XML Schema.
用过dubbo的同学应该很熟悉下面的配置:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<!-- 提供方应用信息,用于计算依赖关系 -->
<dubbo:application name="hello-world-app" />
<!-- 使用zk为注册中心暴露服务地址 -->
<dubbo:registry address="zookeeper://127.0.0.1:2181" />
<!-- 用dubbo协议在20880端口暴露服务 -->
<dubbo:protocol name="dubbo" port="20880" />
<bean id="demoService" class="com.alibaba.dubbo.demo.provider.DemoServiceImpl" />
<dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService" />
</beans>
dubbo官方文档告诉我们 使用 <dubbo:service />
来暴露dubbo服务提供者,使用,<dubbo:reference />
来引用远程服务。那<dubbo:service />
背后究竟做了什么呢?我们如何才能实现跟dubbo类似的<xxx:service />
呢?
本文着重介绍Spring Framework 基于Schema风格的XML扩展机制,通过Spring提供的xml扩展机制,我们可以在spring.xml中加入自己的标签,之后Spring会帮我们解析并纳入自己的管理范围内。
环境配置
- JDK 1.7
- Spring 4.3.6.RELEASE
- Maven 3.3
- IDEA 15.0
快速入门
看完 Spring’s extensible XML configuration mechanism,不得不佩服 老外不仅代码写的NB,技术文档写的也非常nice。
本文通过一个简单示例(山寨dubbo 那一套),提供 <mario:service />
,<mario:ref />
,<mario:registry />
,<mario:protocol />
。
要实现 <xxx:service />
XML Bean配置机制,大致分为4步:
- 编写自己的 XML schema文件;
- 编写自定义
NamespaceHandler
实现类; - 编写一个或者多个
BeanDefinitionParser
实现类; - 注册自定义 XML schema和
NamespaceHandler
实现类。
1、定义XML schema
首先,我们需要定义一个xsd文件来声明XML标签元素,本文中为 mario.xsd,如下:
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.bytebeats.com/schema/rpc"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:beans="http://www.springframework.org/schema/beans"
targetNamespace="http://www.bytebeats.com/schema/mario"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xsd:import namespace="http://www.springframework.org/schema/beans"/>
<xsd:complexType name="abstractConfig">
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element ref="beans:property" minOccurs="0" maxOccurs="unbounded" />
</xsd:choice>
<xsd:anyAttribute namespace="##other" processContents="lax" />
</xsd:complexType>
<xsd:element name="service">
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="abstractConfig">
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element ref="beans:property" minOccurs="0" maxOccurs="unbounded" />
</xsd:choice>
<xsd:attribute name="id" type="xsd:ID" />
<xsd:attribute name="ref" type="xsd:string" use="required"/>
<xsd:attribute name="interface" type="xsd:string" use="required"/>
<xsd:attribute name="group" type="xsd:string" use="optional"/>
<xsd:attribute name="registry" type="xsd:string" use="optional"/>
<xsd:attribute name="version" type="xsd:string" use="optional"/>
<xsd:attribute name="timeout" type="xsd:string" use="optional"/>
<xsd:attribute name="retries" type="xsd:string" use="optional"/>
<xsd:attribute name="async" type="xsd:boolean" use="optional"/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:element name="ref">
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="abstractConfig">
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element ref="beans:property" minOccurs="0" maxOccurs="unbounded" />
</xsd:choice>
<xsd:attribute name="id" type="xsd:ID" />
<xsd:attribute name="interface" type="xsd:string" use="required"/>
<xsd:attribute name="group" type="xsd:string" use="optional"/>
<xsd:attribute name="registry" type="xsd:string" use="optional"/>
<xsd:attribute name="version" type="xsd:string" use="optional"/>
<xsd:attribute name="timeout" type="xsd:string" use="optional"/>
<xsd:attribute name="retries" type="xsd:string" use="optional"/>
<xsd:attribute name="async" type="xsd:boolean" use="optional"/>
<xsd:attribute name="check" type="xsd:boolean" use="optional"/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:element name="registry">
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="abstractConfig">
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element ref="beans:property" minOccurs="0" maxOccurs="unbounded" />
</xsd:choice>
<xsd:attribute name="id" type="xsd:ID" />
<xsd:attribute name="protocol" type="xsd:string" use="required"/>
<xsd:attribute name="address" type="xsd:string" use="required"/>
<xsd:attribute name="username" type="xsd:string" use="optional"/>
<xsd:attribute name="password" type="xsd:string" use="optional"/>
<xsd:attribute name="check" type="xsd:boolean" use="optional"/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:element name="protocol">
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="abstractConfig">
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element ref="beans:property" minOccurs="0" maxOccurs="unbounded" />
</xsd:choice>
<xsd:attribute name="id" type="xsd:ID" />
<xsd:attribute name="name" type="xsd:string" use="required"/>
<xsd:attribute name="port" type="xsd:string" use="required"/>
<xsd:attribute name="host" type="xsd:string" use="optional"/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
</xsd:schema>
需要注意两点:
- targetNamespace,后面在注册和Spring Bean XML中都会用到;
- mario.xsd 文件需放在META-INF目录下。
2、定义NamespaceHandler
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
/**
* ${DESCRIPTION}
*
* @author Ricky Fung
* @create 2016-11-23 11:48
*/
public class MarioNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("service", new MarioServiceBeanDefinitionParser());
registerBeanDefinitionParser("ref", new MarioReferenceBeanDefinitionParser());
registerBeanDefinitionParser("registry", new MarioRegistryBeanDefinitionParser());
registerBeanDefinitionParser("protocol", new MarioProtocolBeanDefinitionParser());
}
}
3、定义BeanDefinitionParser
定义一个BeanDefinitionParser负责解析上面mario schema中定义的xml标签,如下:
import com.bytebeats.spring4.extension.domain.RpcServiceBean;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
/**
* ${DESCRIPTION}
*
* @author Ricky Fung
* @create 2016-11-23 11:50
*/
public class MarioServiceBeanDefinitionParser extends AbstractBeanDefinitionParser {
@Override
protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
return parseComponet(element, parserContext);
}
private AbstractBeanDefinition parseComponet(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(RpcServiceBean.class);
String id = element.getAttribute("id");
if (StringUtils.hasText(id)) {
builder.addPropertyValue("id", id);
}
String ref = element.getAttribute("ref");
builder.addPropertyValue("ref", ref);
String interfaceName = element.getAttribute("interface");
builder.addPropertyValue("interfaceName", interfaceName);
String group = element.getAttribute("group");
if (StringUtils.hasText(group)) {
builder.addPropertyValue("group", group);
}
String registry = element.getAttribute("registry");
if (StringUtils.hasText(registry)) {
builder.addPropertyValue("registry", registry);
}
String version = element.getAttribute("version");
if (StringUtils.hasText(version)) {
builder.addPropertyValue("version", version);
}
String timeout = element.getAttribute("timeout");
if (StringUtils.hasText(timeout)) {
builder.addPropertyValue("timeout", Integer.parseInt(timeout));
}
String retries = element.getAttribute("retries");
if (StringUtils.hasText(retries)) {
builder.addPropertyValue("retries", Integer.parseInt(retries));
}
String async = element.getAttribute("async");
if (StringUtils.hasText(async)) {
builder.addPropertyValue("async", Boolean.valueOf(async));
}
return builder.getBeanDefinition();
}
}
4、注册schema和handler
在META-INF目录下面分别新建spring.handlers和spring.schemas文件。
spring.schemas 如下:
http\://www.bytebeats.com/schema/mario/mario.xsd=/META-INF/mario.xsd
spring.handlers内容如下:
http\://www.bytebeats.com/schema/mario=com.bytebeats.spring4.extension.xml.MarioNamespaceHandler
到这里,所有准备工作都已经完成了,接下来就是如何在 Spring XML配置文件中使用了。
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mario="http://www.bytebeats.com/schema/mario"
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-4.0.xsd
http://www.bytebeats.com/schema/mario http://www.bytebeats.com/schema/mario/mario.xsd">
<context:annotation-config/>
<context:component-scan base-package="com.bytebeats.spring4.extension.xml"/>
<mario:registry id="zk" protocol="zookeeper" address="127.0.0.1" />
<mario:protocol id="hessian" name="hessian" port="9001"/>
<mario:service id="rpcService" ref="helloService" interface="com.bytebeats.spring4.extension.service.IHelloService" timeout="5000" retries="1"></rpc:service>
<mario:ref id="accountService" interface="com.bytebeats.spring4.extension.service.IAccountService" retries="0" check="false" />
<bean id="helloService" class="com.bytebeats.spring4.extension.service.impl.HelloServiceImpl" />
</beans>
参考资料
Spring Extensible XML:http://docs.spring.io/spring/docs/current/spring-framework-reference/html/xml-custom.html