第6章 Dubbo 自定義標簽的設計與實現

Dubbo 提供了 XML 和注解兩種方式來與 Spring 進行結合,目前最常用的還是 XML 方式,Dubbo 自定義了若干個 XML 標簽(eg. <dubbo:service>)。本文首先分析 “如何在 Spring 中自定義 XML 標簽”,然后分析 Dubbo 自定義標簽的設計與實現(基于 dubbo 2.6.7-SNAPSHOT)。

一、Spring 自定義標簽的使用姿勢

步驟如下:

三個類

  • 定義 xml 標簽類,eg. Dubbo ApplicationConfig
  • 編寫 xml 標簽解析類,eg. DubboBeanDefinitionParser
  • 編寫命名空間處理類,eg. DubboNamespaceHandler,用于注冊標簽解析器

三個配置文件

  • 編寫 xsd 文件,eg. META-INF/dubbo.xsd
  • 指定 xsd 文件的位置,eg. META-INF/spring.schemas,注意:該文件的文件名和文件路徑是固定的
  • 指定命名空間(key)與命名空間處理器(value)的對應關系,eg. META-INF/spring.handlers,注意:該文件的文件名和文件路徑是固定的

示例

image.png

maven 依賴

<!-- 用于定義自定義標簽 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
    <version>5.1.8.RELEASE</version>
</dependency>
<!-- 用于提供上下文(這里用于測試) -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.1.8.RELEASE</version>
</dependency>

xml 標簽類 RpcConfig

/**
 * xml 標簽類
 */
public class RpcConfig {
    private String id;
    private int timeout;
    // getter and setter
    ...
}

標簽解析器

/**
 * <rpc:xxx></rpc:xxx> 標簽解析器
 */
public class RpcBeanDefinitionParser implements BeanDefinitionParser {
    /**
     * eg. RpcConfig
     */
    private Class<?> beanClass;

    public RpcBeanDefinitionParser(Class<?> aClass) {
        this.beanClass = aClass;
    }

    @Override
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        // 1. 創建 BeanDefinition
        RootBeanDefinition rootBD = new RootBeanDefinition();
        rootBD.setBeanClass(beanClass);
        rootBD.getPropertyValues().add("id", element.getAttribute("id"));
        rootBD.getPropertyValues().add("timeout", element.getAttribute("timeout"));
        // 2. 注冊 BeanDefinition 到 BeanDefinitionRegistry 中
        BeanDefinitionRegistry registry = parserContext.getRegistry();
        registry.registerBeanDefinition(beanClass.getName(), rootBD);
        return rootBD;
    }
}

命名空間處理器

/**
 * 命名空間處理類
 */
public class RpcNamespaceHandler extends NamespaceHandlerSupport {
    @Override
    public void init() {
        // 注冊 <rpc:provider> 標簽的解析器
        // key:elementName - eg.provider,value:解析器
        registerBeanDefinitionParser("provider", new RpcBeanDefinitionParser(RpcConfig.class));
    }
}

META-INF/rpc.xsd

<xsd:schema
        xmlns="http://io.study/schema"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        targetNamespace="http://io.study/schema">
    <!-- 歸類屬性 -->
    <xsd:complexType name="providerType">
        <xsd:attribute name="id" type="xsd:string">
            <!-- 注釋 -->
            <xsd:annotation>
                <xsd:documentation><![CDATA[ The provider id. ]]></xsd:documentation>
            </xsd:annotation>
        </xsd:attribute>
        <xsd:attribute name="timeout" type="xsd:int">
            <xsd:annotation>
                <xsd:documentation><![CDATA[ The provider timeout. ]]></xsd:documentation>
            </xsd:annotation>
        </xsd:attribute>
    </xsd:complexType>
    <!-- 標簽元素 -->
    <xsd:element name="provider" type="providerType">
        <xsd:annotation>
            <xsd:documentation><![CDATA[ provider 元素的的文檔說明 ]]></xsd:documentation>
        </xsd:annotation>
    </xsd:element>
</xsd:schema>

可去掉注釋簡化如下:

<xsd:schema
        xmlns="http://io.study/schema"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        targetNamespace="http://io.study/schema">
    <!-- 歸類屬性 -->
    <xsd:complexType name="providerType">
        <xsd:attribute name="id" type="xsd:string"/>
        <xsd:attribute name="timeout" type="xsd:int"/>
    </xsd:complexType>
    <!-- 標簽元素 -->
    <xsd:element name="provider" type="providerType"/>
