前言
在我們平時(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)容,從而大大簡化了我們用戶的配置萝勤。
如果不這樣做的話露筒,我們就需要配置一個(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è)屬性是可選屬性。
我們在使用自定義的標(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作為一個(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 tag
與nested 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é)果
在完成上面的配置操作后,我們現(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()));
}
}
至此滋早,已經(jīng)分析完了如何擴(kuò)展Spring中的文檔定義榄审。通過自己定義的元素,我們很好地向使用方屏蔽了底層的具體實(shí)現(xiàn)杆麸,其他的好處可以參考Webx中對(duì)使用Schema來擴(kuò)展Spring的優(yōu)勢搁进。