Dubbo源码-Dubbo是如何随心所欲自定义XML标签的

叨叨

今天考虑了很久要不要写这篇文章。

距离《Dubbo源码》系列的开篇到现在已经快两个月时间了。当时是想着工作上的RPC框架使用存在一些让人头疼的问题,就来看看Dubbo给出了一套什么样的解决方案。

结果,写完第一篇没几天,工作上因为要赶一个项目的进度,关小黑屋了,前段时间刚放出来-_-!

琢磨着,做事不能半途而废。今天就又打开了Dubbo项目,pull下代码,在十多个子模块之间来回滚动,感觉都不是好惹的,一时不知道从哪下手了。再一想,Dubbo源码系列不能就这么唐突的出一篇就结束了啊。

行,思来想去,还是接着从上篇提到的dubbo-demo模块继续往下说……

正文

下面是dubbo-demo-provider模块下的dubbo-demo-provider.xml配置文件内容


<pre style="margin: 0.5em 0px; padding: 0.4em 0.6em; border-radius: 8px; background: rgb(255, 255, 255); color: rgb(0, 0, 0); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; font-family: Menlo; font-size: 9pt;"><?xml version="1.0" encoding="UTF-8"?> <!--
 Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.  You may obtain a copy of the License at   http://www.apache.org/licenses/LICENSE-2.0   Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> <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">

    <!-- provider's application name, used for tracing dependency relationship -->
 <dubbo:application name="demo-provider"/>

    <!-- use zookeeper registry center to export service -->
 <dubbo:registry address="zookeeper://127.0.0.1:2181"/>

    <!-- use dubbo protocol to export service on port 20880 -->
 <dubbo:protocol name="dubbo" port="20880"/>

    <!-- service implementation, as same as regular local bean -->
 <bean id="demoService" class="com.alibaba.dubbo.demo.provider.DemoServiceImpl"/>

    <!-- declare the service interface to be exported -->
 <dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService"/>

</beans></pre>

注意:这里的dubbo:registry在上篇已经说明,有过改动,这里使用的是zookeeper的配置

dubbo-demo-provider与常见的xml文件有何不同

1、除了<bean id="demoService" ... 其他的标签我们日常都没有使用过

2、<beans>标签中除了常见的“http://www.springframework.org/schema/beans/spring-beans-4.3.xsd”还多了“http://dubbo.apache.org/schema/dubbo/dubbo.xsd

我们平常使用的xml都是在Spring框架下,所以可以看到熟悉的<beans>、<bean> 、<import>等。那有没有想过,为什么定义一个<bean>标签就是生命一个bean,就能够在Spring上下文注册一个类的实例呢?其实,这些工作Spring在幕后都帮我们做好了,这个我在之前的《Spring读书笔记》系列有着重写过。

稍稍扫一眼Dubbo的代码,就会发现,Dubbo也是基于Spring开发的,使用了Spring的很多特性,但是鉴于自己的业务框架需求,需要做相应的拓展和定制化,实现一套自己的自定义XML标签。那么这些标签又是如何生效和被使用的呢

基于Spring的Schema提供自定义配置支持

在dubbo-demo-provider.xml中见到的那些标签也是基于Spring的Schema实现的一套自定义标签。

这一套流程主要包括以下几个步骤:

  • 编写配置类和属性

  • 编写XSD文件

  • 编写spring.handlers和spring.schemas

  • 编写DubboNamespaceHandler和DubboBeanDefinitionParser,主要负责标签解析

编写配置类和属性

针对dubbo-demo-provider中的<dubbo:application name="demo-provider"/>来说,该标签对应的配置类在dubbo-config-api模块下的ApplicationConfig。


<pre style="margin: 0.5em 0px; padding: 0.4em 0.6em; border-radius: 8px; background: rgb(255, 255, 255); color: rgb(0, 0, 0); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; font-family: Menlo; font-size: 9pt;">package com.alibaba.dubbo.config;

