Spring 中的 XML schema 擴展機制

前言

好久沒有寫關於 Spring 的文章了,最近在系統梳理 Dubbo 代碼的過程當中發現了 XML schema 這個被遺漏的知識點。因爲工做中使用 SpringBoot 比較多的緣由,幾乎不多接觸 XML,此文能夠算作是亡羊補牢,另外一方面,也爲後續的 Dubbo 源碼解析作個鋪墊。php

XML schema 擴展機制是啥?這並非一塊很大的知識點,翻閱一下 Spring 的文檔,我甚至沒找到一個貫穿上下文的詞來描述這個功能,XML Schema Authoring 是文檔中對應的標題,簡單來講:spring

Spring 爲基於 XML 構建的應用提供了一種擴展機制,用於定義和配置 Bean。 它容許使用者編寫自定義的 XML bean 解析器,並將解析器自己以及最終定義的 Bean 集成到 Spring IOC 容器中。服務器

dubbo.xml

Dubbo 依賴了 Spring,並提供了一套自定義的 XML 標籤,<dubbo:application> ,<dubbo:registry> ,<dubbo:protocol>,<dubbo:service>。做爲使用者,大多數人只須要關心這些參數如何配置,但不知道有沒有人好奇過,它們是如何加載進入 Spring 的 IOC 容器中被其餘組件使用的呢?這便牽扯出了今天的主題:Spring 對 XML schema 的擴展支持。微信

自定義 XML 擴展

爲了搞懂 Spring 的 XML 擴展機制,最直接的方式即是實現一個自定義的擴展。實現的步驟也很是簡單,分爲四步:app

  1. 編寫一個 XML schema 文件描述的你節點元素。
  2. 編寫一個 NamespaceHandler 的實現類
  3. 編寫一個或者多個 BeanDefinitionParser 的實現 (關鍵步驟).
  4. 註冊上述的 schema 和 handler。

咱們的目的即是想要實現一個 kirito XML schema,咱們的項目中能夠自定義 kirito.xml,在其中會以 kirito 爲標籤來定義不一樣的類,並在最終的測試代碼中驗證這些聲明在 kirito.xml 的類是否被 Spring 成功加載。大概像這樣,是否是和 dubbo.xml 的格式很像呢?eclipse

kirito.xml

動手實現

有了明確的目標,咱們逐步開展本身的工做。ide

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 標籤的關鍵點。ui

② kirito.xsd 定義了兩個元素: application 和 service,出於簡單考慮,都只有一個 name 字段。this

schema 的意義在於它能夠和 eclipse/IDEA 這樣智能化的集成開發環境造成很好的搭配,在編輯 XML 的過程當中,用戶能夠得到告警和提示。 若是配置得當,可使用自動完成功能讓用戶在事先定義好的枚舉類型中進行選擇。

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 以後,還須要一個 NamespaceHandler 來幫助 Spring 解析 XML 中不一樣命名空間的各種元素。

<kirito:application name="kirito"/>
<dubbo:application name="dubbo"/>
<motan:application name="motan"/>
複製代碼

不一樣的命名空間須要不一樣的 NamespaceHandler 來處理,在今天的示例中,咱們使用 KiritoNamespaceHandler 來解析 kirito 命名空間。KiritoNamespaceHandler 繼承自 NamespaceHandlerSupport 類,並在其 init() 方法中註冊了兩個 BeanDefinitionParser ,用於解析 kirito 命名空間/kirito.xsd 約束中定義的兩個元素: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 命名空間處理器的抽象實現,我粗略看了NamespaceHandler 的幾個實現類,parse 和 decorate 方法能夠完成元素節點的組裝並經過 ParserContext 註冊到 Ioc 容器中,但實際咱們並無調用這兩個方法,而是經過 init() 方法註冊 BeanDefinitionParser 來完成解析節點以及註冊 Bean 的工做,因此對於 NamespaceHandler,咱們主要關心 init 中註冊的兩個 BeanDefinitionParser 便可。

3 編寫KiritoBeanDefinitionParser

在文章開始咱們便標記到 BeanDefinitionParser 是最爲關鍵的一環,每個 BeanDefinitionParser 實現類都負責一個映射,將一個 XML 節點解析成 IOC 容器中的一個實體類。

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);
    }

}
複製代碼

因爲咱們的實體類是很是簡單的,因此不存在很複雜的解析代碼,而實際項目中,每每須要大量的解析步驟。parse 方法會解析一個個 XML 中的元素,使用 RootBeanDefinition 組裝成對象,並最終經過 parserContext 註冊到 IOC 容器中。

至此,咱們便完成了 XML 文件中定義的對象到 IOC 容器的映射。

4 註冊schema和handler

最後一步還須要通知 Spring,告知其自定義 schema 的所在之處以及對應的處理器。

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 的約定。

至此一個自定義的 XML schema 便擴展完成了,隨後來驗證一下。

驗證擴展

咱們首先定義好 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 去加載它,並驗證 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

一個基礎的基於 XML schema 的擴展便完成了。

Dubbo中的XML schema擴展

最後咱們以 Dubbo 爲例,看看一個成熟的 XML schema 擴展是如何被應用的。

Dubbo中的應用

恰好對應了四個標準的擴展步驟,是否是對 XML 配置下的 Dubbo 應用有了更好的理解了呢?

順帶一提,僅僅完成 Bean 的註冊仍是不夠的,在「註冊」的同時,Dubbo 還進行了一系列其餘操做如:暴露端口,開啓服務器,完成註冊中心的註冊,生成代理對象等等行爲,因爲不在本文的範圍內,後續的 Dubbo 專題會專門介紹這些細節,本文即是瞭解 Dubbo 加載流程的前置文章了。

歡迎關注個人微信公衆號:「Kirito的技術分享」,關於文章的任何疑問都會獲得回覆,帶來更多 Java 相關的技術分享。

關注微信公衆號
相關文章
相關標籤/搜索