Dubbo源码深度解析(中)

news2025/1/23 3:03:56

        接着《Dubbo源码深度解析(上)》继续讲,上篇博客主要讲Dubbo提供的三个注解的作用,即:@EnableDubbo、@DubboComponentScan、@EnableDubboConfig。其中后两个注解是在@EnableDubbo上的,因此在启动类上加上@EnableDubbo注解,等同于加上@DubboComponentScan注解和@EnableDubboConfig注解。并且还讲到了Dubbo的包扫描,以及Dubbo整合SpringBoot后,是如何将配置文件中的dubbo.xxx属性绑定到Dubbo的配置类上的。         

       本篇博文将主要讲@DubboService注解的原理以及Dubbo的SPI机制,其实@DubboService注解的原理,在上篇博文中已经讲过了。这里回顾一下:核心是依赖ServiceClassPostProcessor或者ServiceAnnotationBeanPostProcessor,其中,ServiceAnnotationBeanPostProcessor继承自ServiceClassPostProcessor(非抽象类),都是实现了BeanDefinitionRegistryPostProcessor接口。因此会实现BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry()方法,在该方法中,会扫描指定包下所有被@DubboService注解修饰的类,并往Spring容器中注册一个BeanDefinition对象,其中BeanDefinition的beanClass的值为ServiceBean,最终Spring实例化的就是beanClass返回的类型,即ServiceBean,上篇博文最后通过AbstractApplicationContext#getBeansOfType(ServiceBean.class)方法,能获取到一个Bean对象,这跟预期是一致的。

        其实在生成ServiceBean的时候,会用到Dubbo的配置类,在子模块service-provider中,我通过DubboConfiguration,定义了三个配置类,分别是:ApplicationConfig、ProtocolConfig和RegistryConfig,代码如下:

package com.szl.config;

import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.config.ProtocolConfig;
import org.apache.dubbo.config.RegistryConfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * Dubbo配置类,以下几个类实际上都是 AbstractConfig类的子类,在Spring容器初始化这几个Bean的时候,
 * 由于父类中的 AbstractConfig#addIntoConfigManager()方法是被@PostConstruct注解所修饰的,因此,
 *  该方法会调用被自动调用。
 * </p>
 */
@Configuration
public class DubboConfiguration {

    @Bean
    public ApplicationConfig applicationConfig() {
        ApplicationConfig applicationConfig = new ApplicationConfig();
        applicationConfig.setName("service-provider");
        return applicationConfig;
    }

    @Bean
    public ProtocolConfig protocolConfig() {
        ProtocolConfig protocolConfig = new ProtocolConfig();
        protocolConfig.setPort(20880);
        protocolConfig.setName("dubbo");
        protocolConfig.setServer("netty4");
        return protocolConfig;
    }

    @Bean
    public RegistryConfig registryConfig() {
        RegistryConfig registryConfig = new RegistryConfig();
        registryConfig.setAddress("nacos://127.0.0.1:8848");
        return registryConfig;
    }
}

        我在注释中写的很详细,这三个配置类都有一个相同的父类,即AbstractConfig,并且在其父类中,有一个方法是被@PostConstruct注解修饰的。最后在初始化这个Bean的时候会,Spring会自动调用被这个注解修饰的方法,看看AbstractConfig#addIntoConfigManager()方法,代码如下:

857bc0212ef344728250193657e641f0.png

        在该方法中,调用了ApplicationModel.getConfigManager()方法,得到ConfigManager对象,即配置管理器,看看是怎么获取的,代码如下:

70ce246bd2294d33a5a2584ffbcbe4f6.png

        又需要看看ApplicationModel的LOADER属性是怎么获取的,其实就是看ExtensionLoader#getExtensionLoader(FrameworkExt.class),代码如下:

d2f0817a735c4ba1ba12f9a807835018.png

        可知,ExtensionLoader有一个EXTENSION_LOADERS,用作缓存,如果通过传入的Class获取不到,则创建一个对象ExtensionLoader,调用其有参构造方法,传入Class,在这里,Class也就是FrameworkExt.class,并将创建好的ExtensionLoader放入EXTENSION_LOADERS,方便下次直接获取。具体看看ExtensionLoader的有参构造方法,代码如下: b18d97a90e31407d92e9cab44ab9ebb0.png

        因此又会调用到ExtensionLoader#getExtensionLoader()方法,也就是刚刚前面讲的。只不过此时的type属性为ExtensionFactory.class,然后还是先从EXTENSION_LOADERS中获取,假设是第一次,也获取不到,因此会调用ExtensionLoader的有参构造方法。再次回到上图的方法中,但是不同的是,这次由于type是等于ExtensionFactory.class,则此时objectFactory为null,并且还要调用ExtensionFactory#getAdaptiveExtension()方法,看看该方法,代码如下:

a5916d232fcc43efbd5bdca3dec35ace.png

b1484edda3894dd584c2862199f80b9f.png

97a9425a38294363877d2cdd1aaa58a6.png

c605db50c88b49258d4c017e4839f4fe.png

994842aaddac46dea3f3db80d1b77ff2.png

        看看FrameworkExt,代码如下:

86ff404c89cf4e8fa8d262eb6212129a.png

        FrameworkExt确实被@SPI注解修饰了,只不过value值为"",因此ExtensionLoader#cacheDefaultExtensionName()方法中,最终设置的cachedDefaultName属性为null。回到ExtensionLoader#loadExtensionClasses(),紧接着就是调用ExtensionLoader#loadDirectory()方法,不过在此之前按,需要先看看ExtensionLoader的strategies属性,代码如下:

4983573706954382b8bf7040d672229d.png

8f92208c9477409aabd29518ea3df183.png

        调用ServiceLoader#load()方法,传入LoadingStrategy.class,代码如下:

2716ad08d90046918932a784912fea32.png

41d2825226db4fad9191b72712f46ec5.png

        通过Java的stream流,遍历调用,new LoadingStrategy接口的实现类,得到对象,放入

ExtensionLoader的strategies属性中,那LoadingStrategy接口的实现类是如何获取的呢?其实ServiceLoader是Java的 rt.jar提供的类,属于Java自身提供的SPI机制,它会从classpath目录下的META-INF/services下,找一个名为LoadingStrategy接口全限定名的文件,即org.apache.dubbo.common.extension.LoadingStrategy,刚好找到了,代码如下:

ec014ab9fa3b4dc1944671947927411b.png

        因此最终实例化的就是这三个实现类,断点验证一下:

5eb60f45e36d49a7add3506e6999d06b.png

        因此,我们如果想实现SPI机制,可以借助于Java自带的ServiceLoader实现,如果想实现更复杂的SPI机制,可以借鉴Dubbo,因为讲到这里,还不全是Dubbo的SPI机制。回到ExtensionLoader#loadExtensionClasses()方法,代码如下:

957400923afc45fe92698ad2de727dd4.png

        这里涉及到加载的路径,看看LoadingStrategy接口的三个实现类,代码如下:

be00706224884164bfc4cab3841d5ac0.png

e8156c26cccb4f0e80083f26d08baa54.png

fa9a60c7a48c44e49099502f15d5d7d2.png

        可以知道,扫描的路径分别是:META-INF/dubbo/internal/ 或 META-INF/dubbo/ 或 META-INF/services/。ok,再看看ExtensionLoader#loadDirectory()方法,该方法是关键,代码如下:

98687fe4938c406985339df06ad3a9ca.png

        可以知道,最终是根据前面的几个路径下,并且拼接接口的全限定名,得到文件路径,通过ClassLoader去找文件,得到urls并进行遍历,最终调用到ExtensionLoader#loadResource()方法,进行真正的加载,看看该方法,代码如下:

fd1d9afdf58a47269e162b2bcd9b51a0.png

ff770a58147b40e6b070534951e29da9.png

        当然,现在看的是dubbo-common下的文件,在其他模块下,可能也存在org.apache.dubbo.common.extension.ExtensionFactory,这里就不去找了。再看看ExtensionLoader#loadClass()方法,代码如下:

38e7794ab05f4811b517b0072e458a10.png

        顺便看看ExtensionFactory接口的实现类,如下:

722d82c7594e4a219fff2cad5c79314d.png

95debbb819674a4697e7aa6c42f689de.png

        由此可知,这两个实现类中,只有AdaptiveExtensionFactory是被@Adaptive注解修饰的。再回到ExtensionLoader#getAdaptiveExtensionClass()方法中,代码如下:

ebe0be33eb95485cb630545e80358786.png

        因此会返回AdaptiveExtensionFactory.class,再回到ExtensionLoader#createAdaptiveExtension()方法中,代码如下:

c586899fa0484a1caf1a86d1f0a0ce5e.png

        调用ExtensionLoader#injectExtension()方法,进行Dubbo的依赖注入,代码如下:
d7081a6eb2604d58b667e138bab34735.png

fd5e65b2e6b845668bacffed416a100d.png

        由于AdaptiveExtensionFactory中没有Setter方法,因此无需进行依赖注入,但是它的无参构造中,做了一些初始化处理,无参构造为:

873df9b2ce104ce299c58c77c18102c8.png

        需要看看ExtensionLoader#getSupportedExtensions()方法,代码如下:

951a665034ac43cdb7e2934065b2d58f.png

dfbac50821fe4cd5a03d6f2053971ed5.png

        这也是刚刚讲过的方法,最终只需要看ExtensionLoader#loadClass()方法,代码如下:

13fa0acbeaa743f58b883bc26d3d75dd.png

        因此AdaptiveExtensionFactory不会被放入AdaptiveExtensionFactory的factories属性中,而是ExtensionFactory接口的其他实现,分别在两个文件中,如下:

747fcc06d8f746789d44dca461d5dc98.png

4b9a73f5a50349fd8bfdc219a28741cf.png

        打断点验证一下,结果如下:

c7fa5b3c10f445e6bd1577907f3828eb.png

        这两个对象,即:SpringExtensionFactory和SpiExtensionFactory,将会是实现Dubbo的依赖注入的关键,看名字也能知道:前一个是通过Spring容器寻找依赖的对象;而后一个则是通过Dubbo的SPI机制寻找依赖的对象。当然,寻找依赖并不是直接通过这两个类来寻找的,而是通过AdaptiveExtensionFactory#getExtension()方法来寻找的,代码如下:

b5a1c3d57d8549e7b704d9a52d7c4b7c.png

f29424aeb9a6457dadb67c7032356183.png

        而SpringExtensionFactory的CONTEXTS属性,则是在ReferenceBean中或者ServiceBean总进行赋值的:

2224f12c13c144b4999bdd97d6071fa5.png

e651f132917242ea8d2f00df4b866ec8.png

     到这里为止,Dubbo的SPI机制算是讲清楚了,想彻底搞懂这块,建议自己跟着源码,再配合博客一起看。OK,回到ApplicationModel#getConfigManager()方法传入的是 name,代码如下:

70b38bc08f9b41d698eb73d874de38dd.png

d0123574153841cdb853322fdc363d9c.png

2536f97635854fbd9091e5c808040837.png

        加载FrameworkExt接口实现类的逻辑跟ExtensionFactory类似,也是在classpath下,找文教名叫org.apache.dubbo.common.context.FrameworkExt的文件,如下:

c3a207ef253e457eb3450d46ecb5c8dd.png

        最终得到这三个实现类的Class的集合,打断点结果如下:

faff763e654d43018382f321b330c355.png

        回到ExtensionLoader#createExtension()方法中,代码如下:

abc0328e72e04d11880e430014cd039d.png
        通过name,即"config"可以获取到Class,即ConfigManager.class,然后通过EXTENSION_INSTANCES获取,由于是第一次,当然获取为空,因此进入if代码块,通过反射,实例化ConfigManager,并放入EXTENSION_INSTANCES中,其中key为"config",然后调用ExtensionLoader#injectExtension()方法,实际上就是调用AdaptiveExtensionFactory#getExtension()方法,寻找依赖,这块上文讲的很清楚了,这里不过多赘述。而ConfigManager有如下的Setter方法,代码如下:

77f19d7a75ef48bd8fb17e383489c85a.png

        这些Setter方法也不一定都会被调用,只有获取到了相应的对象,才会通过反射,进行赋值。需要说的是,给Setter方法赋值是不是一定通过Dubbo的的提供的依赖注入进行的赋值?这也不一定,可能在其他地方进行赋值,只不过Dubbo提供了这种方式。比如我打断点的时候,发现就不是通过Dubbo的方式进行的依赖注入,断点如下:

b79fc24f55c948ae99715b6a99f4fc5b.png

        OK,至于是哪种方式调用Setter方法进行赋值的,这里不过多纠结,这并非核心流程。回到最开始的地方,即AbstractConfig#addIntoConfigManager()方法,由于该方法被@PostConstruct注解修饰,因此会自动被调用,断点如下:

288cb1491b064e12ace13778887f8d0f.png

