Dubbo源码深度解析(五)

news2025/1/10 13:55:47

        上一篇博客主要讲服务提供方服务的发布,以及Netty是如何启动的,客户端发过来的请求,会经过哪些处理器,以及补充之前没讲完的SPI机制等等。这篇博客将会接着继续讲,在看这篇博客之前,请先看上一篇博客:《Dubbo源码深度解析(四)》。

        上一篇讲到了DubboProtocol的exporterMap属性的设置,代码如下:

a757f835289d44dfad07060e1bc02950.png

        存入的值究竟是什么对象?我觉得有必要彻底搞清楚。还是回到ServiceConfig#doExportUrlsFor1Protocol()方法,直接看关键的地方,代码如下:

246b5b84ac674451aa3d87330198bdad.png

98e5a7526b0440d5b22965e7f1e91bae.png

14613d040f154b50acbd53670be85221.png

        因此到这里为止,可以知道,调用Protocol$Adaptive#export()方法,传入的Invoker对象为RegistryProtocol.InvokerDelegate对象,而RegistryProtocol.InvokerDelegate对象invoker是DelegateProviderMetaDataInvoker对象,而DelegateProviderMetaDataInvoker对象的invoker属性为抽象类AbstractProxyInvoker的匿名实现类。最终调用的是AbstractProxyInvoker匿名实现类的 doInvoke()方法,而该方法调用的是 Wrapper实现类的invokeMethod()方法,打断点验证:

0aea713fbcf24392b68cae01bca4ce88.png

        到这里还没完,再看看Protocol$Adaptive#export()方法,代码如下:

abcd3b737eff47839535404bf2285539.png

        因此,看ProtocolFilterWrapper#export()方法,代码如下:

9668abdb537448e282b94fedbb5bcd7f.png

        重点看看ProtocolFilterWrapper#buildInvokerChain()方法,代码如下:

41b94452f6f549e4a441b0f15712f03d.png

af2b3734edaf48ecb1f74de84af1a238.png

        这里也涉及到SPI,不详细讲了,直接看org.apache.dubbo.rpc.Filter文件,内容如下:

3983f7d0b87e420281b66caf6b97e750.png

        就是获取这些实现类,只不过指定了group为"provider",举例,如下面这两种:

ec50c2614204475387dd5c132c753909.png

b9e7ba00804248c09574a1d6510c9335.png

        最终的结果为多个FilterNode对象嵌套,其中FilterNode对象的filter属性是Filter对象,next属性还是FilterNode对象,只不过它的filter属性变了。打断点,结果如下:

6bed1614cf9a4e6a96065ab2a358592d.png

        再调用ProtocolListenerWrapper#export()方法,代码如下:

f623b8a1058d4feb9742a5fed5e6111a.png

bb07e588b07647b9a034393408b5ee44.png

d95214a035e843b7bf7c1d3e8650c423.png

c48f57fe6a7541cb8d0c07d01234429b.png

        再看看RegistryProtocol#doLocalExport方法,代码如下:

ae2580aaf9ed4dddaff942d823099b0b.png

 

        回到ServiceConfig#doExportUrlsFor1Protocol()方法核心的地方法,代码如下:

345dc0ea550e4390ba073b5f6b462533.png

        可知返回的exporter为RegistryProtocol.DestroyableExporter对象,而RegistryProtocol.DestroyableExporter对象的exporter属性为RegistryProtocol.ExporterChangeableWrapper对象,而RegistryProtocol.ExporterChangeableWrapper对象的exporter属性为ListenerExporterWrapper对象,而ListenerExporterWrapper对象的exporter属性为DubboExporter对象,而DubboExporter对象的invoker属性为FilterNode对象链,而FilterNode的filter是前面读取的Filter对象的实现类(被@Activate注解修饰,切group的值为"provider"),FilterNode的next属性还是FilterNode对象,最后一个FilterNode对象的next属性为RegistryProtocol.InvokerDelegate对象而RegistryProtocol.InvokerDelegate对象的invoker属性为DelegateProviderMetaDataInvoker对象,而DelegateProviderMetaDataInvoker对象那个的invoker属性为AbstractProxyInvoker抽象类的匿名实现类。断点验证,结果如下:

0e30d5a01c2e4be08a9d75ac244cf432.png

        由断点结果来看,符合预期(层层嵌套....在看代码的时候,不做笔记,很容易漏掉)。当然,这里的RegistryProtocol.DestroyableExporter对象也不是那么重要,重要的是DubboExporter对象,因为最终获取的是DubboExporter#getInvoker()方法,获取Invoker对象,并执行Invoker#invoke()方法,直接看调用Invoker#invoke()方法的地方,代码如下:

2d58086cab704e91a214503f7f9f8bd8.png

        看看FilterNode#invoke()方法,代码如下:

e1b7fce3963240949e21d2bb74a6647c.png

4d9500dbc244415b97a9bbfb227a514a.png

        (注:每个Filter的实现类都提供了不同的功能,这里我就不讲了,有兴趣的自己看) 剩下要经过的的Filter实现类为以下这些,Filter#invoker()方法分别是为:

5f8e46a7988146f68c15f34dfa8cd933.png

a6e78c189c8146bb890548c1607ef2a5.png

b3d66ebdefaa4e2e962e279a0097d4aa.png

54d2bd2ea41c4e948b73c45050988962.png

115019d0b2594968a9df7921dca99feb.png

47179b33a3394b1b96a918090f9870ae.png

        最后一个Filter为ExceptionFilter,在ExceptionFilter#invoke()方法中,才会真正的调用Invoker#invoke()方法,代码如下:

fb89764fb51649c78454325acda57306.png

9f27de1febb141df8f716bd314dbbfb0.png

f7fc977d7d68473f9a0194d786069144.png

d332c8d8c91f4b39aa6ff8bbd437d27e.png

        看看AbstractProxyInvoker#invoke()方法,代码如下:

afabe787d4424611abd1ce13a639ac4c.png

        在JavassistProxyFactory中创建了AbstractProxyInvoker抽象类的匿名实现类,并且实现了AbstractProxyInvoker#doInvoke()方法,代码如下:

195e54a1478c44aa8e562045ec71a34d.png

        可知,最终是调用Wrapper实现类的invokeMethod()方法,只不过这个实现类是加载String动态动态生成的字节码对象,反射调用构造方法传创建的,这在前面提到过,这里不再赘述。但是我把Wrapper实现类.java文件弄出来了,它的invokeMethod()方法为:

474944dc629a4c3dbd3dd425f6083f9b.png

        最后得到结果,返回。顺便看看返回执行的逻辑是怎样的,还是回到NettyServerHandler#channelRead()方法,代码如下:

f117b62d185047988c2347bb23147071.png

        看看NettyChannel#getOrAddChannel()方法,代码如下:

51954f50bcdc4aa2a6a0b9f92b58f965.png

f39e916f900d4db08e625e81572aee31.png

6fbbee639c70454bb740393b2cd957e6.png

b67b5dddcb044449a7f38cc682bd1431.png

        看看HeaderExchangeChannel#getOrAddChannel()方法,看看返回的ExchangeChannel的实现类是哪个,代码如下:

cacb0e6f2736473186d1fdfff610e6a8.png

f3c721fb4aa24f4e96080cf7830d4385.png

2a698229c7574d4698a920cc5957dcf6.png

1344c9cd13d54f16bd0082f5d125f8b5.png

        断点看看,结果如下:

5f12464e4a44434aa4d66af71a343922.png

        上图调用Channel#writeAndFlush()方法的时候,并不会马上向客户端发消息,而是经过一系列ChannelHandler链,代码就不追了,对Netty有兴趣的,可以看我写的博客《Netty源码深度解析》。大概是这样:如果是服务端向客户端发消息的话,会经过一系列ChannelOutboundHandler对象。其实就是一个ChannelHandler链,从tail -> head (tail 和  head均为DefaultChannelPipeline类的成员属性,通过AbstractChannelHandlerContext的next和prev属性关联起来,组成双向链表,而ChannelOutboundHandler对象会先包装成DefaultChannelHandlerContext对象,它是AbstractChannelHandlerContext的子类,再添加到tail 和 head之间);如果是客户端给服务端发消息,则是head -> tail (调用的是一系列ChannelInboundHandler#channelRead()方法)。这里就是往DefaultChannelPipeline中添加ChannelOutboundHandler对象,如添加编码类,代码如下:

4844ad22aa2247718e276d2e1dba3d38.png

        之前聊到解码的时候没详细讲调用链路,这里我在讲编码的时候,讲细一点。这里的编码是通过InternalEncoder完成的,看看该类,代码如下:

e914bec1fe9c42a6bf3b87358faaf1f9.png

        看看InternalEncoder的继承图,结果如下:

2ffb77cf00384be98d08c88e10749534.png

        可知InternalEncoder实现了ChannelOutboundHandler,最终消息发出去之前,肯定会调用ChannelOutboundHandler#write()方法,具体的实现是在InternalEncoder的父类MessageToByteEncoder#write()方法,代码如下:

73900cbd93734d808395ab70a8d81ecf.png

8ef21766367f47a4b9d333352be8cedc.png

        再看看InternalEncoder#encode()方法,代码如下:

34fde6b9452b40ef98d4e76d3103b540.png

720f9af7eed74607aea35db671452086.png

3d4313a093484c968adb2112f7486df2.png

744d102079e641ff85145b742d52d3a3.png

c9b2069e9f4946cfae32a432d9359c0c.png

        到这里为止,我觉得应该是把服务提供方目标方法的调用,以及执行结果是如何响应给客户端,讲解得非常清楚了。同时,还顺便提了一点Netty相关的代码,希望我讲的,能给各位带来一点启发。

        再回到RegistryProtocol#export()方法,还会干一件事,也就是将服务提供方的ip/port等信息注册到注册中心,核心代码如下:

d949729ea3bf4c1e89969e0f6c59a7bf.png

        先看看RegistryProtocol#getRegistry()方法,代码如下:

917858a2656f454ebde01e3be6dd32da.png

5343c5dc15bd4bfa822eabd67ae2e059.png

        由于RegistryProtocol是通过Dubbo的SPI机制加载创建的,之前也提到过,通过Dubbo的SPI创建对象的时候,会找出所有没有被@DisableInject修饰的Setter方法,然后从Spring容器中或者Dubbo的SPI找对应的类的对象,通过反射调用Setter方法进行赋值,很显然RegistryFactory对象在Spring容器中是没有,因此是通过进行Dubbo的SPI的,看看RegistryFactory接口,代码如下:

f2587c73cfb2459dbdf4942cbacdbf54.png

743cfaa7349f4461b22cb13c259afaa9.png

        由于依赖了Nacos相关的依赖,因此还需要在dubbo-registry-nacos模块下找org.apache.dubbo.registry.RegistryFactory文件,结果如下:

c1190cf82f274e50903d5cbb8b7a283d.png

        因此可以知道,最终的结果RegistryProtocol的registryFactory属性为RegistryFactoryWrapper对象,而RegistryFactoryWrapper对象的registryFactory属性为NacosRegistryFactory对象,断点验证,结果如下:

13d93d928bfa446188ef81df5839a21b.png

        因此,直接看RegistryFactoryWrapper#getRegistry()方法即可,代码如下:

57209635710b4f19ba86c8f1321ad994.png

        再看AbstractRegistryFactory#getRegistry()方法,代码如下:

dabb9f21e2e24a52bd028fa0ac918741.png

7bdf0aa684f3485984aaa6dc2fdad21d.png

        顺便看看NacosRegistry的类继承结构,结果如下:

86916a30121243c18c6dde1cb681006b.png