import com.alibaba.dubbo.common.Constants;
import com.alibaba.dubbo.common.compiler.support.AdaptiveCompiler;
import com.alibaba.dubbo.common.logger.LoggerFactory;
import com.alibaba.dubbo.config.support.Parameter;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * ApplicationConfig * * @export
  */ public class ApplicationConfig extends AbstractConfig {

    private static final long serialVersionUID = 5508512956753757169L;

    // application name
 private String name;

    // module version
 private String version;

    // application owner
 private String owner;

    // application's organization (BU)
 private String organization;

    // architecture layer
 private String architecture;

    // environment, e.g. dev, test or production
 private String environment;

    // Java compiler
 private String compiler;

    // logger
 private String logger;

    // registry centers
 private List<RegistryConfig> registries;

    // monitor center
 private MonitorConfig monitor;

    // is default or not
 private Boolean isDefault;

    // directory for saving thread dump
 private String dumpDirectory;

    private Boolean qosEnable;

    private Integer qosPort;

    private Boolean qosAcceptForeignIp;

    // customized parameters
 private Map<String, String> parameters;

    public ApplicationConfig() {
    }

    public ApplicationConfig(String name) {
        setName(name);
    }

    @Parameter(key = Constants.APPLICATION_KEY, required = true)
    public String getName() {
        return name;
    }

    public void setName(String name) {
        checkName("name", name);
        this.name = name;
        if (id == null || id.length() == 0) {
            id = name;
        }
    }

    @Parameter(key = "application.version")
    public String getVersion() {
        return version;
    }

    public void setVersion(String version) {
        this.version = version;
    }

    public String getOwner() {
        return owner;
    }

    public void setOwner(String owner) {
        checkMultiName("owner", owner);
        this.owner = owner;
    }

    public String getOrganization() {
        return organization;
    }

    public void setOrganization(String organization) {
        checkName("organization", organization);
        this.organization = organization;
    }

    public String getArchitecture() {
        return architecture;
    }

    public void setArchitecture(String architecture) {
        checkName("architecture", architecture);
        this.architecture = architecture;
    }

    public String getEnvironment() {
        return environment;
    }

    public void setEnvironment(String environment) {
        checkName("environment", environment);
        if (environment != null) {
            if (!("develop".equals(environment) || "test".equals(environment) || "product".equals(environment))) {
                throw new IllegalStateException("Unsupported environment: " + environment + ", only support develop/test/product, default is product.");
            }
        }
        this.environment = environment;
    }

    public RegistryConfig getRegistry() {
        return registries == null || registries.isEmpty() ? null : registries.get(0);
    }

    public void setRegistry(RegistryConfig registry) {
        List<RegistryConfig> registries = new ArrayList<RegistryConfig>(1);
        registries.add(registry);
        this.registries = registries;
    }

    public List<RegistryConfig> getRegistries() {
        return registries;
    }

    @SuppressWarnings({"unchecked"})
    public void setRegistries(List<? extends RegistryConfig> registries) {
        this.registries = (List<RegistryConfig>) registries;
    }

    public MonitorConfig getMonitor() {
        return monitor;
    }

    public void setMonitor(MonitorConfig monitor) {
        this.monitor = monitor;
    }

    public void setMonitor(String monitor) {
        this.monitor = new MonitorConfig(monitor);
    }

    public String getCompiler() {
        return compiler;
    }

    public void setCompiler(String compiler) {
        this.compiler = compiler;
        AdaptiveCompiler.setDefaultCompiler(compiler);
    }

    public String getLogger() {
        return logger;
    }

    public void setLogger(String logger) {
        this.logger = logger;
        LoggerFactory.setLoggerAdapter(logger);
    }

    public Boolean isDefault() {
        return isDefault;
    }

    public void setDefault(Boolean isDefault) {
        this.isDefault = isDefault;
    }

    @Parameter(key = Constants.DUMP_DIRECTORY)
    public String getDumpDirectory() {
        return dumpDirectory;
    }

    public void setDumpDirectory(String dumpDirectory) {
        this.dumpDirectory = dumpDirectory;
    }

    @Parameter(key = Constants.QOS_ENABLE)
    public Boolean getQosEnable() {
        return qosEnable;
    }

    public void setQosEnable(Boolean qosEnable) {
        this.qosEnable = qosEnable;
    }

    @Parameter(key = Constants.QOS_PORT)
    public Integer getQosPort() {
        return qosPort;
    }

    public void setQosPort(Integer qosPort) {
        this.qosPort = qosPort;
    }

    @Parameter(key = Constants.ACCEPT_FOREIGN_IP)
    public Boolean getQosAcceptForeignIp() {
        return qosAcceptForeignIp;
    }

    public void setQosAcceptForeignIp(Boolean qosAcceptForeignIp) {
        this.qosAcceptForeignIp = qosAcceptForeignIp;
    }

    public Map<String, String> getParameters() {
        return parameters;
    }

    public void setParameters(Map<String, String> parameters) {
        checkParameterName(parameters);
        this.parameters = parameters;
    }
}</pre>