</xsd:schema>

META-INF/spring.schemas

http\://io.study/schema/rpc.xsd=META-INF/rpc.xsd
  • key:在 spring 的 xml 配置文件中 schemaLocation 指定的 *.xsd 文件的位置標志;
  • value:*.xsd 文件的在類路徑下的位置。

META-INF/spring.handlers

http\://io.study/schema=io.study.xmltag.RpcNamespaceHandler
  • key:在 *.xsd 和 spring 的 xml 配置文件中指定的命名空間;
  • value:命令空間處理器。

測試

spring 配置文件 test/rpc.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:rpc="http://io.study/schema"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://io.study/schema http://io.study/schema/rpc.xsd">
    <!-- 使用自定義標簽 -->
    <rpc:provider id="helloworld" timeout="60"/>
</beans>

測試主類

public class MainTest {
    public static void main(String[] args) {
        // 1. 加載配置文件 rpc.xml
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("test/rpc.xml");
        // 2. 根據 type 獲取 bean;也可以根據 name 獲取 bean
        RpcConfig rpc = context.getBean(RpcConfig.class);
        // 3. 使用 bean
        System.out.println(rpc.getId() + " - " + rpc.getTimeout());
    }
}

二、Spring 自定義標簽大致原理

BeanDefinitionParserDelegate # BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd)
// 1. 獲取命名空間 namespaceUri=http://io.study/schema
-- String namespaceUri = getNamespaceURI(ele) 
// 2. 根據命令空間獲取處理器,handler=RpcNamespaceHandler
-- NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri) 
  -- namespaceHandler.init() // 執行解析器的注冊操作
// 3. 使用命令空間處理器進行解析
-- handler.parse(ele, new ParserContext(...))
  -- NamespaceHandlerSupport # BeanDefinition parse(Element element, ParserContext parserContext)
    // 獲取對應 xml 元素的解析器
    -- BeanDefinitionParser parser = findParserForElement(element, parserContext);
    // 進行解析
    -- parser.parse(element, parserContext)

spring 在加載過程中,遇到自定義標簽時會去 spring.schemas 中尋找 *.xsd,會去 spring.handlers 中尋找命名空間處理器 XxxNamespaceHandler 完成自定義標簽的解析器的注冊,然后獲取對應標簽的解析器,進行解析操作。

詳細的源碼分析,見 《spring 源碼深度解析》第四章。

三、Dubbo 自定義標簽的設計與實現

類比上述我們實現的 <rpc.xxx> 標簽,Dubbo 也是用相同的機制實現了一套 <dubbo:xxx> 標簽,eg. <dubbo:service>,我們以 <dubbo:service> 為例,來看下 Dubbo 自定義標簽的實現機制。

image.png

xml 標簽類 ServiceBean

public class ServiceBean<T> extends ServiceConfig<T> ... {
    ...
}

public class ServiceConfig<T> extends AbstractServiceConfig {
    // reference to interface impl
    private T ref;
    ...
}

標簽解析器

/**
 * <dubbo:xxx></dubbo:xxx> 標簽解析器
 */
public class DubboBeanDefinitionParser implements BeanDefinitionParser {
    /**
     * eg. ServiceBean
     */
    private final Class<?> beanClass;
    ...

    public DubboBeanDefinitionParser(Class<?> beanClass, boolean required) {
        this.beanClass = beanClass;
        ...
    }

    @Override
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        return parse(element, parserContext, beanClass, required);
    }

    @SuppressWarnings("unchecked")
    private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) {
        RootBeanDefinition beanDefinition = new RootBeanDefinition();
        beanDefinition.setBeanClass(beanClass);
        beanDefinition.setLazyInit(false);
        /********* 處理 id 屬性 *********/
        String id = element.getAttribute("id");
        ...
        // 注冊 BeanDefinition 到 BeanDefinitionRegistry 中
        parserContext.getRegistry().registerBeanDefinition(id, beanDefinition);
        // 添加 id 屬性
        beanDefinition.getPropertyValues().addPropertyValue("id", id);

        /********* 處理 ProtocolConfig,即 <dubbo:protocol> 標簽 *********/
        if (ProtocolConfig.class.equals(beanClass)) {
            ...
        /********* 處理 ServiceBean,即 <dubbo:service> 標簽 *********/
        } else if (ServiceBean.class.equals(beanClass)) {
            ...
            // 設置 ref 屬性
            beanDefinition.getPropertyValues().addPropertyValue("ref", new BeanDefinitionHolder(classDefinition, id + "Impl"));
        /********* 處理 ProviderConfig,即 <dubbo:provider> 標簽 *********/
        } else if (ProviderConfig.class.equals(beanClass)) {
            ...
        }
        ...
    }
}