7dcce161301243fca140ee3cac326345.png

        由此可知,注册服务的核心当然是调用ListenerRegistryWrapper#register()方法,代码如下:

c6735337613a4545ad9fd82f98cefbc0.png

32ad9fa58a2540d5a79edaf29edfbb01.png

e4ed085c017c4307959f67aaa5df1556.png

ed109d52912440a6ab0fb53ed441eeee.png

42bbaad4f21042f6867b04f7783668bc.png

e1c0387088ff49a39fb387492f0cf2b4.png

b99b352a64084654bb03e106db78f9b5.png

ec90304fb6374c3880d7c8b9186570df.png

        最终在Nacos上展示的元数据信息也是上面这些,看看Nacos控制台,结果如下:

f92ef81f6df24644bafe1e48dbc696cc.png

        这就是Dubbo往Nacos注册实例的大概逻辑,至于Nacos是如何处理注册请求,后续我会专门写一篇博客讲解。以上,就是本篇博客的全部内容。下一篇,我将会讲服务消费方相关的逻辑,讲解就不会很仔细,因为很多东西在本篇博客以及之前的博客,讲得很清楚了。如果还有疑问,请留言,谢谢!

 

 

 

 

 

 

 

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2040626.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

VTK—vtkStructuredGrid提取维度面数据

1.在VTK自带的vtkStructuredGrid数据文件combq.bin和combxyz.bin 2.文件读取代码如下: //读取数据文件Create(vtkMultiBlockPLOT3DReader, reader);reader->SetXYZFileName("G:/Temp/vtkTest/combxyz.bin");reader->SetQFileName("G:/Temp/v…

Vitis AI 基本认知(训练过程)

目录 1. 目的 2. TensorBoard 2.1 In TensorFlow 2.2.1 安装 TensorBoard 2.2.2 导入必要的库 2.2.3 初始化 2.2.4 记录数据 2.2.5 启动 TensorBoard 2.2.6 刷新间隔 2.2 In PyTorch 3. 训练周期 Epoch 3.1 Epoch 3.2 Batch 3.3 Iteration 4. 总结 1. 目的 介绍…

传奇游戏发布渠道

传奇游戏发布渠道 回答:游戏发布平台|手机游戏发布平台 传奇游戏发布渠道作为游戏开发商直接控制的信息传播途径,其安全性自然有着较高的保障。首先,渠道通常会采用先进的加密技术和安全协议来保护数据传输过程中的安全,防止信息…

Centos 7 升级GCC时遇到 mirrorlist.centos.org; Unknown error“

问题描述 在执行如下操作的时候, yum install devtoolset-9-gcc devtoolset-9-gcc-c devtoolset-9-binutils 出现: 14: curl#6 - "Could not resolve host: mirrorlist.centos.org; Unknown error" 网上搜索了一下,原因是 mir…

redis集合若干记录

无序集合 redis通常使用字典结构保存集合数据,字典健存储集合元素,字典值为空。如果一个集合全为整数,使用字典就有点浪费了,redis使用intset保存。 插入元素到intset中 获取插入元素编码,如果插入元素编码级别高于int…

Chapter 36 PySpark数据计算

欢迎大家订阅【Python从入门到精通】专栏,一起探索Python的无限可能! 文章目录 前言一、map算子二、flatMap算子三、reduceByKey算子四、filter算子五、distinct算子六、sortBy算子七、综合案例 前言 在大数据处理的时代,Apache Spark以其高…

猫头虎 分享:Python库 Pygame 的简介、安装、用法详解入门教程

猫头虎 分享:Python库 Pygame 的简介、安装、用法详解入门教程 😺 摘要:今天,猫头虎将带大家深入了解Python中常用的Pygame库。Pygame是开发2D游戏和多媒体应用的首选工具之一。在本文中,我们将从安装Pygame、了解Pyg…

深入学习零拷贝

