Spring中XML schema擴(kuò)展機(jī)制的深入講解
前言
很久沒有寫關(guān)于 Spring 的文章了,最近在系統(tǒng)梳理 Dubbo 代碼的過程中發(fā)現(xiàn)了 XML schema 這個(gè)被遺漏的知識點(diǎn)。由于工作中使用 SpringBoot 比較多的原因,幾乎很少接觸 XML,此文可以算做是亡羊補(bǔ)牢,另一方面,也為后續(xù)的 Dubbo 源碼解析做個(gè)鋪墊。
XML schema 擴(kuò)展機(jī)制是啥?從Spring2.0開始,Spring提供了XML Schema可擴(kuò)展機(jī)制,用戶可以自定義XML Schema文件,并自定義XML Bean解析器,并集成到Spring Ioc 容器中。這并不是一塊很大的知識點(diǎn),翻閱一下 Spring 的文檔,我甚至沒找到一個(gè)貫穿上下文的詞來描述這個(gè)功能,XML Schema Authoring 是文檔中對應(yīng)的標(biāo)題,簡單來說:
Spring 為基于 XML 構(gòu)建的應(yīng)用提供了一種擴(kuò)展機(jī)制,用于定義和配置 Bean。 它允許使用者編寫自定義的 XML bean 解析器,并將解析器本身以及最終定義的 Bean 集成到 Spring IOC 容器中。
Dubbo 依賴了 Spring,并提供了一套自定義的 XML 標(biāo)簽,<dubbo:application> ,<dubbo:registry> ,<dubbo:protocol>,<dubbo:service>。作為使用者,大多數(shù)人只需要關(guān)心這些參數(shù)如何配置,但不知道有沒有人好奇過,它們是如何加載進(jìn)入 Spring 的 IOC 容器中被其他組件使用的呢?這便牽扯出了今天的主題:Spring 對 XML schema 的擴(kuò)展支持。
自定義 XML 擴(kuò)展
為了搞懂 Spring 的 XML 擴(kuò)展機(jī)制,最直接的方式便是實(shí)現(xiàn)一個(gè)自定義的擴(kuò)展。實(shí)現(xiàn)的步驟也非常簡單,分為四步:
- 編寫一個(gè) XML schema 文件描述的你節(jié)點(diǎn)元素。
- 編寫一個(gè) NamespaceHandler 的實(shí)現(xiàn)類
- 編寫一個(gè)或者多個(gè) BeanDefinitionParser 的實(shí)現(xiàn) (關(guān)鍵步驟).
- 注冊上述的 schema 和 handler。
我們的目的便是想要實(shí)現(xiàn)一個(gè) kirito XML schema,我們的項(xiàng)目中可以自定義 kirito.xml,在其中會以 kirito 為標(biāo)簽來定義不同的類,并在最終的測試代碼中驗(yàn)證這些聲明在 kirito.xml 的類是否被 Spring 成功加載。大概像這樣,是不是和 dubbo.xml 的格式很像呢?
動手實(shí)現(xiàn)
有了明確的目標(biāo),我們逐步開展自己的工作。
1 編寫kirito.xsd
resources/META-INF/kirito.xsd
<?xml version="1.0" encoding="UTF-8"?> <xsd:schema xmlns="http://www.cnkirito.moe/schema/kirito" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:beans="http://www.springframework.org/schema/beans" targetNamespace="http://www.cnkirito.moe/schema/kirito"> ① <xsd:import namespace="http://www.springframework.org/schema/beans"/> <xsd:element name="application"> ② <xsd:complexType> <xsd:complexContent> <xsd:extension base="beans:identifiedType"> <xsd:attribute name="name" type="xsd:string" use="required"/> </xsd:extension> </xsd:complexContent> </xsd:complexType> </xsd:element> <xsd:element name="service"> ② <xsd:complexType> <xsd:complexContent> <xsd:extension base="beans:identifiedType"> <xsd:attribute name="name" type="xsd:string" use="required"/> </xsd:extension> </xsd:complexContent> </xsd:complexType> </xsd:element> </xsd:schema>
① 注意這里的 targetNamespace="http://www.cnkirito.moe/schema/kirito"
這便是之后 kirito 標(biāo)簽的關(guān)鍵點(diǎn)。
② kirito.xsd 定義了兩個(gè)元素: application 和 service,出于簡單考慮,都只有一個(gè) name 字段。
schema 的意義在于它可以和 eclipse/IDEA 這樣智能化的集成開發(fā)環(huán)境形成很好的搭配,在編輯 XML 的過程中,用戶可以獲得告警和提示。 如果配置得當(dāng),可以使用自動完成功能讓用戶在事先定義好的枚舉類型中進(jìn)行選擇。
2 編寫KiritoNamespaceHandler
public class KiritoNamespaceHandler extends NamespaceHandlerSupport { @Override public void init() { super.registerBeanDefinitionParser("application", new KiritoBeanDefinitionParser(ApplicationConfig.class)); super.registerBeanDefinitionParser("service", new KiritoBeanDefinitionParser(ServiceBean.class)); } }
完成 schema 之后,還需要一個(gè) NamespaceHandler 來幫助 Spring 解析 XML 中不同命名空間的各類元素。
<kirito:application name="kirito"/> <dubbo:application name="dubbo"/> <motan:application name="motan"/>
不同的命名空間需要不同的 NamespaceHandler 來處理,在今天的示例中,我們使用 KiritoNamespaceHandler 來解析 kirito 命名空間。KiritoNamespaceHandler 繼承自 NamespaceHandlerSupport 類,并在其 init() 方法中注冊了兩個(gè) BeanDefinitionParser ,用于解析 kirito 命名空間/kirito.xsd 約束中定義的兩個(gè)元素:application,service。BeanDefinitionParser 是下一步的主角,我們暫且跳過,將重心放在父類 NamespaceHandlerSupport 之上。
public interface NamespaceHandler { void init(); BeanDefinition parse(Element element, ParserContext parserContext); BeanDefinitionHolder decorate(Node source, BeanDefinitionHolder definition, ParserContext parserContext); }
NamespaceHandlerSupport 是 NamespaceHandler 命名空間處理器的抽象實(shí)現(xiàn),我粗略看了NamespaceHandler 的幾個(gè)實(shí)現(xiàn)類,parse 和 decorate 方法可以完成元素節(jié)點(diǎn)的組裝并通過 ParserContext 注冊到 Ioc 容器中,但實(shí)際我們并沒有調(diào)用這兩個(gè)方法,而是通過 init() 方法注冊 BeanDefinitionParser 來完成解析節(jié)點(diǎn)以及注冊 Bean 的工作,所以對于 NamespaceHandler,我們主要關(guān)心 init 中注冊的兩個(gè) BeanDefinitionParser 即可。
3 編寫KiritoBeanDefinitionParser
在文章開始我們便標(biāo)記到 BeanDefinitionParser 是最為關(guān)鍵的一環(huán),每一個(gè) BeanDefinitionParser 實(shí)現(xiàn)類都負(fù)責(zé)一個(gè)映射,將一個(gè) XML 節(jié)點(diǎn)解析成 IOC 容器中的一個(gè)實(shí)體類。
public class KiritoBeanDefinitionParser implements BeanDefinitionParser { private final Class<?> beanClass; public KiritoBeanDefinitionParser(Class<?> beanClass) { this.beanClass = beanClass; } private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass) { RootBeanDefinition beanDefinition = new RootBeanDefinition(); beanDefinition.setBeanClass(beanClass); beanDefinition.setLazyInit(false); String name = element.getAttribute("name"); beanDefinition.getPropertyValues().addPropertyValue("name", name); parserContext.getRegistry().registerBeanDefinition(name, beanDefinition); return beanDefinition; } @Override public BeanDefinition parse(Element element, ParserContext parserContext) { return parse(element, parserContext, beanClass); } }
由于我們的實(shí)體類是非常簡單的,所以不存在很復(fù)雜的解析代碼,而實(shí)際項(xiàng)目中,往往需要大量的解析步驟。parse 方法會解析一個(gè)個(gè) XML 中的元素,使用 RootBeanDefinition 組裝成對象,并最終通過 parserContext 注冊到 IOC 容器中。
至此,我們便完成了 XML 文件中定義的對象到 IOC 容器的映射。
4 注冊schema和handler
最后一步還需要通知 Spring,告知其自定義 schema 的所在之處以及對應(yīng)的處理器。
resources/META-INF/spring.handlers
http\://www.cnkirito.moe/schema/kirito=moe.cnkirito.sample.xsd.KiritoNamespaceHandler
resources/META-INF/spring.schemas
http\://www.cnkirito.moe/schema/kirito/kirito.xsd=META-INF/kirito.xsd
沒有太多可以說的,需要遵守 Spring 的約定。
至此一個(gè)自定義的 XML schema 便擴(kuò)展完成了,隨后來驗(yàn)證一下。
驗(yàn)證擴(kuò)展
我們首先定義好 kirito.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:kirito="http://www.cnkirito.moe/schema/kirito" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.cnkirito.moe/schema/kirito http://www.cnkirito.moe/schema/kirito/kirito.xsd"> <kirito:application name="kirito-demo-application"/> <kirito:service name="kirito-demo-service"/> </beans>
使用 Spring 去加載它,并驗(yàn)證 IOC 容器中是否存在注冊成功的 Bean。
@SpringBootApplication @ImportResource(locations = {"classpath:kirito.xml"}) public class XmlSchemaAuthoringSampleApplication { public static void main(String[] args) { ConfigurableApplicationContext applicationContext = SpringApplication.run(XmlSchemaAuthoringSampleApplication.class, args); ServiceBean serviceBean = applicationContext.getBean(ServiceBean.class); System.out.println(serviceBean.getName()); ApplicationConfig applicationConfig = applicationContext.getBean(ApplicationConfig.class); System.out.println(applicationConfig.getName()); } }
觀察控制臺的輸出:
kirito-demo-service
kirito-demo-application
一個(gè)基礎(chǔ)的基于 XML schema 的擴(kuò)展便完成了。
Dubbo中的XML schema擴(kuò)展
最后我們以 Dubbo 為例,看看一個(gè)成熟的 XML schema 擴(kuò)展是如何被應(yīng)用的。
剛好對應(yīng)了四個(gè)標(biāo)準(zhǔn)的擴(kuò)展步驟,是不是對 XML 配置下的 Dubbo 應(yīng)用有了更好的理解了呢?
順帶一提,僅僅完成 Bean 的注冊還是不夠的,在“注冊”的同時(shí),Dubbo 還進(jìn)行了一系列其他操作如:暴露端口,開啟服務(wù)器,完成注冊中心的注冊,生成代理對象等等行為,由于不在本文的范圍內(nèi),后續(xù)的 Dubbo 專題會專門介紹這些細(xì)節(jié),本文便是了解 Dubbo 加載流程的前置文章了。
總結(jié):
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
- SpringBoot配置logback.xml 多環(huán)境的操作步驟
- spring*.xml配置文件明文加密的實(shí)現(xiàn)
- 使用maven開發(fā)springboot項(xiàng)目時(shí)pom.xml常用配置(推薦)
- SpringBoot整合Mybatis無法掃描xml文件的解決
- 解決Spring boot整合mybatis,xml資源文件放置及路徑配置問題
- 關(guān)于Spring自定義XML schema 擴(kuò)展的問題(Spring面試高頻題)
- 如何擴(kuò)展Spring Cache實(shí)現(xiàn)支持多級緩存
- Springboot啟動擴(kuò)展點(diǎn)超詳細(xì)教程小結(jié)
- Spring XML Schema擴(kuò)展機(jī)制的使用示例
相關(guān)文章
eclipse實(shí)現(xiàn)DSA數(shù)字簽名
這篇文章主要為大家詳細(xì)介紹了eclipse實(shí)現(xiàn)DSA數(shù)字簽名算法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-06-06JAVA StringBuffer類與StringTokenizer類代碼解析
這篇文章主要介紹了JAVA StringBuffer類與StringTokenizer類代碼解析,具有一定借鑒價(jià)值,需要的朋友可以參考下2018-01-01SpringMVC 跨重定向請求傳遞數(shù)據(jù)的方法實(shí)現(xiàn)
這篇文章主要介紹了SpringMVC 跨重定向請求傳遞數(shù)據(jù)的方法實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06Java中Spring Boot支付寶掃碼支付及支付回調(diào)的實(shí)現(xiàn)代碼
這篇文章主要介紹了Java中Spring Boot支付寶掃碼支付及支付回調(diào)的實(shí)現(xiàn)代碼,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-02-02IntelliJ IDEA 安裝 Grep Console插件 自定義控制臺輸出多顏色格式功能
由于Intellij idea不支持顯示ascii顏色,grep-console插件能很好的解決這個(gè)問題,下面就以開發(fā)JavaEE項(xiàng)目中,結(jié)合Log4j配置多顏色日志輸出功能,感興趣的朋友一起看看吧2020-05-05詳解利用SpringMVC攔截器控制Controller返回值
這篇文章主要介紹了詳解利用SpringMVC攔截器控制Controller返回值,通過定義一個(gè)StringResult注解,在訪問方法的時(shí)候返回StringResult中的內(nèi)容,有興趣的可以了解一下。2017-01-01response.setContentType()參數(shù)以及作用詳解
這篇文章主要介紹了response.setContentType()參數(shù)以及作用詳解,本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08