美文网首页
第6章 Dubbo 自定义标签的设计与实现

第6章 Dubbo 自定义标签的设计与实现

作者: 原水寒 | 来源:发表于2019-07-22 11:42 被阅读0次

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 项目

相关文章

网友评论

      本文标题:第6章 Dubbo 自定义标签的设计与实现

      本文链接:https://www.haomeiwen.com/subject/fivfkctx.html