bb77f11bb67349249d8a5bffa7b20738.png

        在上篇博客中,讲到了往Spring容器中,添加了两个监听器,分别是:DubboBootstrapApplicationListener、DubboLifecycleComponentApplicationListener,这两个监听器会监听两种事件,分别是ContextRefreshedEvent、ContextClosedEvent。先看看DubboBootstrapApplicationListener,代码如下:

1ef79ab93c4a4d90bc886dda601cc019.png

2221e074073a41b5be4a958a09e2c1fb.png

        先看看DubboBootstrap#initialize()方法,代码如下:

f63335f6cfc943df8ba8fab43a929713.png

        在该方法中,又调用了七个方法,分别看看,代码如下:

① DubboBootstrap#initFrameworkExts()方法:

a93c371872c048deab0f70b9203b7bbb.png

f80d1d93225545e9b430f880d8328dd1.png

        处理默认的配置中心,这里没有配置,因此均为空:

72c1c6dd41544939809dde13d5a99ad5.png

② DubboBootstrap#startConfigCenter()方法:

 

 

 

 

 

 

 

 

 

 

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

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

相关文章

数据分析模型:洞察数据背后的奥秘

数据分析模型&#xff1a;洞察数据背后的奥秘 事件分析模型通过对用户行为事件的深入剖析&#xff0c;能够了解用户在产品上的具体行为&#xff0c;为产品优化和营销策略提供了有力依据。漏斗分析模型则专注于用户转化流程&#xff0c;帮助找出业务流程中的薄弱环节&#xff0c…

[Meachines] [Easy] Sense PFSense防火墙RCE

信息收集 IP AddressOpening Ports10.10.10.60TCP:80,443 $ nmap -p- 10.10.10.60 --min-rate 1000 -sC -sV PORT STATE SERVICE VERSION 80/tcp open http lighttpd 1.4.35 |_http-title: Did not follow redirect to https://10.10.10.60/ |_http-server-header…

zlm-openRtpServer使用

http://127.0.0.1/index/api/openRtpServer?port50253&tcp_mode1&stream_idtest123&secretLSAjqhPDsC0IcL27hI6U8heTxgnTlZlW使用getRtpInfo查看这个流的情况&#xff0c;注意只有这个端口在接受流的时候&#xff0c;才会返回数据 http://127.0.0.1/index/api/ge…

【文献阅读】TAM: Topology-Aware Margin Loss for Class-Imbalanced Node Classification

Abstract 在类别不平衡的图数据中学习无偏的节点表示是一项具有挑战性的任务&#xff0c;因为相邻节点之间存在相互作用。现有研究的共同点在于&#xff0c;它们根据少数类节点的总体数量“作为一个整体”进行补偿&#xff08;忽略了图中的节点连接&#xff09;&#xff0c;这…

理解<共轭复数乘法,复数乘法,复数除法>

目录 复数乘法共轭复数乘法的理解复数除法 复数乘法 共轭复数乘法的理解 共轭复数乘法&#xff1a;可以理解为滤波&#xff0c;计算两序列的相关。即一个序列固定&#xff0c;另一个序列进行滑动计算该点的累积和。因为傅里叶变换的卷积定理&#xff0c;时域卷积对于频域相乘。…

web漏洞-知识点详解

先放张图&#xff01;右边漏洞比左边漏洞更重要&#xff01;右边漏洞更多&#xff01;重点讲述右边漏洞&#xff01; sql注入 危害情况&#xff1a;可以获取网站数据库中的数据&#xff0c;如果数据中有敏感信息&#xff0c;比如管理员账号密码&#xff0c;就可以登录后台 漏…

学生管理系统之更新和删除、筛选

学生管理系统之更新和删除 建立新的窗口 添加组件 进行布局 使用Widget把二个放在一块,作为一列,然后全选进行栅格布局,最后添加弹簧进行微调。 编写增加的槽函数 在主函数中调用对话框

CC++内存魔术:掌控无形资源

hello,uu们,今天呢我们来详细讲解C&C的内存管理,好啦,废话不多讲,开干 1:C/C内存分布 2:C语言中动态内存管理方式:malloc/calloc/realloc/free 3:C内存管理方式 3.1:new/delete操作内置类型 3.1.1:代码1 3.1.2:代码2 3.2:new和delete操作自定义类型 3.2.1:C语言创建…

初识增强现实(AR)

初识增强现实&#xff08;AR&#xff09; 笔记来源&#xff1a; 1.2023年中国增强现实&#xff08;AR&#xff09;行业研究报告 2.wiki/Augmented reality 3.In-Depth Review of Augmented Reality: Tracking Technologies, Development Tools, AR Displays, Collaborative AR…

