​ 先放一张官网的服务暴露时序图,对我们梳理源码有很大的帮助。注:不论是暴露还是导出或者是其他翻译,都是描述export的,只是翻译不同。

0.配置解析

​ 在Spring的配置文件中,Dubbo指明了DubboNamespaceHandler类作为标签解析。

​ 与服务相关的显然就是service,找到对应的ServiceBean类,进入这个类,开始服务暴露的源码分析。这个类位于Dubbo源码config模块-spring模块下的根目录。

1.开始export

​ export也是上面时序图中最开始的一个方法,从这个方法名也知道,这就是服务暴露或者叫出口最关键的方法。进入ServiceBean类,在这个类中一共有两处调用了此方法。即onApplicationEventafterPropertiesSet,了解过Spring Bean生命周期的朋友看到这两个方法肯定眼熟,果然,这个类实现了相关的接口:

​ 看一下onApplicationEvent方法:

​ 从它的if判断条件调用的几个方法名可以看出,如果是延迟暴露、还未暴露过且支持暴露就可以执行export方法了。这里说一下,这个isDelay方法有点迷惑,字面意思应该为是否延迟,返回ture代表延迟。但是实际意思却为返回true代表不延迟,因为这个判断条件是delaynull || delay-1,代表没有设置延迟。所以这个方法中的export才是第一个触发的。

​ 接着进入到export方法。这个方法会跳转到ServiceConfig类,是ServiceBean的父类,也正好符合时序图。

​ 这几个if的作用就是判断是否需要暴露和延迟暴露。如果不需要暴露就返回,否则都会执行doExport方法的。进入这个方法,这个方法代码很多,前面一堆if都是检测配置信息的,关注的重点在doExportUrls方法。

​ Dubbo是支持多注册中心和多协议的,在这里就表现出来了。获取到的注册中心URL放到一个list里面。其中loadRegistries方法就是根据配置组装成相关的URL并返回,如加载注册中心地址、检查地址是否合法、添加配置信息等。咱们先关注重点,这个方法就不跟下去了,不然没完没了。至于组装后的URL可以debug自己看看,大概样子如下:

2.组装URL

​ 进入到doExportUrlsFor1Protocol方法,这个比较重要。从它的名字可以看出,它的作用是组装暴露URL。

​ 这个方法很长,主要就是创建一个map然后添加各种值,包括配置信息、提供的服务等等。由于这个方法分支非常多,官网给了各个分支含义的解释,配合源码能很好理解其意思:

// 获取 ArgumentConfig 列表
for (遍历 ArgumentConfig 列表) {
if (type 不为 null,也不为空串) { // 分支1
1. 通过反射获取 interfaceClass 的方法列表
for (遍历方法列表) {
1. 比对方法名,查找目标方法
2. 通过反射获取目标方法的参数类型数组 argtypes
if (index != -1) { // 分支2
1. 从 argtypes 数组中获取下标 index 处的元素 argType
2. 检测 argType 的名称与 ArgumentConfig 中的 type 属性是否一致
3. 添加 ArgumentConfig 字段信息到 map 中,或抛出异常
} else { // 分支3
1. 遍历参数类型数组 argtypes,查找 argument.type 类型的参数
2. 添加 ArgumentConfig 字段信息到 map 中
}
}
} else if (index != -1) { // 分支4
1. 添加 ArgumentConfig 字段信息到 map 中
}
}

​ 当然,如果你没有配置相关的信息,如dubbo:method,在debug源码时,压根就不会进入到这些分支里面。现在我们看一下URL长啥样:

​ 可以看到协议已经变成了dubbo,具体的服务接口也显示了出来。而map的值就存在parameters当中。

3.服务暴露

​ 依旧在doExportUrlsFor1Protocol方法里,具体的服务URL已经组装好了,接下来就是服务暴露了。先看这么一段代码:

​ 这段代码有两个关键点,已经在图中标注。第一处是先进行本地暴露。第二处判断如果有注册中心,就会进行远程暴露。注册中心的URL在doExportUrls中已经获取了。

