用過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