在学习中遇到了一个问题就是什么是零拷贝,因此学习之后以此来记录一下。 零拷贝、直接I/O、异步I/O等,优化的目的就是为了提高系统的吞吐量,减少访问磁盘次数。访问磁盘的速度会比读写内存会慢十倍以上。因此就需要提高它的读写速度。 什么…

uniapp自定义请求头信息header

添加请求头:uniapp自定义请求头信息header 代码

Java性能优化之并发编程:深入解析与实战技巧

在Java应用程序的性能优化中,并发编程是一个关键领域。通过合理使用并发编程技术,可以充分利用多核CPU的计算能力,提高程序的执行效率。本文将深入探讨Java并发编程的优化策略,并提供一些实用的代码示例和实战技巧。 1. 线程与同…

Linux Day1 系统编程和文件操作

系统编程内容 文件I/O (输入/输出): 1)使用标准库函数如fopen, fclose, fread, fwrite, fgetc, fputc, fgets, fprintf, fscanf等进行文件操作。 2)使用open, close, read, write等系统调用来实现底层文件操作。 进程管理: 1)使用fork, e…

力扣 3152. 特殊数字Ⅱ

题目描述 queries二维数组是nums数组待判断的索引区间(左闭右闭)。需要判断每个索引区间中的nums相邻元素奇偶性是否不同,如果都不同则该索引区间的搜索结果为True,否则为False。 暴力推演:也是我最开始的思路 遍历q…

招聘技术研发类岗位,HR会考察候选人哪些方面?

技术研发团队在当下的企业视为发展的核心,对于企业长期发展和市场竞争力至关重要,作为HR,如何选拔技术研发岗位的人才,也是难度较大的工作。 作为应聘者来说,同样应该主动去了解HR是如何考察技术性人才,以…

使用docker部署rabbitmq集群

部署环境准备 192.168.81.128 rabbitmq-1 192.168.81.129 rabbitmq-2 192.168.81.130 rabbitmq-3 首先创建挂载目录(三个节点都创建) systemctl stop firewalld && setenforce 0 关闭防火墙和selinux mkdir /data/rabbitmq -p cd /da…

加和分数、训练、测试

一、加和所有alignment的分数 1、路线图中 2、l_i只与token有关,有一个专门训练的网络;h_i变化只与null有关 3、distribution生成的概率不受路径影响,只要到达位置概率就是一样的 4、计算alignment分数的总和 (1)αi…

Word转html并移植到web项目

1.打开对应word文件 建议使用web视图查看文档 这样可以提前预览转转成html样式 2.如果有图片修改图片大小及格式 在web视图下,把图片调制适当大小,不然导出的html可能图片较小 3.点击另存为 4.选择网页格式,同时将后缀修改为html(默认是h…

从springBoot框架服务器上下载文件 自定义一个启动器

在springboot框架中下载服务器存储的图片: 1)springboot默认访问放行的目录只有static,在static目录下存放图片资源 2)编译后的static目录中有一个1.png 2.5)编写控制器: Controller //RequestMapping("/upload&q…

如何在 Linux 内核中高效使用链表:原理与实践

文章目录 前言一、Linux内核链表源码分析1.链表的初始化1. 静态初始化宏 LIST_HEAD_INIT(name)宏 LIST_HEAD(name) 2. 动态初始化函数 INIT_LIST_HEAD(struct list_head *list) 对比总结2.链表的添加list_add 函数的定义函数参数内部实现__list_add 函数 list_add 的功能总结使…

Java Spring|day4.SpringCloud

SpringCloud 定义 springcloud是分布式微服务架构的一站式解决方案,是多种微服务架构落地技术的集合体,俗称微服务全家桶。实现的功能有服务注册与发现,服务调用,服务熔断,负载均衡,服务降级,…

Excel公式合并同类项

Excel公式合并同类项 1、新建表,用公式引用要处理的数据,快速选中表格复制公式2、 合并同类项,复制数据,删除重复项3、 sumif()合并同类项4、vlookup()复制同类项 1、新建表,用公式引用要处理的数据,快速选…