Dubbo源码深度解析(六)

news2024/12/29 10:47:46

        上一篇博客《Dubbo源码深度解析(五)》主要讲:当服务消费方发起请求时,服务提供方是通过Netty服务接受请求并处理的,涉及到Netty相关使用及部分原理的讲解,以及最后又是如何将Invoker对象的执行结果返回给服务消费方的等。同时也讲了服务消费方的调用链路,这块还是比较复杂的。难点在于:要对Dubbo的SPI机制很熟悉,才知道具体是哪个接口实现类,并且还涉及到接口的包装类,以及对真正干活的Invoker的层层包装;如果对Dubbo理解和使用不够深入,可以借助debug,但如果没有真的理解Dubbo的SPI机制,是无法搞清楚为什么最终得到的Invoker对象会经过那么多层包装,而哪些包装的类又是怎么来的;还有就是Dubbo中很多地方,都是动态生成字节码对象,如果对字节码对象中接口的实现方法不熟悉的话,也很难继续看下去。

        这篇博客,将从服务消费方开始讲:被@DubboReference修饰的属性,是如何得到实现类并进行属性注入的,以及服务消费方最终是如何调用到服务提供方的。这里我就默认各位都认真看过了我前面写的五篇Dubbo源码解析博客。因此,我不会再像之前一样,讲得那么仔细了。

        这里可以考虑几个问题:

① 对被@DubboReference修饰的属性进行依赖注入,但服务消费方本地并没有定义接口实现类,因此,最终注入的对象,一定是代理对象;

② 既然是属性注入,多半是类似@Autowired注解,Dubbo一定会提供一个BeanPostProcessor,专门处理@DubboReference注解。这跟服务提供方处理@DubboService注解还不一样,因为它是创建Bean对象并注册到Spring容器中,因此需要借助于BeanFactoryPostProcessor或者ImportBeanDefinitionRegistrar(需借助@Import注解)来处理,因为这两种接口的执行时机是在BeanPostProcessor之前,准确地讲是生成BeanDefinition阶段(如包扫描、处理配置类),因为Spring最终是根据BeanDefinition来生成Bean的。根据我前面讲的内容可以知道,服务提供方就是通过ServiceAnnotationBeanPostProcessor来处理的(BeanFactoryPostProcessor的实现类)。

③ 服务消费方是如何跟服务提供方进行通讯的?毫无疑问也是使用的Netty,这里肯定也涉及到注册中心,因为服务消费方肯定是从注册中心拉取对应服务提供方的相关信息,如IP/PORT等,同时还要考虑服务提供方配置的变更,因此,服务消费方肯定会定期拉取服务消费方的信息并更新到自己本地。如果服务提供方有多个节点,那是不是还要考虑负载均衡呢?因此负载均衡这块肯定是在服务消费方发起调用的时候实现的,参考之前服务提供方之前的FilterNode链,可能服务消费方也会有类似的处理,当然这都只是猜测,具体可以看源码,来验证自己的猜测。

        开始今天的内容。关于上文说的处理@DubboReference注解,其实在之前的博客中有提到,就是在@EnableDubboConfig注解中做了处理,代码如下:

ba7d581205f849f0a6105796a7ac749f.png

03e94e9cad6345ff8359cdf93637b803.png

        DubboBeanUtils#registerCommonBeans()方法中,就往Spring容器中注册了一个后置处理器,即ReferenceAnnotationBeanPostProcessor,看名字也能知道,肯定是它处理的@DubboReference注解,注解看看这个后置处理器,先看看它的类继承结构,结果如下:

abe44df3dc564063b178818ccdb88f8d.png

        可以知道,ReferenceAnnotationBeanPostProcessor实现了InstantiationAwareBeanPostProcessor接口,看看该接口,代码如下:

