第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阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容