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 ...
随机推荐
- Codeforces Round #668 C. Balanced Bitstring (Div. 2)题解(思维)
题目链接 题目大意 给你一个长为n的01串,要你使得每一个01串中0和1的个数都要相等,01串中有?字符,你可以使得这个字符变为0或1,要你求是否可以满足条件.输出YES或NO 题目思路 这个题目的难 ...
- 导出mysql内数据 python建倒排索引
根据mysql内数据,python建倒排索引,再导回mysql内. 先把mysql内的数据导出,先导出为csv文件,因为有中文,直接打开csv文件会乱码,再直接改文件的后缀为txt,这样打开时不会是乱 ...
- 以注解的方式实现api和provider
1.provider import com.alibaba.dubbo.config.annotation.Service; import facade.EchoService; import com ...
- Jmeter代理服务器录制脚本--浏览器拦截访问链接
在 Jmeter性能测试的过程中您是否会遇到代理服务器无法打开浏览器,无法录制脚本的情况呢? 在测试过程中,我也遇到过这样的问题,希望能帮到正在找寻答案的你.... Jmeter录制脚本时,跟http ...
- 安装spyder记录
sudo apt-get install spyder 报错:ERROR: Could not find a version that satisfies the requirement pyqt5& ...
- MySQL慢查询日志(SLOW LOG)
慢查询日志可以帮助DBA或开发人员定位可能存在问题的SQL语句,从而进行优化. 如何开启 默认情况下,MySQL是不开启慢查询日志的.可以通过以下命令查看是否开启: mysql> SHOW VA ...
- 2016 piapiapia 数组绕过
0x00.感悟 写完这道题,我感觉到了扫源码的重要性.暑假复现的那些CVE,有的就是任意文件读取,有的是任意命令执行,这些应该都是通过代码审计,得到的漏洞.也就和我们的CTF差不多了. ...
- 派大星的烦恼MISC
挺有意思的杂项,python将二进制转图片的时候出现的图片不像二维码,想看题解的时候发现网上的大部分题解都是直接转发,更有意思了. 题目是派大星的烦恼,给了我们一张粉红图片,放进010editor里面 ...
- 5分钟入门MP4文件格式
写在前面 本文主要内容包括,什么是MP4.MP4文件的基本结构.Box的基本结构.常见且重要的box介绍.普通MP4与fMP4的区别.如何通过代码解析MP4文件 等. 写作背景:最近经常回答团队小伙伴 ...
- matplotlib 绘制多个图——两种方法
import numpy as np import matplotlib.pyplot as plt #创建自变量数bai组du x= np.linspace(0,2*np.pi,500) #创建函数 ...