e444c0e5c799447a94088b1de000fd45.png

        如果对Spring源码熟悉的话,就知道AutowiredAnnotationBeanPostProcessor实现了该接口,并且实现了postProcessProperties()方法,在该方法中处理被@Autowired注解修饰的类的成员属性或者成员方法,并实现属性的依赖注入。ReferenceAnnotationBeanPostProcessor也类似,但它实现的是postProcessPropertyValues()方法,但是根据Spring源码可知,这也没问题(建议通过实现postProcessProperties()方法实现依赖注入,因为postProcessPropertyValues()是一个过期的方法,后续Spring版本会弃用),Spring核心代码如下:

b9c22bd038054b069635eebe9d78d831.png

        看看 ReferenceAnnotationBeanPostProcessor的无参构造,代码如下:

17b7ca7af58d43ada7057ed3ff3ab30d.png

5fc8f81c6a8945c986c4c107276ba1d4.png

        再看看 ReferenceAnnotationBeanPostProcessor#postProcessPropertyValues()方法,代码如下:

284c75da85f34a7bb020323343a26802.png

        看看AbstractAnnotationBeanPostProcessor#findInjectionMetadata()方法,代码如下:

67411bfd6b404d89bb5f406c9606e27e.png

748d2383f5634177b8a5cb57e7ddabb1.png

        以字段被@DubboReference注解修饰为例 ,看看AbstractAnnotationBeanPostProcessor#findFieldAnnotationMetadata()方法,代码如下:

39e11c7279da413e8295362f5aef6f2f.png

        再回到ReferenceAnnotationBeanPostProcessor#postProcessPropertyValues()方法,该方法中,调用的是AbstractAnnotationBeanPostProcessor.AnnotatedInjectionMetadata#inject()方法,代码如下:

631d926c16a9423cbf7eab5567f14db7.png

5e5a2bb1f8d541e6908d48f6f66b4af3.png

8f1a341d212d4f8e896a18930185b10b.png

cb6807e26cc542369aa8f6a576fce4f6.png

        再看看AbstractAnnotationBeanPostProcessor#getInjectedObject()方法,看看是如何获取带注入的对象的,代码如下:

6baeaddc3421490491c37251439b6494.png

        继续看ReferenceAnnotationBeanPostProcessor#doGetInjectedBean()方法,代码如下:

b90284b2a22d4bccbc2f21c3a29286b7.png

        看ReferenceAnnotationBeanPostProcessor#buildReferenceBeanIfAbsent()方法,代码如下:

92dc6475667e4c7fb1950bd9ff3dc185.png

        AnnotatedInterfaceConfigBeanBuilder#build()方法,代码如下:

879d9610cca346458b2359de32578872.png

240fad329da74448a014b9b930601060.png

        再看看AnnotatedInterfaceConfigBeanBuilder#configureBean()方法,代码如下:

ae40b14470d540b98af43850763b086f.png

        需要要提一下的是,此时RegistryConfig对象(注册中心)没有设置给ReferenceBean对象,因为代码中会判断@是否给@DubboReference注解设置了registry属性(即注册中心名),具体代码如下:

f7795d7e18c64f06a4b8c9b61a5e194d.png

cab9f908b6ac44da924886d3c85e06e8.png

        再看看ReferenceBeanBuilder#postConfigureBean()方法,代码如下:

37bd8281882d4558a517117003e79af3.png

                既然是初始化方法,当然需要看看afterPropertiesSet()方法,代码如下:

        ReferenceAnnotationBeanPostProcessor#doGetInjectedBean()方法,再看ReferenceAnnotationBeanPostProcessor#registerReferenceBean()方法,代码如下:

        最后调用ReferenceBean.get()方法,代码如下:

        看看ReferenceBean#init()方法,代码如下:

        重点看ReferenceBean#createProxy()方法,代码如下:

        再看看ConfigValidationUtils#loadRegistries()方法,代码如下:

        根据前面将服务提供方,也能知道这里的REF_PROTOCOL属性实际上是Protocol$Adaptive对象,并且由于url中的协议是"registry",因此最终得到调用的是RegistryProtocol#refer()方法,代码如下:

        看看Cluster#getCluster()方法,代码如下:

        再看看org.apache.dubbo.rpc.cluster.Cluster文件的内容,结果如下:

        因此最终得到的Cluster为MockClusterWrapper对象,而MockClusterWrapper对象的cluster属性为FailoverCluster对象,断点验证,结果如下:

        重点看RegistryProtocol#doRefer()方法,代码如下:

        调用RegistryProtocol#getMigrationInvoker()方法,实际上是调用RegistryProtocol子类的getMigrationInvoker()方法,即:

        再看RegistryProtocol#interceptInvoker()方法,代码如下:

        看看MigrationRuleListener类,发现rawRule是在其无参构造中赋值的,并且结果为"INIT":

        再看MigrationRuleHandler#doMigrate()方法,代码如下:

        回到MigrationRuleHandler#doMigrate()方法,最终调用的是这里,结果如下:

        先看MigrationInvoker#refreshServiceDiscoveryInvoker()方法,代码如下:

        因此调用registryFactory.getRegistry()方法为,得到的Registry对象是ListenerRegistryWrapper对象,而ListenerRegistryWrapper对象的registry属性是ServiceDiscoveryRegistry,断点验证:

        继续看RegistryProtocol#doCreateInvoker()方法,代码如下:

        先看看ServiceDiscoveryRegistryDirectory#buildRouterChain()方法,代码如下:

        重点看看是如何获取RouterFactory对象的集合的,ExtensionLoader#getActivateExtension()方法的代码如下:

        再看看org.apache.dubbo.rpc.cluster.RouterFactory中的内容,结果为:

        最终遍历RouterFactory的集合,调用RouterFactory#getRouter()方法,得到Router对象,结果为:

        回到RegistryProtocol#doCreateInvoker()方法,ServiceDiscoveryRegistryDirectory#subscribe()方法,代码如下:

        看看ServiceDiscoveryRegistry#createServiceDiscovery()方法,代码如下:

        可知serviceDiscovery属性实际上是EventPublishingServiceDiscovery对象,而EventPublishingServiceDiscovery对象的serviceDiscovery属性是NacosServiceDiscovery对象。回到ServiceDiscoveryRegistry#subscribe()方法,代码如下:

        回到MigrationInvoker#migrateToServiceDiscoveryInvoker()方法,到这里为止,发现MigrationInvoker#refreshServiceDiscoveryInvoker()方法,其实没做什么,那重点应该是MigrationInvoker#refreshInterfaceInvoker()方法,代码如下:

        先看看父类的subscribe()方法,看了些啥,代码如下:

        再看NacosRegistry#doSubscribe()方法,代码如下:

        获取到服务名的列表之后,再调用NacosRegistry#doSubscribe()方法,订阅服务,代码如下:

        看看NacosRegistry#notifySubscriber()方法,代码如下:

        重点看看RegistryDirectory#toInvokers()方法,传入的URL对象当然是服务提供方的地址信息,代码如下:

        重点看protocol.refer()方法是如何生成Invoker对象的,实际上DynamicDirectory的protocol属性也是通过Setter方法设置的,与前面也讲过,生成DynamicDirectory对象的时候会进行属性的注入,因此最终protocol实际上是Protocol$Adaptive对象,也就是调用Protocol$Adaptive#refer()方法,代码如下:

        由前面可知,在Protocol$Adaptive#refer()方法中,由于传入的URL的协议是"dubbo",因此extension对象实际上是ProtocolFilterWrapper对象,而ProtocolFilterWrapper对象的protocol属性是ProtocolListenerWrapper对象,而ProtocolListenerWrapper对象的protocol属性是DubboProtocol对象。先看看ProtocolFilterWrapper#refer()方法,代码如下:

        断点看看最终得到的FilterNode链是什么样的,结果如下:

        回到ProtocolFilterWrapper#refer()方法,再调用ProtocolListenerWrapper#refer()方法,代码如下:

        最终调用的才是DubboProtocol#refer()方法,而refer()方法并不在DubboProtocol中实现,而是在其父类,因此最终调用的是AbstractProtocol#refer()方法,代码如下:

        这里的requestHandler跟之前服务提供是一样的,是共用的,继续看Exchangers#connect()方法,代码如下:

        剩下的内容将在最后一篇博客中讲解,敬请期待~

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

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