js第二天

比较运算符 ==左右两边值是否相等 ===左右两边值和类型是否全相等 !==左右两边是否类型和值全不相等 undefin === null NaN === NaN(错误,NaN不等于任何值) =单等是赋值 ==双等是判断 ===三等是全等,开发中判断是否相等,一般用全等。 console.log 先比较a和a,相…

电脑添加虚拟网卡与ensp互联,互访

一、按照过程 1、打开设备管理器 2、点击网络适配器&#xff0c;点击左上角操作&#xff0c;点击“添加过时硬件” 3、下一页 4、选择“安装我手动从列表选择的硬件”&#xff0c;下一页 5、下拉&#xff0c;选择“网络适配器”&#xff0c;下一页 6、厂商选择“Microsoft”&…

基于MFC对话框吸管实验

1.新建项目 2.将bmp图像放入res文件下 3.导入bmp文件到bitmap 4.在dlg中添加picture控件&#xff0c;修改控件属性&#xff08;Type Image属性&#xff09;&#xff0c;把bitmap资源添加到控件中 5.重写鼠标单击鼠标中键响应事件 6.主要源代码 void CMFC吸管Dlg::OnMButtonDow…

使用Springboot + netty 打造聊天服务之Nacos集群问题记录

目录 1、前言1.1、方法一1.2、方法二 2、方案二实战2.1、在netty服务里加上ws连接、中断事件2.2、在netty服务里加上消息服务 4、总结 使用Springboot netty 打造聊天服务系列文章 第一章 初始搭建工程 第二章 Nacos集群问题记录 1、前言 在使用Springboot Nacos Netty(Web…

SAPUI5基础知识23 - 模型的种类(小结)

1. 背景 在前序的学习中&#xff0c;我们学习了SAPUI5的MVC架构中的各个知识点&#xff0c;包括视图的设计&#xff0c;控制器的设计&#xff0c;以及模型的使用。 在企业级应用程序中&#xff0c;对于数据的处理的需求是很大的&#xff0c;在学习更复杂的数据绑定方式之前&a…

Pr2024苹果(mac)版剪辑软件安装下载(附下载链接)

Adobe Premiere Pro 2024&#xff08;简称PR 2024&#xff09;是一款由Adobe公司开发的专业视频编辑软件&#xff0c;被广泛应用于电影、电视、广告和社交媒体视频的制作。以下是对PR 2024的详细简介&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1rN-3kB3KQgn0JswDa…

《学会 SpringMVC 系列 · 剖析初始化》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; CSDN入驻不久&#xff0c;希望大家多多支持&#xff0c;后续会继续提升文章质量&#xff0c;绝不滥竽充数…

win10系统资源监视器磁盘蓝黄线、活动时间代表什么意思?

win10系统资源监视器磁盘蓝黄线、活动时间代表什么意思&#xff1f;在win10系统中磁盘在运行工程中会有不同的数据信息显示&#xff0c;如下图所示&#xff0c;10兆字节/秒、磁盘中蓝线和黄线、活动时间都达标什么意思呢&#xff1f;本文中winwin7小编给大家分享介绍下win10系统…

Python 爬虫入门(六):urllib库的使用方法

Python 爬虫入门&#xff08;六&#xff09;&#xff1a;urllib库的使用方法 前言1. urllib 概述2. urllib.request 模块2.1 发送GET请求2.2 发送POST请求2.3 添加headers2.4 处理异常 3. urllib.error 模块4. urllib.parse 模块4.1 URL解析4.2 URL编码和解码4.3 拼接URL 5. ur…

MySQL主从复制原理及实现教程

MySQL主从复制是一种数据复制技术&#xff0c;通过建立主服务器&#xff08;Master&#xff09;与从服务器&#xff08;Slave&#xff09;之间的数据同步&#xff0c;实现数据的备份、负载均衡和高可用性。 主从复制原理 MySQL binlog(binary log 即二进制日志文件) 主要记录…

Python 如何创建和操作矩阵?

在Python中&#xff0c;矩阵是二维数组的一种常见表示方式&#xff0c;特别是在数学和科学计算领域。矩阵用于表示和操作多维数据&#xff0c;包括数据分析、图像处理、机器学习、以及物理模拟等诸多领域。Python的NumPy库是处理矩阵的主要工具之一。NumPy提供了高效的多维数组…