​ 先看本地暴露,进入到exportLocal方法:

​ exportLocal方法比较简单,根据协议头判断是否需要暴露服务,如果需要,就创建一个新的URL

​ 我们看一下这个URL长啥样:

​ 协议变成了injvm,从这个协议名称就可以猜测到,这个在一个jvm内的协议。IP地址也从远程注册中心的IP地址变成了本机地址。

​ 本地URL组装好后,会创建一个exporter对象。这个对象是由protocol的export方法生成,我们点进这个抽象方法,会发现它有一个@Adaptive注解。这个注解修饰方法时会生成一个代理类。主要配合SPI机制使用,SPI的作用简单的说就是提供一个标准化的接口,可能有不同的实现,而这个实现类的路径我们就放在一个固定的位置,让框架去读取。同样的用法也在proxyFactory.getInvoker()中。关于SPI的解析放在最后。这个export的具体实现方法如下图:

​ 所在类为InjvmProtocol。这个实现方法就不说了,主要就是根据传入的参数进行封装,我们直接看最终的exporter:

​ 可以看到,已经找到了服务接口的实现类了。最后就是将exporter添加到exporters中,这个exporters是本地的一个集合,专门缓存exporter。

​ 接着就是远程暴露了,其实和本地暴露的目的一样,都要封装成invoker——>exporter,最后添加到exporters中,还多了一步注册。首先依旧是通过getInvoker封装成invoker。(这里说句题外话,可以根据参数的协议类型找到这些抽象方法的实现类。Dubbo命名很严谨,比如参数中,URL的协议为registry,那么其实现类就是RegistryProtocol。至于为什么要封装成invoker我们最后再分析,现在只需理解这么做是为了屏蔽细节,统一暴露)。

​ 封装成invoker后又弄了一层wrapperInvoker,点进这个类,可以发现其实就给invoker额外封装一层,可以提供更多信息以及一些工具方法,比如ServiceConfig、检测是否有效。

​ 接着主要区别在export方法当中,其实现方法在RegistryProtocol类中(因为参数wrapperInvoker的url协议为registry)。实现方法部分截图如下:

​ 这个方法主要做了如下工作:

​ 1.调用doLocalExport导出服务

​ 2.向注册中心注册

​ 3.向注册中心订阅override数据

​ 4.创建并返回DestroyableExporter

​ 首先进入到doLocalExport方法,这个方法主要就是会调用DubboProtocol的export方法,为了避免过多的代码截图把自己弄昏了,就不贴这个方法了。这个方法开头同样的,根据invoker获取URL,关键在于它调用了一个openServer。看到这个方法名应该知道是啥意思了,即打开服务。好家伙,终于要结束了么。

​ 这个方法很清晰,获取注册中心的IP和端口号、检查缓存、创建server。接着跟进源码,bind过程,主要关注Transports的bind方法。这里Dubbo也是用Adaptive注解和SPI机制,实现了拓展功能。它会根据传入的参数选择不同类型的Transport,默认是NettyTransporter。接下来就是Netty服务启动的相关过程了,以前写过相关博客,就不跟进了。

​ 接着,我们看上上张截图,有一个if会判断是否需要注册,如果需要注册就会向注册中心注册。我们接着跟踪源码,一直到如下方法:

​ 看到了Zookeeper客户端,到这里就明白了,是向Zookeeper添加信息。我们最后看一下Zookeeper里面的内容。我们打开Zookeeper客户端,查看一下服务:

​ 可以发现,已经有我们注册的服务了。最好下个可视化的Zookeeper客户端,可以进入到这些目录,可以找到Provider的IP地址。