相关文章

LMDeploy 量化部署实践闯关任务

一、LMDeploy量化介绍 1.LMDeploy部署模型的优势 LMDeploy实现了高效的推理、可靠的量化、卓越的兼容性、便捷的服务以及有状态的推理。 相比于vllm具有领先的推理性能: LMDeploy也提供了大模型量化能力:主要包括KV Cache量化和模型权重量化。 LMDepl…

0813作业+梳理

一、实现虚拟机械臂控制 #include<myhead.h> #define SER_PORT 8888 //服务器端口号 #define SER_IP "192.168.0.126" //服务器ip地址 #define CLI_PORT 5555 //客户端端口号 #define CLI_IP "192.168.0.133" //客户端地址 int main(int argc, …

css - word-spacing 属性(指定段字之间的间距大小)属性定义及使用说明

介绍 CSS word-spacing 属性&#xff0c;用于指定段字之间的空间&#xff0c;例如&#xff1a; p {word-spacing:30px; }word-spacing属性增加或减少字与字之间的空白。 注意&#xff1a; 负值是允许的。 浏览器支持 表格中的数字表示支持该属性的第一个浏览器版本号。 属…

sqlserver 消息 9420,级别 16,状态 1,第 7 行

declare TerminalXml xml set TerminalXml(select * from TCK_TerminalInfo(nolock) for xml PATH) 执行时报9420错误,sqlserver 消息 9420,级别 16,状态 1,第 7 行 感觉非常奇怪,这个程序在很多客户多运行.当时以为数据库的配置不对.我重启了数据服务,还是没有解决…

智慧校园信息化服务平台、基于微信小程序的校园服务管理系统

摘 要 本文论述了智慧校园信息化服务平台的设计和实现&#xff0c;该网站从实际运用的角度出发&#xff0c;运用了计算机网站设计、数据库等相关知识&#xff0c;基于 ssm框架和Mysql数据库设计来实现的&#xff0c;网站主要包括用户注册、用户登录、查看教室信息、校园趣事…

mysql聚合函数和分组

我最近开了几个专栏&#xff0c;诚信互三&#xff01; > |||《算法专栏》&#xff1a;&#xff1a;刷题教程来自网站《代码随想录》。||| > |||《C专栏》&#xff1a;&#xff1a;记录我学习C的经历&#xff0c;看完你一定会有收获。||| > |||《Linux专栏》&#xff1…

Java并发类API -- Future和Callable

1.Future和Callable接口 Future 是一个表示异步计算结果的接口&#xff1b; 接口Callable与线程功能密不可分&#xff0c;但和Runnable的主要区别为&#xff1a; 1&#xff09;Callable接口的call&#xff08;&#xff09;方法可以有返回值&#xff0c;而Runnable接口的run&a…

java:IDEA修改java版本的几个不同的地方

文章目录 项目JDK设置&#xff08;Project SDK&#xff09;项目模块级JDK设置&#xff08;Module SDK&#xff09;IDE级别的JDK设置Maven配置文件编译器&#xff08;Java Compiler&#xff09;构建工具配置文件&#xff08;如build.gradle或pom.xml&#xff09;.idea/misc.xml文…

C语言(17)——单链表的应用

目录 1.单链表经典算法OJ题⽬ 1.1单链表相关经典算法OJ题1&#xff1a;移除链表元素 1.2单链表相关经典算法OJ题2&#xff1a;反转链表 1.3 单链表相关经典算法OJ题3&#xff1a;链表的中间节点 1.单链表经典算法OJ题⽬ 1.1单链表相关经典算法OJ题1&#xff1a;移除链表元素…

还有比这java状态压缩更通俗易懂的解释?

前言 Java中的状态压缩&#xff0c;或者说位运算状态压缩&#xff0c;是一种利用位操作&#xff08;如位与&、位或|、位异或^、位非~、左移<<、右移>>等&#xff09;来高效地存储和处理状态信息的技术。这种技术特别适用于那些状态空间不是很大&#xff0c;但…

微服务:网关路由和登录校验

