用过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:service 对外提供RPC服务,一切都司空见惯。但你有没有想过dubbo:service 到底是什么?Spring是怎么解析它并把它注入到容器中的?
本文着重介绍Spring Framework 基于Schema风格的XML扩展机制,通过Spring提供的xml扩展机制,我们可以在spring.xml中加入自己的标签,之后Spring会帮我们解析并纳入自己的管理范围内。
环境配置
JDK 1.7
Spring 4.3.3.RELEASE
Maven 3.3
IDEA 15
示例
通过学习 Spring Extensible XML ,自己写了一个类似dubbo 自定义标签的demo样例供大家学习,工程结构如下图所示:
最后,我们也可以在Spring配置文件引入我们自定义的标签了,如下:
<?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:rpc="http://www.bytebeats.com/schema/rpc"
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/rpc http://www.bytebeats.com/schema/rpc/rpc.xsd">
<rpc:registry id="zk" protocol="zookeeper" address="127.0.0.1" />
<rpc:protocol id="hessian" name="hessian" port="9001"/>
<rpc:service id="rpcService" ref="helloService" interface="com.bytebeats.spring4.extension.service.IHelloService" timeout="5000" retries="1"></rpc:service>
<rpc: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>
1、定义XML schema
首先,我们需要定义一个xsd文件来声明XML标签元素,本文中为rpc.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/rpc"
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>
关于XML Schema这里不详述了,大家可以参考 w3school XML Schema 简介
2、定义解析器
定义一个BeanDefinitionParser负责解析xml,如下:
package com.bytebeats.spring4.extension.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 RpcServiceBeanDefinitionParser 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();
}
}
3、定义NamespaceHandler
package com.bytebeats.spring4.extension.xml;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
/**
* ${DESCRIPTION}
*
* @author Ricky Fung
* @create 2016-11-23 11:48
*/
public class RpcNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("service", new RpcServiceBeanDefinitionParser());
registerBeanDefinitionParser("ref", new RpcReferenceBeanDefinitionParser());
registerBeanDefinitionParser("registry", new RpcRegistryBeanDefinitionParser());
registerBeanDefinitionParser("protocol", new RpcProtocolBeanDefinitionParser());
}
}
4、配置schema和handler
在META-INF目录下面分别新建spring.handlers和spring.schemas文件。
spring.schemas
http\://www.bytebeats.com/schema/rpc/rpc.xsd=/META-INF/rpc.xsd
spring.handlers
http\://www.bytebeats.com/schema/rpc=com.bytebeats.spring4.extension.xml.RpcNamespaceHandler
5、使用
辛苦这么大半天,接下来该看看成果了,首先是
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:rpc="http://www.bytebeats.com/schema/rpc"
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/rpc http://www.bytebeats.com/schema/rpc/rpc.xsd">
<context:annotation-config/>
<context:component-scan base-package="com.bytebeats.spring4.extension.xml"/>
<rpc:registry id="zk" protocol="zookeeper" address="127.0.0.1" />
<rpc:protocol id="hessian" name="hessian" port="9001"/>
<rpc:service id="rpcService" ref="helloService" interface="com.bytebeats.spring4.extension.service.IHelloService" timeout="5000" retries="1"></rpc:service>
<rpc: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 容器中获取自定义的xml标签元素,如下:
public class App {
public static void main( String[] args ) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
IHelloService helloService = (IHelloService) context.getBean("helloService");
System.out.println(helloService.sayHello("ricky"));
RpcServiceBean rpcServiceBean = (RpcServiceBean) context.getBean("rpcService");
System.out.println("rpcServiceBean:"+rpcServiceBean.getInterfaceName());
RpcReferenceBean accountService = (RpcReferenceBean) context.getBean("accountService");
System.out.println("accountService:"+accountService.getInterfaceName());
RpcRegistryBean rpcRegistryBean = (RpcRegistryBean) context.getBean("zk");
System.out.println("rpcRegistryBean:"+rpcRegistryBean.getAddress());
RpcProtocolBean rpcProtocolBean = (RpcProtocolBean) context.getBean("hessian");
System.out.println("rpcProtocolBean:"+rpcProtocolBean.getPort());
context.close();
}
}
点此下载源码:https://github.com/TiFG/spring4-samples/tree/master/spring-ch3-extensible
参考
Spring Extensible XML:http://docs.spring.io/spring/docs/current/spring-framework-reference/html/xml-custom.html