緣起 Dubbo ,講講 Spring XML Schema 擴展機制

背景

在 Dubbo 中,可以使用 XML 配置相關信息,也可以用來引入服務或者導出服務。配置完成,啓動工程,Spring 會讀取配置文件,生成注入 相關 Bean。那 Dubbo 如何實現自定義 XML 被 Spring 加載讀取?

Spring XML Schema 擴展機制。從 Spring 2.0 開始,Spring 開始提供了一種基於 XML Schema 格式擴展機制,用於定義和配置 bean。

Spring XML Schema 擴展機制

實現 Spring XML Schema 擴展,其實非常簡單,只需要完成下面四步。

  1. 創建 XML Schema 文件,由於該文件後綴名爲 xsd,下面稱爲 XSD 文件。
  2. 編寫實現一個或多個 BeanDefinitionParser
  3. 編寫NamespaceHandler實現類。
  4. 註冊 NamespaceHandler 以及 XSD 文件。

我們按照以上步驟,最終完整 Spring 解析如下配置。

<?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:demo="http://www.test.com/demo"
       xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd

        http://www.test.com/demo http://www.test.com/demo/demo.xsd">

    <demo:application name="test" id="test"/>
</beans>

創建 XSD 文件

XSD 文件,主要用來定義 XML 格式,用來驗證 XML 合法性。在 IDE 中,導入 XSD 文件,編輯 XML 文件可以獲得相關提示。

下面我們生成一個 XSD 文件。

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.test.com/demo"
            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            xmlns:beans="http://www.springframework.org/schema/beans"
            targetNamespace="http://www.test.com/demo"
            elementFormDefault="qualified"
            attributeFormDefault="unqualified">

    <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:schema>

上面 XSD 文件中 http://www.test.com/demo 爲自定義命名空間地址,下面將會使用到。

實現 BeanDefinitionParser

這裏實現 BeanDefinitionParser,真正解析 XML 動作在這裏完成。

由於上面的例子比較簡單,我們可以直接繼承 Spring 提供的抽象類 AbstractSingleBeanDefinitionParser,然後實現相關方法就可以了。

public class DemoBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {

    /**

     * 返回最會需要注入 Spring Bean 的類型
     * @param element
     * @return
     */
    @Override
    protected Class<?> getBeanClass(Element element) {
        return DemoApplication.class;
    }

    /***
     * 這個方法完成真正解析動作
     * @param element
     * @param builder
     */
    @Override
    protected void doParse(Element element, BeanDefinitionBuilder builder) {
        String name=element.getAttribute("name");
        builder.addPropertyValue("name",name);
    }
}

當然也可以直接實現 BeanDefinitionParser,這樣更加靈活,但是這樣相比於上面這個就比較複雜了。

public class BeanApplicationDefinitionParser implements BeanDefinitionParser {

    @Override
    public BeanDefinition parse(Element element, ParserContext parserContext) {

        String name=element.getAttribute("name");
        // Bean 定義,最後根據這個生產 Bean
        RootBeanDefinition rootBeanDefinition=new RootBeanDefinition();
        rootBeanDefinition.setBeanClass(DemoApplication.class);
        rootBeanDefinition.setLazyInit(false);
        // 添加解析的屬性
        rootBeanDefinition.getPropertyValues().add("name",name);
        // 將生成的 BeanDefinition 註冊,少了這一步將會導致最後生成 Bean 時報錯
        parserContext.getRegistry().registerBeanDefinition("application",rootBeanDefinition);
        return rootBeanDefinition;
    }
}

實現 NamespaceHandler

這一步實現 NamespaceHandler,開發者自定義 NamespaceHandler 只要繼承 NamespaceHandlerSupport 抽象類,實現 init 方法。在這個方法中註冊上面一步實現 BeanDefinitionParser

public class DemoNameSpaceHandler extends NamespaceHandlerSupport {

    @Override

    public void init() {

        // elementName 爲命名空間
        registerBeanDefinitionParser("application",new BeanApplicationDefinitionParser());
    }
}

註冊 XSD 以及 NamespaceHandler

這一步我們需要在 META-INF 中生成兩個配置文件,分別爲 spring.handlersspring.schemas

spring.schemas 指定 XSD 文件路徑。

http\://www.test.com/demo/demo.xsd=com/spring/learning/xml/schemas/autoring/leanrn/demo.xsd

spring.handlers 指定 NamespaceHandler 完整類名,既包含前面的包名。

這裏需要注意的是 需要進行轉義

測試運行

首先我們生產 Spring 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:demo="http://www.test.com/demo"
       xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd

        http://www.test.com/demo http://www.test.com/demo/demo.xsd">

    <demo:application name="test" id="test"/>
</beans>

這裏需要注意需要使用 XSD 文件中定義 http://www.test.com/demo

接着我們使用 SpringBoot ,導入 XML 文件,然後運行。

@SpringBootApplication
@ImportResource(locations = {"classpath:applicationContext.xml"})
public class XmlSchemaApplication {


    public static void main(String[] args) {
        ConfigurableApplicationContext applicationContext = SpringApplication.run(XmlSchemaApplication.class, args);
        DemoApplication demoApplication=applicationContext.getBean(DemoApplication.class);
        System.out.println("application name is "+demoApplication.getName());
    }
}

輸出結果爲:

output
application name is test

Spring XML 擴展機制源碼研究

這裏我們主要研究自定義 XML 擴展文件如何被 Spring 加載。

Spring 啓動過程中會通過 BeanDefinitionDocumentReader 讀取 beans 標籤裏面所有配置,這個過程將會通過 BeanDefinitionParserDelegate#parseCustomElement 解析自定義元素。

carbon34.png

上面解析過程可以獲得自定義 NamespaceHandler,然後調用 parse 方法解析。

接着我們查看 NamespaceHandlerResolver#resolve 方法,查看如何獲取自定義 NamespaceHandler

carbon35.png

在這個方法中,主要是從 handlerMappings 緩存中獲取 NamespaceHandler。而該緩存來源於 getHandlerMappings 方法,這個方法將會加載我們上面自定義 spring.handlers 文件。

carbon36.png

看完 Spring 加載 NamespaceHandler 過程,下面我們查看最重要 BeanDefinition 如何生成。

上面已經講到 Spring 會使用 NamespaceHandler.parse 解析,由於我們繼承了 NamespaceHandlerSupport,查看裏面具體實現。

carbon37.png

carbon38.png

獲取到 BeanDefinition 會將其註冊到容器中,然後會通過 BeanDefinition 生成 Bean。這個生成過程不屬於本章節內容,所以不再概述,感興趣同學可以自行搜索。

Dubbo XML Schema 擴展實現

最後我們查看 Dubbo XML Schema 擴展如何實現。

image.png

image.png

可以看到 Dubbo XML Schema 擴展剛好對應 Spring 四個標準的步驟。

總結

最後用一張圖片總結全文內容。

SpringXML擴展.png

幫助文檔

xsd-custom-registration
Spring中的XML schema擴展機制

其他平臺.png

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章