詳細(xì)分析如何擴(kuò)展Spring中的文檔定義

前言

在我們平時(shí)使用Spring框架的時(shí)候,在90%的情況下都是使用其自帶的默認(rèn)標(biāo)簽吴汪,因?yàn)槠淠J(rèn)提供給我們的標(biāo)簽元素在絕大多數(shù)情況下也滿足了我們的需求。那么我們?yōu)槭裁催€要自己去定義標(biāo)簽去進(jìn)行擴(kuò)展呢?其本質(zhì)原因還是因?yàn)槟悴荒鼙WC任何情況下當(dāng)前的場景就能滿足你的需求,在某些特殊情況下彰亥,我們不得不對(duì)其進(jìn)行擴(kuò)展泪漂;比如Dubbo框架就對(duì)Spring默認(rèn)的標(biāo)簽元素進(jìn)行了擴(kuò)展廊营,加入了自己定制化的一些內(nèi)容,從而大大簡化了我們用戶的配置萝勤。

Dubbo自定義標(biāo)簽說明.png

如果不這樣做的話露筒,我們就需要配置一個(gè)具體的Bean,這樣會(huì)造成兩個(gè)問題:
1.增加用戶配置的復(fù)雜度
2.破壞封裝特性敌卓,將底層實(shí)現(xiàn)細(xì)節(jié)強(qiáng)制地暴露給用戶
因此慎式,通過上面的分析我們知道,學(xué)會(huì)如何擴(kuò)展Spring的文檔定義還是非常有必要的趟径,本文的目的就是將詳細(xì)說明我們應(yīng)該如何對(duì)其進(jìn)行擴(kuò)展瘪吏,并且將其與Spring的Ioc容器進(jìn)行集成。

如何實(shí)現(xiàn)

從Spring2.0開始舵抹,Spring擁有了一個(gè)新的特征機(jī)制:可以基于Schema對(duì)基本Spring XML格式進(jìn)行擴(kuò)展肪虎,從而實(shí)現(xiàn)自定義和配置bean。

我們要實(shí)現(xiàn)一個(gè)自定義標(biāo)簽的具體步驟如下:
1.編寫一個(gè)XML Schema來描述我們自定義的元素
2.編寫一個(gè)NamespaceHandler接口的實(shí)現(xiàn)(這步非常簡單)
3.編寫一個(gè)或者多個(gè)BeanDefinitionParser(BeanDefinitionParser是真正完成XML元素的解析任務(wù))
4.將以上的構(gòu)建都注冊到Spring容器中(這步也非常簡單)
下面我們將對(duì)上面的每一個(gè)步驟做詳細(xì)的說明惧蛹。

定義Schema文檔

<?xml version="1.0" encoding="UTF-8"?>
<!--這里xmlns是用于說明這份XSD的命名空間
   targetNamespace必須指定扇救,因?yàn)镾pring會(huì)根據(jù)namespace來進(jìn)行NamespaceHandler的映射
   elementFormDefault="qualified"表示該Schema文件中聲明的元素在使用時(shí)需要加命名空間
   attributeFormDefault="unqualified"表示元素的屬性無需加命名空間
-->
<xsd:schema xmlns="http://www.panlingxiao.com/schema/myns"
            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            xmlns:beans="http://www.springframework.org/schema/beans"
            targetNamespace="http://www.panlingxiao.com/schema/myns" 
            elementFormDefault="qualified" attributeFormDefault="unqualified">

    <!--因?yàn)樵谖臋n定義中又使用到了Spring的XSD文件,因此手動(dòng)需要引入-->
        <xsd:import namespace="http://www.springframework.org/schema/beans" 
schemaLocation="http://www.springframework.org/schema/beans/spring-beans.xsd"/>


    <!--定義的元素名稱為dateformat-->
    <xsd:element name="dateformat">
        <xsd:complexType>
            <xsd:complexContent>
                <!---
                    這里的xsd:extension表示從beans:identifiedType從這個(gè)復(fù)雜類型中繼承
                    并且在它的基礎(chǔ)上又添加了自己的兩個(gè)屬性
                -->
                <xsd:extension base="beans:identifiedType">
                    <xsd:attribute name="lenient" type="xsd:boolean"/>
                    <xsd:attribute name="pattern" type="xsd:string" use="required"/>
                </xsd:extension>
            </xsd:complexContent>
        </xsd:complexType>
    </xsd:element>
