Spring 可擴(kuò)展XML配置機(jī)制

簡(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):

  1. targetNamespace俯树,后面在注冊(cè)和Spring Bean XML中都會(huì)用到贰盗;
  2. 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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市哀九,隨后出現(xiàn)的幾起案子阅束,更是在濱河造成了極大的恐慌息裸,老刑警劉巖沪编,帶你破解...
    沈念sama閱讀 222,729評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件访圃,死亡現(xiàn)場(chǎng)離奇詭異相嵌,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)批糟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門跃赚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來纬傲,“玉大人,你說我怎么就攤上這事算墨≈祝” “怎么了?”我有些...
    開封第一講書人閱讀 169,461評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵挖藏,是天一觀的道長(zhǎng)膜眠。 經(jīng)常有香客問我溜嗜,道長(zhǎng)炸宵,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,135評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮裹匙,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘拨黔。我一直安慰自己贺待,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,130評(píng)論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著哪工,像睡著了一般雁比。 火紅的嫁衣襯著肌膚如雪撤嫩。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,736評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音程奠,去河邊找鬼瞄沙。 笑死,一個(gè)胖子當(dāng)著我的面吹牛泛粹,可吹牛的內(nèi)容都是我干的肮疗。 我是一名探鬼主播伪货,決...
    沈念sama閱讀 41,179評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼碱呼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼宗侦!你這毒婦竟也來了矾利?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,124評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤欣鳖,失蹤者是張志新(化名)和其女友劉穎泽台,沒想到半個(gè)月后怀酷,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體胰坟,經(jīng)...
    沈念sama閱讀 46,657評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡笔横,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,723評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了厢塘。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片肌幽。...
    茶點(diǎn)故事閱讀 40,872評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡喂急,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出糕簿,到底是詐尸還是另有隱情狡孔,我是刑警寧澤苗膝,帶...
    沈念sama閱讀 36,533評(píng)論 5 351
  • 正文 年R本政府宣布离唐,位于F島的核電站,受9級(jí)特大地震影響侯繁,放射性物質(zhì)發(fā)生泄漏胖喳。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,213評(píng)論 3 336
  • 文/蒙蒙 一贮竟、第九天 我趴在偏房一處隱蔽的房頂上張望丽焊。 院中可真熱鬧,春花似錦咕别、人聲如沸技健。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,700評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽雌贱。三九已至,卻和暖如春偿短,著一層夾襖步出監(jiān)牢的瞬間欣孤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,819評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工勾怒, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留段只,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,304評(píng)論 3 379
  • 正文 我出身青樓误堡,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親肩狂。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,876評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容