续上篇&#xff1a;微服务&#xff1a;服务的注册与调用和OpenFiegn-CSDN博客 参考&#xff1a;黑马程序员之微服务 &#x1f4a5; 该系列属于【SpringBoot基础】专栏&#xff0c;如您需查看其他SpringBoot相关文章&#xff0c;请您点击左边的连接 目录 一、网关路由 1. 网关…

苹果注册海外账户|注册海外Apple ID|下载海外App

注册海外apple ID 背景 先前的一直使用的Apple ID注册地区是国内。 但是一些app因地区限制需要海外账户才能下载&#xff0c;因此需要使用海外apple ID。 因此需要注册海外apple ID&#xff08;如美国&#xff09;。 前言 绑定手机号 手机号可以和国内Apple ID一样没关系…

记一次SATA硬盘上电不转问题排查(最终查到和供电线有关)

一、背景 今年把旧的台式机换成了新的台式机&#xff0c;把硬盘挪到新电脑了。 二、问题 把硬盘挪到新电脑后&#xff0c;SSD可以正常使用&#xff0c;但是有个氦气机械盘始终不能用。 三、排查过程 对比实验 做了一些实验&#xff1a; 把硬盘接回旧电脑会正常转&#x…

【轨物洞见】找到技术创新的“真问题”

技术创新是用技术解决真问题&#xff0c;创造真价值。问题是需求&#xff0c;技术是供应&#xff0c;将需求与供应有效连接的能力&#xff0c;是创新力。技术创新的第一步在于找到“真问题”。在找问题之前&#xff0c;我们先了解一下问题是如何产生的。 问题就是期望值与现状之…

eNSP 华为交换机链路聚合

华为交换机链路聚合 链路聚合好处&#xff1a; 1、提高带宽 2、链路冗余 SW_2&#xff1a; <Huawei>sys [Huawei]sys SW_2 [SW_2]vlan batch 10 20 [SW_2]int g0/0/4 [SW_2-GigabitEthernet0/0/4]port link-type access [SW_2-GigabitEthernet0/0/4]port default vl…

ECCV2024|商汤发布3D面部动画系统UniTalker:通过统一模型扩展音频驱动的 3D 面部动画

商汤研究院最新发布了一个先进的3D面部动画系统UniTalker&#xff0c;可以从不同的音频领域生成逼真的面部动作&#xff0c;包括各种语言的清晰和嘈杂的声音、文本到语音生成的音频&#xff0c;甚至伴有背景音乐的嘈杂歌曲。 UniTalker 可以输出多个注释。对于具有新注释的数据…

理解线程id和简单封装原生线程库

一、理解线程id 首先我们要知道给用户提供的线程id不是内核里面LWP&#xff08;轻量级进程id&#xff09;&#xff0c;而是pthread库自己维护的一个唯一值。 我们理解为什么线程id不是内核里面LWP&#xff0c;因为用户没有权限使用内核里面的字段&#xff0c;那是专门给OS管理…

DOM破坏案例

目录 DOM破坏 编码问题 简单闭合""号使用onclick onclick函数 焦点事件 标签使用 限制数字字母 js匿名函数绕过 覆盖 DOM破坏 编码问题 urlcode可以被识别 %16进制 <textarea> 可以解码不能执行 <script>&#59</script> 没有实体编码…

基于asp.net的在线考试系统、基于c#的在线考试管理系统

摘 要 伴随着社会以及科学技术的发展&#xff0c;互联网已经渗透在人们的身边&#xff0c;网络慢慢的变成了人们的生活必不可少的一部分&#xff0c;紧接着网络飞速的发展&#xff0c;管理系统这一名词已不陌生&#xff0c;越来越多的学校、公司等机构都会定制一款属于自己个…

SpingBoot自动装配原理

一&#xff0c;什么是SpringBoot自动装配 springboot自动装配&#xff1a;一个springboot项目通过扫描类路径下存在的类和各种配置信息自动装配&#xff0c;生成对应的B哦按对象&#xff0c;然后将他们交给spring容器管理。 二、SpringBoot自动装配原理 2.1启动类注解SpringB…