疑问解析

  • 为什么要本地暴露?

    • 调用本地服务时,避免网络通信。
  • 为什么要封装成invoker和export?

    • 前面的源码分析中,本地和远程都经过了封装invoker和export两个步骤。export是服务暴露的最终形态,其包含invoker以及其他更多信息,比如注册中心、服务接口、实现类等等信息。下面是官网的一张截图:

    • 官网是这么说的:由于 Invoker 是 Dubbo 领域模型中非常重要的一个概念,很多设计思路都是向它靠拢、或转换为它。这个所谓的靠拢就如图中显示的那样,不管在消费者方还是服务提供方,均会出现Invoker,它代表一个可执行体,并屏蔽了内部细节。既然它这么重要,我们就看一下它是如果创建的。
    • 其是由proxyFactory.getInvoker创建而来,通过debug找到它的实现类:

    • 上面的方法在JavassistProxyFactory类中,其重写了doInvoke方法,比较简单,只是转发了invokeMethod。其中AbstractProxyInvoker是一个抽象类,实现了Invoker接口。而这个Wrapper的作用是包裹目标类,仅可通过getWrapper(Classs)创建子类。子类可以对入参Class进行解析,拿到类方法、成员变量等信息。在这里,目标类就是暴露服务的实现类。
    • 关于Wrapper的分析内容非常多,这里记录一下官网的解析:http://dubbo.apache.org/zh/docs/v2.7/dev/source/export-service/#221-invoker-%E5%88%9B%E5%BB%BA%E8%BF%87%E7%A8%8B
  • SPI是什么?

    • SPI(Service Provider Interface),其作用前面也说了,就是定义一个标准接口,这个接口的实现由用户决定。这样做的好处就是提高了框架的拓展性。但是这个接口的实现放在哪,得让框架知道。在Java SPI中,规定在META-INF/services/ 目录下,创建一个以接口全路径名命名的文件,文件中写出接口实现类的全路径名。然后Java就会去遍历加载这些实现类并创建实例。
    • 前面说了Java SPI,但是Dubbo并没有用Java规定的方法,而是自己实现了SPI机制。可以从ServiceLoader.load()方法跟踪源码看一下,Java SPI机制是遍历了所有的实现类,而不是按需加载,造成了不必要的浪费。说到Dubbo SPI,那么它的规定目录在哪?在META-INF/dubbo/internal目录下。我们从源码的该路径下找个文件看看。

​ 可以看到Dubbo SPI的配置文件内容是键值对的形式,这样就可以实现按需加载。根据key值,获取全路径名,然后加载。 如果需要自己自定义,就直接在MEATA-INF/dubbo/目录下创建配置文件即可。同样的,类似Java SPI中的ServiceLoader,Dubbo中叫ExtensionLoader。这个类的几个方法,作用很明确,也不复杂,这里就不跟踪了。其中getExtensionLoader方法,入参是需要加载的接口,这个方法会检查是否有对应类型的ExtensionLoader对象,如果没有就新建一个。createExtension方法就是根据名字获取对应的实现类,这样就实现了按需加载。

Dubbo服务暴露源码解析②的更多相关文章

  1. Dubbo服务引用源码解析③

    ​ 上一章分析了服务暴露的源码,这一章继续分析服务引用的源码.在Dubbo中有两种引用方式:第一种是服务直连,第二种是基于注册中心进行引用.服务直连一般用在测试的场景下,线上更多的是基于注册中心的方式 ...

  2. 13.1 dubbo服务降级源码解析

    从 9.1 客户端发起请求源码 的客户端请求总体流程图中,截取部分如下: //代理发出请求 proxy0.sayHello(String paramString) -->InvokerInvoc ...

  3. Netty5服务端源码解析

    Netty5源码解析 今天让我来总结下netty5的服务端代码. 服务端(ServerBootstrap) 示例代码如下: import io.netty.bootstrap.ServerBootst ...

  4. SpringCloud服务调用源码解析汇总

    相信我,你会收藏这篇文章的,本篇文章涉及Ribbon.Hystrix.Feign三个组件的源码解析 Ribbon架构剖析 这篇文章介绍了Ribbon的基础架构,也就是下图涉及到的6大组件: Ribbo ...

  5. Laravel开发:Laravel核心——Ioc服务容器源码解析(服务器解析)

    make解析 服务容器对对象的自动解析是服务容器的核心功能,make 函数.build 函数是实例化对象重要的核心,先大致看一下代码: public function make($abstract) ...

  6. Laravel开发:Laravel核心——Ioc服务容器源码解析(服务器绑定)

    服务容器的绑定 bind 绑定 bind 绑定是服务容器最常用的绑定方式,在 上一篇文章中我们讨论过,bind 的绑定有三种: 绑定自身 绑定闭包 绑定接口 今天,我们这篇文章主要从源码上讲解 Ioc ...

  7. nacos服务注册源码解析

    1.客户端使用 compile 'com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-discovery:2.2.3.RELEASE' compi ...

  8. Nacos服务发现源码解析

    1.Spring服务发现的统一规范 Spring将这套规范定义在Spring Cloud Common中 discovery包下面定义了服务发现的规范 核心接口:DiscoveryClient 用于服 ...

  9. spring boot actuator工作原理之http服务暴露源码分析

    spring boot actuator的官方文档地址:https://docs.spring.io/spring-boot/docs/current/reference/html/productio ...