在ApplicationConfig同级目录下,还包括其他出现在dubbo-demo-provider.xml中出现自定义标签类,如RegistryConfig、ProtocolConfig

image

注意:<dubbo:application name="demo-provider"/>标签中的dubbo对应的声明在dubbo-demo-provider.xml中的xmlns:dubbo="http://dubbo.apache.org/schema/dubbo,这里的xmlns其实就是一个命名空间的概念。

编写XSD文件

XSD文件已经在dubbo-demo-provider文件中定义好了,dubbo.xsd在dubbo-config-spring模块下,内容较长,举Application为例


...

<pre style="margin: 0.5em 0px; padding: 0.4em 0.6em; border-radius: 8px; background: rgb(255, 255, 255); color: rgb(0, 0, 0); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; font-family: Menlo; font-size: 9pt;"><xsd:element name="application" type="applicationType">
    <xsd:annotation>
        <xsd:documentation><![CDATA[ The application config ]]></xsd:documentation>
    </xsd:annotation>
</xsd:element></pre>

...

<pre style="margin: 0.5em 0px; padding: 0.4em 0.6em; border-radius: 8px; background: rgb(255, 255, 255); color: rgb(0, 0, 0); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; font-family: Menlo; font-size: 9pt;"><xsd:complexType name="applicationType">
    <xsd:sequence minOccurs="0" maxOccurs="unbounded">
        <xsd:element ref="parameter" minOccurs="0" maxOccurs="unbounded"/>
    </xsd:sequence>
    <xsd:attribute name="id" type="xsd:ID">
        <xsd:annotation>
            <xsd:documentation><![CDATA[ The unique identifier for a bean. ]]></xsd:documentation>
        </xsd:annotation>
    </xsd:attribute>
    <xsd:attribute name="name" type="xsd:string" use="required">
        <xsd:annotation>
            <xsd:documentation><![CDATA[ The application name. ]]></xsd:documentation>
        </xsd:annotation>
    </xsd:attribute>
    <xsd:attribute name="version" type="xsd:string">
        <xsd:annotation>
            <xsd:documentation><![CDATA[ The application version. ]]></xsd:documentation>
        </xsd:annotation>
    </xsd:attribute>
    <xsd:attribute name="owner" type="xsd:string">
        <xsd:annotation>
            <xsd:documentation><![CDATA[ The application owner name (email prefix). ]]></xsd:documentation>
        </xsd:annotation>
    </xsd:attribute>
    <xsd:attribute name="organization" type="xsd:string">
        <xsd:annotation>
            <xsd:documentation><![CDATA[ The organization name. ]]></xsd:documentation>
        </xsd:annotation>
    </xsd:attribute>
    <xsd:attribute name="architecture" type="xsd:string">
        <xsd:annotation>
            <xsd:documentation><![CDATA[ The architecture. ]]></xsd:documentation>
        </xsd:annotation>
    </xsd:attribute>
    <xsd:attribute name="environment" type="xsd:string">
        <xsd:annotation>
            <xsd:documentation><![CDATA[ The application environment, eg: dev/test/run ]]></xsd:documentation>
        </xsd:annotation>
    </xsd:attribute>
    <xsd:attribute name="compiler" type="xsd:string">
        <xsd:annotation>
            <xsd:documentation><![CDATA[ The java code compiler. ]]></xsd:documentation>
        </xsd:annotation>
    </xsd:attribute>
    <xsd:attribute name="logger" type="xsd:string">
        <xsd:annotation>
            <xsd:documentation><![CDATA[ The application logger. ]]></xsd:documentation>
        </xsd:annotation>
    </xsd:attribute>
    <xsd:attribute name="registry" type="xsd:string" use="optional">
        <xsd:annotation>
            <xsd:documentation><![CDATA[ The application registry. ]]></xsd:documentation>
        </xsd:annotation>
    </xsd:attribute>
    <xsd:attribute name="monitor" type="xsd:string" use="optional">
        <xsd:annotation>
            <xsd:documentation><![CDATA[ The application monitor. ]]></xsd:documentation>
        </xsd:annotation>
    </xsd:attribute>
    <xsd:attribute name="default" type="xsd:string" use="optional">
        <xsd:annotation>
            <xsd:documentation><![CDATA[ Is default. ]]></xsd:documentation>
        </xsd:annotation>
    </xsd:attribute>
