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

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

与服务相关的显然就是service,找到对应的ServiceBean类,进入这个类,开始服务暴露的源码分析。这个类位于Dubbo源码config模块-spring模块下的根目录。
1.开始export
export也是上面时序图中最开始的一个方法,从这个方法名也知道,这就是服务暴露或者叫出口最关键的方法。进入ServiceBean类,在这个类中一共有两处调用了此方法。即onApplicationEvent和afterPropertiesSet,了解过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服务暴露源码解析②的更多相关文章
- Dubbo服务引用源码解析③
上一章分析了服务暴露的源码,这一章继续分析服务引用的源码.在Dubbo中有两种引用方式:第一种是服务直连,第二种是基于注册中心进行引用.服务直连一般用在测试的场景下,线上更多的是基于注册中心的方式 ...
- 13.1 dubbo服务降级源码解析
从 9.1 客户端发起请求源码 的客户端请求总体流程图中,截取部分如下: //代理发出请求 proxy0.sayHello(String paramString) -->InvokerInvoc ...
- Netty5服务端源码解析
Netty5源码解析 今天让我来总结下netty5的服务端代码. 服务端(ServerBootstrap) 示例代码如下: import io.netty.bootstrap.ServerBootst ...
- SpringCloud服务调用源码解析汇总
相信我,你会收藏这篇文章的,本篇文章涉及Ribbon.Hystrix.Feign三个组件的源码解析 Ribbon架构剖析 这篇文章介绍了Ribbon的基础架构,也就是下图涉及到的6大组件: Ribbo ...
- Laravel开发:Laravel核心——Ioc服务容器源码解析(服务器解析)
make解析 服务容器对对象的自动解析是服务容器的核心功能,make 函数.build 函数是实例化对象重要的核心,先大致看一下代码: public function make($abstract) ...
- Laravel开发:Laravel核心——Ioc服务容器源码解析(服务器绑定)
服务容器的绑定 bind 绑定 bind 绑定是服务容器最常用的绑定方式,在 上一篇文章中我们讨论过,bind 的绑定有三种: 绑定自身 绑定闭包 绑定接口 今天,我们这篇文章主要从源码上讲解 Ioc ...
- nacos服务注册源码解析
1.客户端使用 compile 'com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-discovery:2.2.3.RELEASE' compi ...
- Nacos服务发现源码解析
1.Spring服务发现的统一规范 Spring将这套规范定义在Spring Cloud Common中 discovery包下面定义了服务发现的规范 核心接口:DiscoveryClient 用于服 ...
- spring boot actuator工作原理之http服务暴露源码分析
spring boot actuator的官方文档地址:https://docs.spring.io/spring-boot/docs/current/reference/html/productio ...
随机推荐
- Linux如何安装Docker?
使用yum安装(centos7) Docker要求Centos系统的内核版本高于3.10,安装Docker前需要验证你的服务器内核版本是否支持Docker. 通过 uname -r 命令来查看你的服务 ...
- Arduion学习(一)点亮三色发光二极管
这是我接触Arduion以来第一个小实验 实验准备: 1.查阅相关资料,了解本次实验所用到的引脚.接口的相关知识. 2.准备Arduion板(本次实验所用到的型号为mega2560).三色发光二极管. ...
- 集群--lvs
快一个月没有更新博客了,最近一段时间在忙世界技能大赛网络系统系管理这个项目,没有太多的时间,我百忙之中更新一下.最近赛题中有说到集群这个,lvs这个东西(我也该学学这个了,一直停留在基础部分,是时候学 ...
- ③SpringCloud 实战:使用 Ribbon 客户端负载均衡
这是SpringCloud实战系列中第三篇文章,了解前面第两篇文章更有助于更好理解本文内容: ①SpringCloud 实战:引入Eureka组件,完善服务治理 ②SpringCloud 实战:引入F ...
- linux 权限提升
1.内核提权,根据版本搜索相应exp 查看操作系统版本命令 uname –a lsb_release –a cat /proc/version 查看内核版本 cat /etc/issue 查看发行类型 ...
- 【2020.11.28提高组模拟】T1染色(color)
[2020.11.28提高组模拟]T1染色(color) 题目 题目描述 给定 \(n\),你现在需要给整数 \(1\) 到 \(n\) 进行染色,使得对于所有的 \(1\leq i<j\leq ...
- js实现视频截图,视频批量截图,canvas实现
截取视频的某一时间的图像并保存 利用canvas的绘画能力画出视频某一帧的视频画面, 获得到图像之后转换成base64图像, 再利用a标签的实现自动保存到本地 html代码 <!DOCTYPE ...
- 第1.2节 Python学习环境的使用
Python的环境安装好以后,可以通过IDLE(Python 3.7 64-bit)进入图形界面使用Python,也可以通过Python 3.7 64-bit进入命令行交互式界面,两者都可以使用,不过 ...
- PyQt开发实战: 利用QToolBox开发的桌面工具箱
老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 一.引言 toolBox工具箱是一个容器部件,对应类为QToolBox,在其内有一列从上到下顺序排列 ...
- Typescript + React 高仿 Antd 从零到一打造自己的组件库(完整)
买了张轩老师的课程,感觉很不错,适用于高级进阶,老师讲的通俗易懂,欢迎讨论学习.WX:Jujiu_i