源码分析Dubbo前置篇-寻找注册中心、服务提供者、服务消费者功能入口

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

转载声明:转载请注明出处,本技术博客是本人原创文章

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> 源码分析Dubbo前置篇-寻找注册中心、服务提供者、服务消费者功能入口

本节主要阐述如下两个问题:

  • Dubbo自定义标签实现。
  • dubbo通过Spring加载配置文件后,是如何触发注册中心、服务提供者、服务消费者按照Dubbo的设计执行相关的功能。
  • 所谓的执行相关功能如下:

  • 注册中心启动,监听消息提供者的注册服务、接收消息消费者的服务订阅(服务注册与发现机制)。
  • 服务提供者向注册中心注册服务。
  • 服务消费者向注册中心订阅服务。
  • 服务提供者向注册中心注册服务。

    接下来从使用dubbo的角度,从配置文件入手:

    Dubbo服务提供者的一般配置如下:

    
     1!-- 提供方应用信息,用于计算依赖关系 --
     2dubbo:application name="uop" owner="uce"/
     3!-- 使用zookeeper注册中心暴露服务地址 --
     4dubbo:registry protocol="zookeeper" address="zookeeper://192.168.xx.xx:2181?backup=192.168.xx.xx:2182,192.168.xx.xx:2183" /
     5!--dubbox中引入Kryo和FST这两种高效Java序列化实现,来逐步取代原生dubbo中的hessian2,如果使用kryo记得添加依赖 --
     6dubbo:protocol name="dubbo" serialization="kryo"  port="20990"  /
     7!-- 定义服务提供者默认属性值 --
     8dubbo:provider timeout="5000" threadpool="fixed"  threads="100" accepts="1000" token="true"/
     9!-- 暴露服务接口 一个服务可以用多个协议暴露,一个服务也可以注册到多个注册中心--
    10!--Provider上尽量多配置Consumer端的属性,让Provider实现者一开始就思考Provider服务特点、服务质量的问题--
    11dubbo:service interface="com.yingjun.dubbox.api.UserService" ref="userService" /
    

    上面通过dubbo提供的dubbo:application、dubbo:registry、dubbo:protocol、dubbo:provider、dubbo:service分别定义dubbo应用程序名、注册中心、协议、服务提供者参数默认值、服务提供者,这些配置后面的实现原理是什么呢?是如何启动并发挥相关作用的呢?

    Spring自定义标签实现原理

    dubbo自定义标签与命名空间其实现代码在模块dubbo-config中,下面将详细介绍其实现原理。

    DubboNamespaceHandler

    dubbo命名空间实现handler,其全路径:com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler,其源码实现如下:

    
     1public class DubboNamespaceHandler extends NamespaceHandlerSupport {
     2
     3    static {
     4        Version.checkDuplicate(DubboNamespaceHandler.class);
     5    }
     6
     7    @Override
     8    public void init() {
     9        registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
    10        registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
    11        registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
    12        registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
    13        registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
    14        registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
    15        registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
    16        registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
    17        registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
    18        registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser());
    19    }
    20}
    

    从这里可以看出,dubbo自定义的标签主要包括:application、module、registry、monitor、provider、consumer、protocol、service、reference、annotation,其具体解析实现类主要由DubboBeanDefinitionParser(基于xml配置文件)、AnnotationBeanDefinitionParser(基于注解),下文会详细分析上述两个解析类的实现。

    定义dubbo.xsd 文件

    在dubbo-config-spring模块下的src/main/resouce/META-INF中分别定义dubbo.xsd、spring.handlers、spring.schemas。

    关于Spring如何新增命名空间与标签,在源码分析ElasticJob时已经详细介绍过,再这里就不做过多重复,如需了解,请查看:

    Bean解析机制

    我们应该知道,Spirng的配置支持xml配置文件与注解的方式,故Dubbo也支持两种配置方式,xml与注解方式。

    xml配置方式解析

    源码分析Dubbo前置篇-寻找注册中心、服务提供者、服务消费者功能入口

    BeanDefinitionParser:Spring定义的bean解析器,要实现自定义标签,则需要实现该接口,然后通过NamespaceHandlerSupport将Bean定义解析器注册到Spring bean解析器中。从接口中可以看出,其终极目标就是将Element element(xml节点)解析成BeanDefinition,有关于Spring BeanDefinition,请参考:https://mp.csdn.net/mdeditor/80490206《Spring系列之基础篇-Spring BeanDefinition初探》。

    DubboBeanDefinitionParser构造函数如下:

    
    1public DubboBeanDefinitionParser(Class? beanClass, boolean required) {
    2        this.beanClass = beanClass;
    3        this.required = required;
    4}
    
  • beanClass:该xml标签节点最终会被Spring实例化的类名。
  • required:该标签的ID是否必须。
  • required:该标签的ID是否必须。

    其标签对应的实体对象如下:

  • dubbo:application com.alibaba.dubbo.config.ApplicationConfig

  • dubbo:module com.alibaba.dubbo.config.ModuleConfig

  • dubbo:registry com.alibaba.dubbo.config.RegistryConfig

  • dubbo:monitor com.alibaba.dubbo.config.MonitorConfig

  • dubbo:provider com.alibaba.dubbo.config.ProviderConfig

  • dubbo:consumer com.alibaba.dubbo.config.ConsumerConfig

  • dubbo:protocol com.alibaba.dubbo.config.ProtocolConfig

  • dubbo:service com.alibaba.dubbo.config.ServiceBean

  • dubbo:reference com.alibaba.dubbo.config.ReferenceBean

  • dubbo:module
    com.alibaba.dubbo.config.ModuleConfig

    dubbo:monitor
    com.alibaba.dubbo.config.MonitorConfig

    dubbo:consumer
    com.alibaba.dubbo.config.ConsumerConfig

    dubbo:service
    com.alibaba.dubbo.config.ServiceBean

    bean解析器的主要目的就是将上述标签,解析成对应的BeanDifinition,以便Spring构建上述类的实例。

    本节不探究DubboBeanDefinitionParser根据xml定义的标签与属性转换成BeanDefinitionParser的每一行代码,本节只给出其大体关键点。

    Step1:解析id属性,如果DubboBeanDefinitionParser对象的required属性为true,如果id为空,则根据如下规则构建一个id。

  • 如果name属性不为空,则取name的值,如果已存在,则为 name + 序号,例如  name,name1,name2。
  • 如果name属性为空,如果是dubbo:protocol标签,则取protocol属性,其他的则取interface属性,如果不为空,则取该值,但如果已存在,和name处理相同,在后面追加序号。
  • 如果第二步还未空,则取beanClass的名称,如果已存在,则追加序号。
  • 如果name属性为空,如果是dubbo:protocol标签,则取protocol属性,其他的则取interface属性,如果不为空,则取该值,但如果已存在,和name处理相同,在后面追加序号。

    Step2:根据不同的标签解析特殊属性。

  • dubbo:protocol,添加protocol属性(BeanDefinition)。
  • dubbo:service,添加ref属性。
  • dubbo:provider,嵌套解析,dubbo:provider标签有两个可选的子标签,dubbo:service、dubbo:parameter,这里需要嵌套解析dubbo:service标签知识点,dubbo:provider是配置服务提供者的默认参数,在dubbo spring配置文件中可以配置多个dubbo:provider,那dubbo:service标签如何选取一个合适的dubbo:provider作为其默认参数呢?有两种办法,其一:将dubbo:service标签直接声明在dubbo:provider方法,其二,在dubbo:service中通过provider属性指定一个provider配置,如果不指定,并且存在多个dubbo:provider配置,则会抛出错误。
  • dubbo:customer:解析嵌套标签,其原理与dubbo:provider解析一样。
  • dubbo:service,添加ref属性。

    dubbo:customer:解析嵌套标签,其原理与dubbo:provider解析一样。

    Step3:解析标签,将属性与值填充到BeanDefinition的propertyValues中。
    最终返回BeanDefinition实例,供Spring实例化Bean。

    上述已经解答了Dubbo自定义标签的解析实现,主要完成了ApplicationConfig、RegistryConfig、ServiceBean、ReferenceBean实例的初始化,那什么时候构建与注册中心的连接、服务提供者什么时候会向注册中心注册服务,服务消费者向注册中心订阅服务呢?

    通过上述步骤,我们已经知道已经成功解析注册中心、服务提供者、服务消费者的配置元信息,并将其实例化,按照我们的思路,配置对象生成后,下一步应该是实现Dubbo服务的注册与发现机制,但代码中无法找到相关代码。

    据我目前所掌握的知识,Spring在对象实例化,一般有两种方式来对Bean做一些定制化处理。

  • 实现BeanPostProcessor Spring后置处理器,在Bean初始化前后执行相关操作。
  • Bean实现InitializingBean接口(init-method)
  • ServiceBean与ReferenceBean的核心类图如下:

    源码分析Dubbo前置篇-寻找注册中心、服务提供者、服务消费者功能入口

    ServiceBean(服务提供者)与ReferenceBean(服务消费者)比较特殊,实现了Spring与Bean生命周期相关的接口。

  • InitializingBean,其声明的接口为afterPropertiesSet方法,顾名思义,就是在bean初始化所有属性之后调用。
  • DisposableBean:其声明的接口为destroy()方法,在Spring BeanFactory销毁一个单例实例之前调用。
  • ApplicationContextAware:其声明的接口为void setApplicationContext(ApplicationContext applicationContext),实现了该接口,Spring容器在初始化Bean时会调用该方法,注入ApplicationContext,已方便该实例可以直接调用applicationContext获取其他Bean。
  • ApplicationListener:容器重新刷新时执行事件函数。
  • BeanNameAware:其声明的接口为:void setBeanName(String name),实现该接口的Bean,其实例可以获取该实例在BeanFactory的id或name。
  • FactoryBean:Spring初始化Bean的另外一种方式,例如dubbo:reference,需要返回的对象并不是ReferenceBean,而是要返回ref指定的代理类来执行业务操作,故这里使用FactoryBean非常合适。
  • DisposableBean:其声明的接口为destroy()方法,在Spring BeanFactory销毁一个单例实例之前调用。

    ApplicationListener:容器重新刷新时执行事件函数。

    FactoryBean:Spring初始化Bean的另外一种方式,例如dubbo:reference,需要返回的对象并不是ReferenceBean,而是要返回ref指定的代理类来执行业务操作,故这里使用FactoryBean非常合适。

    FactoryBean定义了如下三个方法:

  • T getObject() throws Exception:获取需要返回的结果对象。
  • Class getObjectType():获取返回对象的类型。
  • boolean isSingleton():返回是否是单例。
  • 看到这里,不免有一点小激动,似乎已经摸到Dubbo服务注册与发现机制(Dubbo服务提供者、Dubbo服务消费者、注册中心的启动流程入口点了,下一步就是详细分析ServiceBean、ReferenceBean的实现原理,试图揭开Dubbo服务注册与发现机制,该部分内容将在下一篇中详细分析。

    注解配置方式解析

    注解配置方式的解析入口类:AnnotationBeanDefinitionParser,也是基于Spring注解解析逻辑,这部分在将在未来《Spring系列进阶篇-源码分析注解解析实现原理》中详细分析,目前暂未深究,读者朋友们,如果有兴趣,可以以AnnotationBeanDefinitionParser为入口,进行进一步的分析。

    本节就讲解到这里了,下一篇将重点分析ServiceBean(服务提供者启动流程)。

    广告:作者的新书《RocketMQ技术内幕》已上市

    源码分析Dubbo前置篇-寻找注册中心、服务提供者、服务消费者功能入口《RocketMQ技术内幕》已出版上市,目前可在主流购物平台(京东、天猫等)购买,本书从源码角度深度分析了RocketMQ NameServer、消息发送、消息存储、消息消费、消息过滤、主从同步HA、事务消息;在实战篇重点介绍了RocketMQ运维管理界面与当前支持的39个运维命令;并在附录部分罗列了RocketMQ几乎所有的配置参数。本书得到了RocketMQ创始人、阿里巴巴Messaging开源技术负责人、Linux OpenMessaging 主席的高度认可并作序推荐。目前是国内第一本成体系剖析RocketMQ的书籍。
    新书7折优惠!7折优惠!7折优惠!

    更多文章请关注微信公众号:

    源码分析Dubbo前置篇-寻找注册中心、服务提供者、服务消费者功能入口

    推荐关注微信公众号:RocketMQ官方微信公众号。

    源码分析Dubbo前置篇-寻找注册中心、服务提供者、服务消费者功能入口

    原文始发于微信公众号(中间件兴趣圈):

    本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

    转载声明:转载请注明出处,本技术博客是本人原创文章

    本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

    原文链接:blog.ouyangsihai.cn >> 源码分析Dubbo前置篇-寻找注册中心、服务提供者、服务消费者功能入口


     上一篇
    源码分析Dubbo服务提供者启动流程-上篇 源码分析Dubbo服务提供者启动流程-上篇
    微信公众号:**[中间件兴趣圈]** 本节将详细分析Dubbo服务提供者的启动流程,请带着如下几个疑问进行本节的阅读,因为这几个问题将是接下来几篇文章分析的重点内容。 什么时候建立与注册中心的连接。 服务提供者什么时候向注册中心注
    2021-04-05
    下一篇 
    【死磕Java并发】—– J.U.C之并发工具类——Semaphore 【死磕Java并发】—– J.U.C之并发工具类——Semaphore
    此篇博客所有源码均来自JDK 1.8 信号量Semaphore是一个控制访问多个共享资源的计数器,和CountDownLatch一样,其本质上是一个“共享锁”。 友情提示:欢迎关注公众号【芋道源码】。😈关注后,拉你进【源码圈】微信
    2021-04-05