簡(jiǎn)介
平時(shí)我們基于 XML定義Bean 格式如下:
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.bytebeats.mybatis3.mapper.trade" />
<property name="sqlSessionFactoryBeanName" value="tradeSqlSessionFactory" />
</bean>
那如果我們想擴(kuò)展XML Bean的定義和配置呢?如下所示:
<myns:dateformat id="dateFormat"
pattern="yyyy-MM-dd HH:mm"
lenient="true"/>
此時(shí)你就需要了解一下 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的同學(xué)應(yīng)該很熟悉下面的配置:
<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">
<!-- 提供方應(yīng)用信息,用于計(jì)算依賴關(guān)系 -->
<dubbo:application name="hello-world-app" />
<!-- 使用zk為注冊(cè)中心暴露服務(wù)地址 -->
<dubbo:registry address="zookeeper://127.0.0.1:2181" />
<!-- 用dubbo協(xié)議在20880端口暴露服務(wù) -->
<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服務(wù)提供者期吓,使用早歇,<dubbo:reference />
來引用遠(yuǎn)程服務(wù)。那<dubbo:service />
背后究竟做了什么呢讨勤?我們?nèi)绾尾拍軐?shí)現(xiàn)跟dubbo類似的<xxx:service />
呢箭跳?
本文著重介紹Spring Framework 基于Schema風(fēng)格的XML擴(kuò)展機(jī)制,通過Spring提供的xml擴(kuò)展機(jī)制潭千,我們可以在spring.xml中加入自己的標(biāo)簽谱姓,之后Spring會(huì)幫我們解析并納入自己的管理范圍內(nèi)。
環(huán)境配置
- JDK 1.7
- Spring 4.3.6.RELEASE
- Maven 3.3
- IDEA 15.0
快速入門
看完 Spring’s extensible XML configuration mechanism屉来,不得不佩服 老外不僅代碼寫的NB茄靠,技術(shù)文檔寫的也非常nice蝶桶。
本文通過一個(gè)簡(jiǎn)單示例(山寨dubbo 那一套)真竖,提供 <mario:service />
恢共,<mario:ref />
讨韭,<mario:registry />
涨岁,<mario:protocol />
梢薪。
要實(shí)現(xiàn) <xxx:service />
XML Bean配置機(jī)制秉撇,大致分為4步:
- 編寫自己的 XML schema文件琐馆;
- 編寫自定義
NamespaceHandler
實(shí)現(xiàn)類瘦麸; - 編寫一個(gè)或者多個(gè)
BeanDefinitionParser
實(shí)現(xiàn)類滋饲; - 注冊(cè)自定義 XML schema和
NamespaceHandler
實(shí)現(xiàn)類喊巍。
1崭参、定義XML schema
首先,我們需要定義一個(gè)xsd文件來聲明XML標(biāo)簽元素奄喂,本文中為 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>
需要注意兩點(diǎn):
- targetNamespace俯树,后面在注冊(cè)和Spring Bean XML中都會(huì)用到贰盗;
- 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
定義一個(gè)BeanDefinitionParser負(fù)責(zé)解析上面mario schema中定義的xml標(biāo)簽菩浙,如下:
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、注冊(cè)schema和handler
在META-INF目錄下面分別新建spring.handlers和spring.schemas文件先嬉。
spring.schemas 如下:
http\://www.bytebeats.com/schema/mario/mario.xsd=/META-INF/mario.xsd
spring.handlers內(nèi)容如下:
http\://www.bytebeats.com/schema/mario=com.bytebeats.spring4.extension.xml.MarioNamespaceHandler
到這里,所有準(zhǔn)備工作都已經(jīng)完成了含懊,接下來就是如何在 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