Dubbo源码阅读——dubbo配置解析原理

一、Dubbo配置

Dubbo框架直接集成了spring的能力,利用spring配置文件扩展出自定义配置文件解析方式。dubbo定义了自己的配置约束文件,dubbo-config/dubbo-config-spring/src/main/resources/dubbo.xsd,用以解析dubbo标签。

1.dubbo配置文件覆盖策略

dubbo常用的三种配置方式:xml配置,注解,属性文件(properties或yaml)。除了这三种常用的配置方式,还可以通过JVM参数,代码编码的方式配置。
dubbo配置的默认覆盖策略有以下几点原则:

  • -D传递给JVM参数优先级最高,比如:-Dubbo.protocol.port=20880
  • 代码或xml配置优先级次高,也就是spring的xml文件中的dubbo标签
  • properties或yaml配置文件优先级最低,如dubbo.properties,一般可用作配置默认值。只有xml文件没有定义时,dubbo.properties文件的属性才会生效。

2.dubbo 标签

(1) dubbo标签配置项列表

dubbo默认已实现的dubbo标签如下:

标签 用途 解释
<dubbo:service/> 服务配置 用于暴露一个服务,定义服务的元信息,一个服务可以用多个协议暴露,一个服务也可以注册到多个注册中心
<dubbo:reference/> 引用配置 用于创建一个远程服务代理,一个引用可以指向多个注册中心
<dubbo:protocol/> 协议配置 用于配置提供服务的协议信息,协议由提供方指定,消费方被动接受
<dubbo:application/> 应用配置 用于配置当前应用信息,不管该应用是提供者还是消费者
<dubbo:module/> 模块配置 用于配置当前模块信息,可选
<dubbo:registry/> 注册中心配置 用于配置连接注册中心相关信息
<dubbo:monitor/> 监控中心配置 用于配置连接监控中心相关信息,可选
<dubbo:provider/> 提供方配置 当 ProtocolConfig 和 ServiceConfig 某属性没有配置时,采用此缺省值,可选
<dubbo:consumer/> 消费方配置 当 ReferenceConfig 某属性没有配置时,采用此缺省值,可选
<dubbo:method/> 方法配置 用于 ServiceConfig 和 ReferenceConfig 指定方法级的配置信息
<dubbo:argument/> 参数配置 用于指定方法参数配置
另外,dubbo标签支持自定义参数扩展,如:
<dubbo:protocol name="jms">
    <dubbo:parameter key="queue" value="your_queue" />
</dubbo:protocol>

(2) dubbo标签优先级

这些标签在dubbo应用启动时会通过DubboBeanDefinitionParser进行解析,将 XML 标签解析为 Bean 对象。这些bean对象有一定的层级关系,多个标签的相同属性可能会被覆盖,这个优先级的原则是:

  • 方法级优先,接口级次之,全局配置再次之
  • 如果级别一样,则消费方优先,提供方次之

其中,服务方的标签的属性可以通过URL参数的形式传给消费方。Dubbo的参数传递方式统一用URL的形式来实现,注意不是jdk的URL,是dubbo自己定义的URL
以timeout属性为例,用官网的一张图展示dubbo标签的优先级:
在这里插入图片描述
其中<dubbo:provider>标签属性可以理解为<dubbo:service>标签属性的默认值,同样,后者会覆盖前者,<dubbo:consumer>标签属性可以理解为<dubbo:reference>标签属性的默认值,后者覆盖前者。
当使用缺省值时,是延迟初始化的,只有引用被注入到其它 Bean,或被 getBean() 获取,才会初始化。如果需要饥饿加载,即没有人引用也立即生成动态代理,可以配置init属性,比如:<dubbo:reference ... init="true" />

二、dubbo配置解析原理

下面通过源码介绍配置解析的详细细节。这里只设计最常用的xml配置的方式。

1.dubbo配置解析的代码流程

(1) 配置解析入口

dubbo应用在启动时,首先进行spring配置文件的解析,当遇到dubbo名称空间时,会回调 DubboNamespaceHandler,该类主要把不同的标签关联到解析实现类中。
该类继承了spring的NamespaceHandlerSupport类,重写init方法,做主要逻辑,代码如下:

	@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());
    }

可以看到registerBeanDefinitionParser方法将遇到的dubbo标签名交给DubboBeanDefinitionParser类来处理,具体的解析逻辑就在DubboBeanDefinitionParserparse方法里,DubboBeanDefinitionParser的parse方法是配置解析的核心代码,该方法代码比较长,主要做了以下几件事情:

  • 生成并检查spring bean定义BeanDefinition
  • 解析具体的标签,注入到BeanDefinition 中
  • 两种方式解析标签的属性,注入到标签对应bean中

下面分段来读一下这个parse方法:
关键节点添加了注释:

(2) 生成spring bean定义并检查