</xsd:schema>

beans:identifiedType是Spring自己所定義的一個(gè)復(fù)雜類型香嗓,該類型只有一個(gè)id屬性迅腔。我們自己定義的復(fù)雜類型從該類型中繼承,那么我們的類型也將擁有id屬性靠娱,并且還自己多增加了兩個(gè)額外屬性沧烈。因此,我們所定義的dateformat元素是一個(gè)復(fù)雜類型像云,它有三個(gè)元素锌雀,其中pattern是必填屬性蚂夕,而其余兩個(gè)屬性是可選屬性。

Paste_Image.png

我們在使用自定義的標(biāo)簽時(shí)腋逆,其內(nèi)容是這樣:

 <!--直接自動(dòng)繼承了一個(gè)id屬性,其余屬性為自己定義--> 
 <myns:dateformat id="dateFormat" pattern="yyyy-MM-dd HH:mm" lenient="true"/>

如果對(duì)上面描述內(nèi)容還不是特別明白的話婿牍,建議參考XML Schema extension 元素說明

自定義NamespaceHandler

在完成Scheman文件編寫之后惩歉,接下來我們就是去自己定義一個(gè)NamespaceHandler去解析特定命名空間下的元素等脂,下面是該接口的說明:

NamespaceHandler接口說明

NamespaceHandler作為一個(gè)基礎(chǔ)接口,它被DefaultBeanDefinitionDocumentReader(可以簡單認(rèn)為它是一個(gè)XML文檔的解析器)用于處理Spring XML配置文件中所定制化的命名空間撑蚌。當(dāng)在解析過程中上遥,出現(xiàn)top-level標(biāo)簽時(shí),則需要返回一個(gè)BeanDefinitionParser争涌,當(dāng)出現(xiàn)一個(gè)內(nèi)嵌的定制標(biāo)簽時(shí)粉楚,則需要返回一個(gè)BeanDefinitionDecorator。下面簡單說明一下top-level tagnested tags之間的區(qū)別:

<?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:myns="http://www.panlingxiao.com/schema/myns"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.panlingxiao.com/schema/myns http://www.panlingxiao.com/schema/myns/myns.xsd">
    <!-- as a top-level bean -->
    <myns:dateformat id="defaultDateFormat" pattern="yyyy-MM-dd HH:mm" lenient="true"/>

    <bean class="com.panlingxiao.spring.ioc.tag.TagBean" id="tagBean">
        <!--nested tag-->
        <myns:hello/>
    </bean>
</beans>

上面的文檔最后還告訴我們第煮,作為開發(fā)者解幼,通常無需直接實(shí)現(xiàn)NamespaceHandler接口,而只需充分使用已經(jīng)提供的NamespaceHandlerSupport即可包警。

package com.panlingxiao.spring.ioc.tag;


import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

/**
 * 自定義NamespaceHandler撵摆,直接繼承NamespaceHandlerSupport即可
 * NamespaceHandler只提供解析器的注冊與管理,并不提供真正的解析邏輯
 */
public class MyNamespaceHandler extends NamespaceHandlerSupport {
   
   /**
     * init方法會(huì)被Spring自動(dòng)調(diào)用
     */
    @Override
    public void init() {
        /*
         * 當(dāng)解析到dateformat元素時(shí)害晦,使用SimpleDateFormatBeanDefinitionParser解析器進(jìn)行解析
         */
        registerBeanDefinitionParser("dateformat", new SimpleDateFormatBeanDefinitionParser());
    }
}

自定義BeanDefinitionParser

當(dāng)NamespaceHandler遇到一個(gè)特定類型的XML標(biāo)簽被映射到對(duì)應(yīng)的BeanDefinitionParser時(shí)特铝,就會(huì)通過它來對(duì)元素進(jìn)行解析。換而言之壹瘟,BeanDefinitionParser是真正意義上完成元素解析邏輯功能的類鲫剿,它會(huì)將一個(gè)元素轉(zhuǎn)換成一個(gè)BeanDefinition,然后注冊到BeanDefinitionRegistry中稻轨,下面是我們的實(shí)現(xiàn)灵莲,我們希望將我們自定義的元素轉(zhuǎn)換成一個(gè)SimpleDateFormat對(duì)象。