</xsd:complexType></pre>

...

代码中上面一部分代码<xsd:element name="application" type="applicationType">表示标签的名称

<xsd:attribute name="name" type="xsd:string" use="required">表示的是application标签的属性,与ApplicationConfig类的属性是一一对应的关系。这里表示有一个属性名为name,且是String类型,必填字段。

编写spring.handlers和spring.schemas

仅仅有上面的配置类和XSD文件还是无法让自定义标签工作,因为Spring还无法发现这些自定义标签,更别提让其发挥该有的作用了。

这时候,需要添加两个配置文件spring.handlers和spring.schemas,从文件字面意思就可以知道,这两个配置文件起到了贯通的作用。spring.handlers用于配置具体的解析类,下面会提到,spring.schemas用于指明schemas的文件路径。

我们可以在dubbo-config-spring模块中看到这两个配置文件

image

Spring会在启动容器的时候加载META-INF目录下的这两个配置文件并加载对应的解析类。

编写DubboNamespaceHandler和DubboBeanDefinitionParser,主要负责标签解析

DubboNamespaceHandler类位于dubbo-config-spring模块下


<pre style="margin: 0.5em 0px; padding: 0.4em 0.6em; border-radius: 8px; background: rgb(255, 255, 255); color: rgb(0, 0, 0); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; font-family: Menlo; font-size: 9pt;">/*
 * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements.  See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License.  You may obtain a copy of the License at * *     http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.dubbo.config.spring.schema;

import com.alibaba.dubbo.common.Version;
import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.ConsumerConfig;
import com.alibaba.dubbo.config.ModuleConfig;
import com.alibaba.dubbo.config.MonitorConfig;
import com.alibaba.dubbo.config.ProtocolConfig;
import com.alibaba.dubbo.config.ProviderConfig;
import com.alibaba.dubbo.config.RegistryConfig;
import com.alibaba.dubbo.config.spring.ReferenceBean;
import com.alibaba.dubbo.config.spring.ServiceBean;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

/**
 * DubboNamespaceHandler * * @export
  */ public class DubboNamespaceHandler extends NamespaceHandlerSupport {

    static {
        Version.checkDuplicate(DubboNamespaceHandler.class);
    }

    @Override
 public void init() {
        registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
        registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
        registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
        registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
        registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
        registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
        registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
        registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
        registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
        registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser());
    }

}
</pre>

是不是又看到了之前在dubbo-demo-provider.xml配置文件中看到那些标签。没错,真正给那些标签赋能的功能代码就在这里。

具体的解析工作交给了dubbo-config-spring模块下的DubboBeanDefinitionParser类,该类实现了Spring的BeanDefinitionParser接口,该类的一个核心方法就是parse()方法,其抽丝剥茧解析标签,加载bean的思路其实和之前在《Spring读书笔记》系列中介绍的一样。最终都是解析并转化为BeanDefinition对象并塞到Spring的上下文中,完成Bean的加载。

我们可以以debug模式启动dubbo-demo-provider模块中的Provider类,通过打断点,会发现首先会执行DubboNamespaceHandler类中的init方法,然后进入DubboBeanDefinitionParser类中的parse方法。

配合dubbo-demo-provider.xml配置文件中的<dubbo:registry address="zookeeper://127.0.0.1:2181"/>,我们在调试的时候发现解析后对应的BeanDefinition如下

image

通过这样一个过程,就实现了将XML自定义的标签加载到Spring容器中,而不需要使用Spring自己的bean去定义。

明白了这个流程,后面看Dubbo的其他配置文件里面那些陌生的标签就不会蒙圈了。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,333评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,812评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,016评论 0 246
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,293评论 0 214
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,650评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,788评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,003评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,741评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,462评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,681评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,168评论 1 262
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,528评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,169评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,119评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,902评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,846评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,719评论 2 274

推荐阅读更多精彩内容