第6章 Dubbo 自定義標(biāo)簽的設(shè)計與實現(xiàn)

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

一、Spring 自定義標(biāo)簽的使用姿勢

步驟如下:

三個類

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

三個配置文件

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

示例

image.png

maven 依賴

<!-- 用于定義自定義標(biāo)簽 -->
<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 標(biāo)簽類 RpcConfig

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

標(biāo)簽解析器

/**
 * <rpc:xxx></rpc:xxx> 標(biāo)簽解析器
 */
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. 創(chuàng)建 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> 標(biāo)簽的解析器
        // 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>
    <!-- 標(biāo)簽元素 -->
    <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>
    <!-- 標(biāo)簽元素 -->
    <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 文件的位置標(biāo)志;
  • 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">
    <!-- 使用自定義標(biāo)簽 -->
    <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. 根據(jù) type 獲取 bean;也可以根據(jù) name 獲取 bean
        RpcConfig rpc = context.getBean(RpcConfig.class);
        // 3. 使用 bean
        System.out.println(rpc.getId() + " - " + rpc.getTimeout());
    }
}

二、Spring 自定義標(biāo)簽大致原理

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

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

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

三、Dubbo 自定義標(biāo)簽的設(shè)計與實現(xiàn)

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

image.png

xml 標(biāo)簽類 ServiceBean

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

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

標(biāo)簽解析器

/**
 * <dubbo:xxx></dubbo:xxx> 標(biāo)簽解析器
 */
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> 標(biāo)簽 *********/
        if (ProtocolConfig.class.equals(beanClass)) {
            ...
        /********* 處理 ServiceBean,即 <dubbo:service> 標(biāo)簽 *********/
        } else if (ServiceBean.class.equals(beanClass)) {
            ...
            // 設(shè)置 ref 屬性
            beanDefinition.getPropertyValues().addPropertyValue("ref", new BeanDefinitionHolder(classDefinition, id + "Impl"));
        /********* 處理 ProviderConfig,即 <dubbo:provider> 標(biāo)簽 *********/
        } 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>

    <!-- 標(biāo)簽元素 -->
    <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>

    <!-- 標(biāo)簽元素 -->
    <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 文件的位置標(biāo)志;
  • 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">
    ...
    <!-- 聲明暴露的服務(wù) -->
    <dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService"/>
</beans>

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容