package com.panlingxiao.spring.ioc.tag;

import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;

import java.text.SimpleDateFormat;

/**
 * Created by panlingxiao on 2017/6/14.
 *
 * 自定義BeanDefinitionParser,完成XML元素的解析處理
 */
public class SimpleDateFormatBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { //①

    /**
     * 返回元素對(duì)應(yīng)的Class殴俱,Class對(duì)象會(huì)被注入到BeanDefinition中
     * @param element
     * @return
     */
    protected Class getBeanClass(Element element) {
        return SimpleDateFormat.class;  //②
    }

    /**
     * 執(zhí)行真正的解析邏輯
     * @param element
     * @param bean
     */
    protected void doParse(Element element, BeanDefinitionBuilder bean) {
        // this will never be null since the schema explicitly requires that a value be supplied
        String pattern = element.getAttribute("pattern");
        //因?yàn)镾impleDateFormat需要一個(gè)pattern字符串作為構(gòu)造方法的參數(shù),因此設(shè)置一個(gè)構(gòu)造方法參數(shù)
        bean.addConstructorArgValue(pattern);

        // this however is an optional property
        String lenient = element.getAttribute("lenient");
        //判斷是否設(shè)置了lenient屬性政冻,如果設(shè)置,則設(shè)置lenient屬性為true
        if (StringUtils.hasText(lenient)) {
            bean.addPropertyValue("lenient", Boolean.valueOf(lenient));
        }
    }
}

① 我們直接使用了Spring為我們所提供的AbstractSingleBeanDefinitionParser,它會(huì)幫我們?nèi)プ詣?dòng)地去處理創(chuàng)建BeanDefinition的任務(wù)线欲,我們只需向其提供的參數(shù)中設(shè)置內(nèi)容即可明场。
② 我們指定了當(dāng)前被解析的元素所對(duì)應(yīng)的對(duì)象類型,返回的Class信息會(huì)被自動(dòng)地設(shè)置到BeanDefinition中李丰,用于后續(xù)對(duì)象的創(chuàng)建苦锨。

注冊Handler和Schema

上面我們已經(jīng)完成所有的處理邏輯,下面我們該考慮如何讓Spring的能夠解析到我們自定義標(biāo)簽,使用我們給定的解析器去處理舟舒;我們只需將我們定制的nameSpaceHandler和XSD文件注冊到兩個(gè)特殊的Properites文件中即可拉庶。這兩個(gè)文件都位于ClassPath路徑下的META-INF目錄下。

META-INF/spring.handlers

通過META-INF/spring.handlers配置文件魏蔗,我們可以將某個(gè)命名空間與對(duì)應(yīng)的NamespaceHandler進(jìn)行映射砍的。

#指定該命名空間下的元素使自己給定的NameSpaceHandler進(jìn)行處理
http\://www.panlingxiao.com/schema/myns=com.panlingxiao.spring.ioc.tag.MyNamespaceHandler

(由于:在Java的Properties中是一個(gè)有效的分隔符痹筛,因?yàn)槲覀冊谶@里需要進(jìn)行一次轉(zhuǎn)義的處理)

META-INF/spring.schemas

META-INF/spring.schemas用于將在XML中聲明的XSD的URL地址與本地的XSD進(jìn)行關(guān)聯(lián)莺治,這樣避免了從遠(yuǎn)程下面文件到本地的處理,并且即使在遠(yuǎn)程服務(wù)器上不存在這個(gè)文件帚稠,也不會(huì)有任何問題谣旁。

http\://www.panlingxiao.com/schema/myns/myns.xsd=META-INF/spring-custom-tag.xsd
文件最終結(jié)果.png

測試結(jié)果

在完成上面的配置操作后,我們現(xiàn)在可以驗(yàn)證我們最終的結(jié)果了~

package com.panlingxiao.spring.ioc.tag;

import java.text.SimpleDateFormat;

/**
 * Created by panlingxiao on 2017/6/14.
 */
public class TagBean {

    private SimpleDateFormat dateFormat;

    public SimpleDateFormat getDateFormat() {
        return dateFormat;
    }