随机推荐

  1. 企业安全04-phpstudy最新版本nginx 默认存在任意文件解析漏洞

    phpstudy最新版本nginx 默认存在任意文件解析漏洞 一.漏洞描述 phpStudy是一个PHP调试环境的程序集成包.该程序包集成最新的Apache+PHP+MySQL+phpMyAdmin+ ...

  2. dubbo起停之配置注解

    虽然说多种方式配置dubbo最后殊途同归实例化为dubbo的各配置对象,但是了解下注解的解析过程也能让我们清楚dubbo在spring bean的什么时候怎么样实例化一个代理对象,这点来说了解整个过程 ...

  3. 测试:ADB

    配置 JAVA: 1.安装jdk的按抓包(傻瓜式安装不需要更改路径) 2.我的电脑右击属性--高级系统设置--环境变量--用户变量--新建:JAVA_HOME C:\Program Files\Jav ...

  4. web端项目如何测试

    1.是否支持各种网络 2.网络如果演示能否正常加载 3.加载时断网还能加载出来么 4.浏览时断网页面是否保持 5.是否兼容各种不同的浏览器 6.不同的浏览器加载出的页面是否一致 7.页面效果如何 8. ...

  5. 想了解表格问答,我们先看看TA的前世

    摘要:表格问答是一种针对自然语言问题,根据表格内容给出答案的任务. 一.什么是表格问答 表1是一张综艺节目收视率报表,假如你需要了解市场份额在3%以上的综艺节目,你会选择采用什么样的方法? 首先,用肉 ...

  6. CAD插件

    CAD插件使用: 1.首先得有插件,插件解压,放那个盘都可以,只要自己觉得放得下,注:(每次打开CAD想要用插件都要的步骤)打开CAD---AP回车----找到插件所在文件夹-------Ctrl+A ...

  7. 部署 Prometheus 和 Grafana 到 k8s

    在 k8s 中部署 Prometheus 和 Grafana Intro 上次我们主要分享了 asp.net core 集成 prometheus,以及简单的 prometheus 使用,在实际在 k ...

  8. PyQt(Python+Qt)学习随笔:QTreeWidget中获取可见项视口位置矩形的visualItemRect方法

    老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 树型部件的visualItemRect方法可以返回参数指定项在视口的位置矩形. QRect visu ...

  9. 第13.3节 图形界面开发tkinter

    一. 引言 老猿最开始是准备就tkinter单独开一个章节,但学了一段时间tkinter,最后放弃了,前一阵子还准备干脆不介绍相关的内容.主要原因有三个,一是tkinter没有界面设计的工具,所有界面 ...

  10. python接口自动化测试框架(post提交添加变量)

    1.python接口测试框架包含哪几部分 数据源-> GET/POST 发送请求->接收返回结果->断言测试结果->生成测试报告(html报告)->网页报告 2.pyth ...