命名空間處理器

/**
 * 命名空間處理類
 */
public class DubboNamespaceHandler extends NamespaceHandlerSupport {
    @Override
    public void init() {
        ...
        registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
        registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
        ...
    }
}

META-INF/dubbo.xsd

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            ...
            xmlns="http://dubbo.apache.org/schema/dubbo"
            targetNamespace="http://dubbo.apache.org/schema/dubbo">
    <!-- 歸類屬性 abstractServiceType -->
    <xsd:complexType name="abstractServiceType">
        <xsd:complexContent>
            <xsd:extension base="abstractInterfaceType">
                ...
                <xsd:attribute name="group" type="xsd:string" use="optional">
                    <xsd:annotation>
                        <xsd:documentation><![CDATA[ The service group. ]]></xsd:documentation>
                    </xsd:annotation>
                </xsd:attribute>
                ...
            </xsd:extension>
        </xsd:complexContent>
    </xsd:complexType>

    <!-- 歸類屬性 serviceType -->
    <xsd:complexType name="serviceType">
        <xsd:complexContent>
            <!-- 繼承 abstractServiceType  -->
            <xsd:extension base="abstractServiceType">
                ...
                <!--  <dubbo:service ref='xxx'/>  -->
                <xsd:attribute name="ref" type="xsd:string" use="optional">
                    <xsd:annotation>
                        <xsd:documentation>
                            <![CDATA[ The service implementation instance bean id. ]]></xsd:documentation>
                    </xsd:annotation>
                </xsd:attribute>
                ...
            </xsd:extension>
        </xsd:complexContent>
    </xsd:complexType>

    <!-- 標簽元素 -->
    <xsd:element name="service" type="serviceType">
        <xsd:annotation>
            <xsd:documentation><![CDATA[ Export service config ]]></xsd:documentation>
        </xsd:annotation>
    </xsd:element>
</xsd:schema>

可去掉注釋簡化如下:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            ...
            xmlns="http://dubbo.apache.org/schema/dubbo"
            targetNamespace="http://dubbo.apache.org/schema/dubbo">
    <!-- 歸類屬性 abstractServiceType -->
    <xsd:complexType name="abstractServiceType">
        <xsd:complexContent>
            <xsd:extension base="abstractInterfaceType">
                ...
                <xsd:attribute name="group" type="xsd:string" use="optional"/>
                ...
            </xsd:extension>
        </xsd:complexContent>
    </xsd:complexType>

    <!-- 歸類屬性 serviceType -->
    <xsd:complexType name="serviceType">
        <xsd:complexContent>
            <!-- 繼承 abstractServiceType  -->
            <xsd:extension base="abstractServiceType">
                ...
                <!--  <dubbo:service ref='xxx'/>  -->
                <xsd:attribute name="ref" type="xsd:string" use="optional"/>
                ...
            </xsd:extension>
        </xsd:complexContent>
    </xsd:complexType>

    <!-- 標簽元素 -->
    <xsd:element name="service" type="serviceType"/>
</xsd:schema>

META-INF/spring.schemas

http\://dubbo.apache.org/schema/dubbo/dubbo.xsd=META-INF/dubbo.xsd
  • key:在 spring 的 xml 配置文件中 schemaLocation 指定的 *.xsd 文件的位置標志;
  • value:*.xsd 文件的在類路徑下的位置。

META-INF/spring.handlers

http\://dubbo.apache.org/schema/dubbo=com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler
  • key:在 *.xsd 和 spring 的 xml 配置文件中指定的命名空間;
  • value:命令空間處理器。

測試使用

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
       http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
    ...
    <!-- 聲明暴露的服務 -->
    <dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService"/>
</beans>

完整示例:第1章 第一個 Dubbo 項目

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,501評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,673評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,610評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,939評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,668評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,004評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,001評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,173評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,705評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,426評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,656評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,139評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,833評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,247評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,580評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,371評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,621評論 2 380

推薦閱讀更多精彩內容