private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) {
		//生成spring的bean定义,指定beanCLass交给spring反射创建实例
        RootBeanDefinition beanDefinition = new RootBeanDefinition();
        beanDefinition.setBeanClass(beanClass);
        beanDefinition.setLazyInit(false);
        String id = element.getAttribute("id");
        //确保spring容器没有重复的bean
        if ((id == null || id.length() == 0) && required) {
        	//取name属性值作为bean id
            String generatedBeanName = element.getAttribute("name");
            if (generatedBeanName == null || generatedBeanName.length() == 0) {
            	//如果protocol标签没有指定name,则默认用“dubbo”做bean id
                if (ProtocolConfig.class.equals(beanClass)) {
                    generatedBeanName = "dubbo";
                } else {//其他标签没有指定name属性,则默认使用interface属性作为bean id
                    generatedBeanName = element.getAttribute("interface");
                }
            }
            if (generatedBeanName == null || generatedBeanName.length() == 0) {
                generatedBeanName = beanClass.getName();
            }
            id = generatedBeanName;
            int counter = 2;
            //检查重复bean,如果有重复,则生成唯一bean id
            while (parserContext.getRegistry().containsBeanDefinition(id)) {
                id = generatedBeanName + (counter++);
            }
        }
        if (id != null && id.length() > 0) {
            if (parserContext.getRegistry().containsBeanDefinition(id)) {
                throw new IllegalStateException("Duplicate spring bean id " + id);
            }
            //每次解析都会向Spring注册新的BeanDefiniton,后续会追加属性
            parserContext.getRegistry().registerBeanDefinition(id, beanDefinition);
            beanDefinition.getPropertyValues().addPropertyValue("id", id);
        }
        /*未完待续*/

前面这一块主要负责把标签解析成对应的bean定义,并注册到spring上下文中,同时保证spring容器中相同id的bean不会被覆盖。

(3) 具体dubbo标签的解析

下面来看具体的像<dubbo:service><dubbo:provider><dubbo:consumer>等标签的解析,这些标签都会解析成xxxConfig类型,最后实例化,其中<dubbo:service><dubbo:reference>标签是xxxBean类型,其实也是xxxConfig的子类,因为这两个标签代表具体的服务暴露和服务消费,父类抽象了一些逻辑出来。

		/*上接*/
		if (ProtocolConfig.class.equals(beanClass)) {
            for (String name : parserContext.getRegistry().getBeanDefinitionNames()) {
                BeanDefinition definition = parserContext.getRegistry().getBeanDefinition(name);
                PropertyValue property = definition.getPropertyValues().getPropertyValue("protocol");
                if (property != null) {
                    Object value = property.getValue();
                    if (value instanceof ProtocolConfig && id.equals(((ProtocolConfig) value).getName())) {
                        definition.getPropertyValues().addPropertyValue("protocol", new RuntimeBeanReference(id));
                    }
                }
            }
        } else if (ServiceBean.class.equals(beanClass)) {
        	//拿到service标签的class属性,也就是服务接口的全限定名
            String className = element.getAttribute("class");
            if (className != null && className.length() > 0) {
                RootBeanDefinition classDefinition = new RootBeanDefinition();
                //反射生成实例,挂在bean定义上
                classDefinition.setBeanClass(ReflectUtils.forName(className));
                classDefinition.setLazyInit(false);
                //该方法主要解析标签中的属性,通过键值对的方式提取出来放到bean定义中,spring会自动注入这些属性。
                parseProperties(element.getChildNodes(), classDefinition);
                beanDefinition.getPropertyValues().addPropertyValue("ref", new BeanDefinitionHolder(classDefinition, id + "Impl"));
            }
        } else if (ProviderConfig.class.equals(beanClass)) {
            parseNested(element, parserContext, ServiceBean.class, true, "service", "provider", id, beanDefinition);
        } else if (ConsumerConfig.class.equals(beanClass)) {
            parseNested(element, parserContext, ReferenceBean.class, false, "reference", "consumer", id, beanDefinition);
        }

在解析<dubbo:provider><dubbo:consumer>时,复用了parseNested方法,主要逻辑是处理内部嵌套标签,因为<dubbo:provider>标签是可以内部嵌套<dubbo:service>标签的,同理<dubbo:consumer>标签可以内部嵌套<dubbo:reference>标签,该方法逻辑定义了内部标签会持有外部标签的对象,这种设计可以使得内部标签可以直接持有外部标签的属性。也就是<dubbo:service>可以获取<dubbo:provider>的属性作为缺省值。

(3) 标签属性的解析和注入

标签的属性提取方式主要分为以下两种:

  • 查找配置对象的get,set,is前缀的方法,并通过反射调用方法名与属性名相同的方法,进行注入。
  • 如果没有属性名找不到对应的get,set,is前缀方法,则当做parameters参数存储,它是一Map对象。

以上两种方式最终都会将属性作为URL参数存储到dubbo框架的URL中。

DubboBeanDefinitionParserparse方法里,关于属性解析的这片代码过于细节,由于篇幅过长,这里就粘贴出来了。可以自行查看源码。

以上三步操作完成后,parse方法返回一个已经属性注入的spring框架的Beandefinition对象。如果属性是引用对象,dubbo默认会创建RuntimeBeanReference类型进行注入,运行时由spring来注入引用对象。
其实这里dubbo只做了属性提取,从标签提取到BeanDefinition,运行时注入和转换都又spring来完成。

2.Dubbo标签承载对象继承关系

最后,放上一张dubbo标签承载对象的继承关系图:
在这里插入图片描述


发布了43 篇原创文章 · 获赞 17 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/weixin_41172473/article/details/103283693