    public void setDateFormat(SimpleDateFormat dateFormat) {
        this.dateFormat = dateFormat;
    }
}
<?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:myns="http://www.panlingxiao.com/schema/myns"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.panlingxiao.com/schema/myns http://www.panlingxiao.com/schema/myns/myns.xsd">

    <!-- as a top-level bean -->
    <myns:dateformat id="defaultDateFormat" pattern="yyyy-MM-dd HH:mm" lenient="true"/>

    <bean class="com.panlingxiao.spring.ioc.tag.TagBean" id="tagBean">
        <property name="dateFormat">
           <!--直接根據(jù)id引用,自定義的元素最終會(huì)被轉(zhuǎn)換成SimpleDateFormat對(duì)象-->
           <ref bean="defaultDateFormat"/>
        </property>
    </bean>

</beans>
package com.panlingxiao.spring.ioc.tag;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.io.IOException;
import java.util.Date;

/**
 * Created by panlingxiao on 2017/6/4.
 * Spring自定義標(biāo)簽
 */
public class SpringCustomTagExample {

    public static void main(String[] args) throws IOException {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("tag/custom-tag.xml");
        TagBean tagBean = ctx.getBean("tagBean", TagBean.class);
        System.out.println(tagBean.getDateFormat().format(new Date()));

    }
}
測試結(jié)果

至此滋早,已經(jīng)分析完了如何擴(kuò)展Spring中的文檔定義榄审。通過自己定義的元素,我們很好地向使用方屏蔽了底層的具體實(shí)現(xiàn)杆麸,其他的好處可以參考Webx中對(duì)使用Schema來擴(kuò)展Spring的優(yōu)勢搁进。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市昔头,隨后出現(xiàn)的幾起案子饼问,更是在濱河造成了極大的恐慌,老刑警劉巖揭斧,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件莱革,死亡現(xiàn)場離奇詭異,居然都是意外死亡讹开,警方通過查閱死者的電腦和手機(jī)盅视,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來旦万,“玉大人闹击,你說我怎么就攤上這事〕伤遥” “怎么了赏半?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長狰腌。 經(jīng)常有香客問我除破,道長,這世上最難降的妖魔是什么琼腔? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任瑰枫,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘光坝。我一直安慰自己尸诽,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布盯另。 她就那樣靜靜地躺著性含,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鸳惯。 梳的紋絲不亂的頭發(fā)上商蕴,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音芝发,去河邊找鬼绪商。 笑死,一個(gè)胖子當(dāng)著我的面吹牛辅鲸,可吹牛的內(nèi)容都是我干的格郁。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼独悴,長吁一口氣:“原來是場噩夢啊……” “哼例书!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起刻炒,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤决采,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后落蝙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體织狐,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年筏勒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了移迫。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡管行,死狀恐怖厨埋,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情捐顷,我是刑警寧澤荡陷,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站迅涮,受9級(jí)特大地震影響废赞,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜叮姑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一唉地、第九天 我趴在偏房一處隱蔽的房頂上張望据悔。 院中可真熱鬧,春花似錦耘沼、人聲如沸极颓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽菠隆。三九已至,卻和暖如春狂秘,著一層夾襖步出監(jiān)牢的瞬間骇径,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工赃绊, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留既峡,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓碧查,卻偏偏與公主長得像,于是被迫代替她去往敵國和親校仑。 傳聞我的和親對(duì)象是個(gè)殘疾皇子忠售,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)迄沫,斷路器稻扬,智...
    卡卡羅2017閱讀 134,599評(píng)論 18 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,748評(píng)論 6 342
  • ?可擴(kuò)展的XML Schema機(jī)制 從Spring2.0開始,Spring提供了XML Schema可擴(kuò)展機(jī)制羊瘩,用...
    gsonliu閱讀 7,897評(píng)論 0 9
  • 2 R4VE 推薦理由:我喜歡它的背景圖 可以根據(jù)自己的喜好來選 效果圖如下 推薦指數(shù)...
    gui你好好閱讀 204評(píng)論 0 0
  • 先貼一個(gè)目前為止最好的記錄 這個(gè)是地方號(hào)泰佳。之前除了一篇辟謠文我翻了翻后臺(tái)記錄屬這個(gè)最好。 然后再貼一個(gè)我負(fù)責(zé)的燒烤...
    張涼嘉閱讀 226評(píng)論 0 0