Dubbo源码深度解析(二)
接着《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.class,最终Spring实例化的就是beanClass返回的类型,即ServiceBean,上篇博文最后通过AbstractApplicationContext#getBeansOfType(ServiceBean.class)方法,能获取到一个Bean对象,这跟预期是一致的。
其实在生成ServiceBean的时候,会用到Dubbo的配置类,在子模块service-provider中,我通过DubboConfiguration,定义了四个配置类,分别是:ApplicationConfig、ProtocolConfig、RegistryConfig和ConfigCenterConfig ,代码如下:
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 {@Beanpublic ApplicationConfig applicationConfig() {ApplicationConfig applicationConfig = new ApplicationConfig();applicationConfig.setName("service-provider");return applicationConfig;}@Beanpublic ProtocolConfig protocolConfig() {ProtocolConfig protocolConfig = new ProtocolConfig();protocolConfig.setPort(20880);protocolConfig.setName("dubbo");protocolConfig.setServer("netty4");return protocolConfig;}@Beanpublic RegistryConfig registryConfig() {RegistryConfig registryConfig = new RegistryConfig();registryConfig.setAddress("nacos://127.0.0.1:8848");return registryConfig;}@Beanpublic ConfigCenterConfig configCenterConfig() {ConfigCenterConfig configCenterConfig = new ConfigCenterConfig();configCenterConfig.setPort(8848);configCenterConfig.setProtocol("nacos");configCenterConfig.setAddress("127.0.0.1");// 使用默认值// configCenterConfig.setGroup("DEFAULT_GROUP");// configCenterConfig.setConfigFile("application-service-provider.yml");configCenterConfig.setNamespace("b1cb3cb8-9c6d-4cfc-a391-26aa0119a421");return configCenterConfig;}
}
我在注释中写的很详细,这四个配置类都有一个相同的父类,即AbstractConfig,并且在其父类中,有一个方法是被@PostConstruct注解修饰的。最后在初始化这个Bean的时候会,Spring会自动调用被这个注解修饰的方法,看看AbstractConfig#addIntoConfigManager()方法,代码如下:
在该方法中,调用了ApplicationModel.getConfigManager()方法,得到ConfigManager对象,即配置管理器,看看是怎么获取的,代码如下:
又需要看看ApplicationModel的LOADER属性是怎么获取的,其实就是看ExtensionLoader#getExtensionLoader(FrameworkExt.class),代码如下:
可知,ExtensionLoader有一个EXTENSION_LOADERS,用作缓存,如果通过传入的Class获取不到,则创建一个对象ExtensionLoader,调用其有参构造方法,传入Class,这里的Class就是FrameworkExt.class,并将创建好的ExtensionLoader放入EXTENSION_LOADERS,方便下次直接获取。具体看看ExtensionLoader的有参构造方法,代码如下:
因此又会调用到ExtensionLoader#getExtensionLoader()方法,也就是刚刚前面讲的。只不过此时的type属性为ExtensionFactory.class,然后还是先从EXTENSION_LOADERS中获取,假设是第一次,也获取不到,因此会调用ExtensionLoader的有参构造方法。再次回到上图的方法中,但是不同的是,这次由于type是等于ExtensionFactory.class,则此时objectFactory为null,并且还要调用ExtensionFactory#getAdaptiveExtension()方法,看看该方法,代码如下:
看看ExtensionFactory,代码如下:
ExtensionFactory确实被@SPI注解修饰了,只不过value值为"",因此ExtensionLoader#cacheDefaultExtensionName()方法中,最终设置的cachedDefaultName属性为null。回到ExtensionLoader#loadExtensionClasses(),紧接着就是调用ExtensionLoader#loadDirectory()方法,不过在此之前按,需要先看看ExtensionLoader的strategies属性,代码如下:
调用ServiceLoader#load()方法,传入LoadingStrategy.class,代码如下:
通过Java的stream流,遍历调用,new LoadingStrategy接口的实现类,得到对象,放入ExtensionLoader的strategies属性中,那LoadingStrategy接口的实现类是如何获取的呢?其实ServiceLoader是Java的 rt.jar下提供的类,属于Java自己实现的SPI机制,它会从classpath目录下的META-INF/services下,找一个名为LoadingStrategy接口全限定名的文件,即org.apache.dubbo.common.extension.LoadingStrategy,刚好找到了,代码如下:
因此最终实例化的就是这三个实现类,断点验证一下:
因此,我们如果想实现SPI机制,可以借助于Java自带的ServiceLoader实现,如果想实现更复杂的SPI机制,可以借鉴Dubbo,因为讲到这里,还不全是Dubbo的SPI机制。回到ExtensionLoader#loadExtensionClasses()方法,代码如下:
这里涉及到加载的路径,看看LoadingStrategy接口的三个实现类,代码如下:
可以知道,扫描的路径分别是:META-INF/dubbo/internal/ 或 META-INF/dubbo/ 或 META-INF/services/。ok,再看看ExtensionLoader#loadDirectory()方法,该方法是关键,代码如下:
可以知道,最终是根据前面的几个路径下,并且拼接接口的全限定名,得到文件路径,通过ClassLoader去找文件,得到urls并进行遍历,最终调用到ExtensionLoader#loadResource()方法,进行真正的加载,看看该方法,代码如下:
当然,现在看的是dubbo-common下的文件,在其他模块下,可能也存在org.apache.dubbo.common.extension.ExtensionFactory,这里就不去找了。再看看ExtensionLoader#loadClass()方法,代码如下:
顺便看看ExtensionFactory接口的实现类,如下:
由此可知,这两个实现类中,只有AdaptiveExtensionFactory是被@Adaptive注解修饰的。再回到ExtensionLoader#getAdaptiveExtensionClass()方法中,代码如下:
因此会返回AdaptiveExtensionFactory.class,再回到ExtensionLoader#createAdaptiveExtension()方法中,代码如下:
调用ExtensionLoader#injectExtension()方法,进行Dubbo的依赖注入,代码如下:
由于AdaptiveExtensionFactory中没有Setter方法,因此无需进行依赖注入,但是它的无参构造中,做了一些初始化处理,无参构造为:
需要看看ExtensionLoader#getSupportedExtensions()方法,代码如下:
这也是刚刚讲过的方法,最终只需要看ExtensionLoader#loadClass()方法,代码如下:
因此AdaptiveExtensionFactory不会被放入AdaptiveExtensionFactory的factories属性中,而是ExtensionFactory接口的其他实现,分别在两个文件中,如下:
打断点验证一下,结果如下:
这两个对象,即:SpringExtensionFactory和SpiExtensionFactory,将会是实现Dubbo的依赖注入的关键,看名字也能知道:前一个是通过Spring容器寻找依赖的对象;而后一个则是通过Dubbo的SPI机制寻找依赖的对象。当然,寻找依赖并不是直接通过这两个类来寻找的,而是通过AdaptiveExtensionFactory#getExtension()方法来寻找的,代码如下:
而SpringExtensionFactory的CONTEXTS属性,则是在ReferenceBean中或者ServiceBean总进行赋值的:
到这里为止,Dubbo的SPI机制大概讲清楚了,Dubbo的SPI剩余的内容,后面碰到再补充。想彻底搞懂这块,建议自己跟着源码,再配合博客一起看。OK,回到ApplicationModel#getConfigManager()方法传入的是 name,代码如下:
加载FrameworkExt接口实现类的逻辑跟ExtensionFactory类似,也是在classpath下,找文教名叫org.apache.dubbo.common.context.FrameworkExt的文件,如下:
最终得到这三个实现类的Class的集合,打断点结果如下:
回到ExtensionLoader#createExtension()方法中,代码如下:
通过name,即"config"可以获取到Class,即ConfigManager.class,然后通过EXTENSION_INSTANCES获取,由于是第一次,当然获取为空,因此进入if代码块,通过反射,实例化ConfigManager,并放入EXTENSION_INSTANCES中,其中key为"config",然后调用ExtensionLoader#injectExtension()方法,实际上就是调用AdaptiveExtensionFactory#getExtension()方法,寻找依赖,这块上文讲的很清楚了,这里不过多赘述。而ConfigManager有如下的Setter方法,代码如下:
这些Setter方法也不一定都会被调用,只有获取到了相应的对象,才会通过反射,进行赋值。需要说的是,给Setter方法赋值是不是一定通过Dubbo提供的依赖注入进行的赋值?这也不一定,可能在其他地方进行赋值,只不过Dubbo提供了这种方式。比如我打断点的时候,发现就不是通过Dubbo的方式进行的依赖注入,断点如下:
OK,至于是哪种方式调用Setter方法进行赋值的,这里不过多纠结,这并非核心流程。回到最开始的地方,即AbstractConfig#addIntoConfigManager()方法,由于该方法被@PostConstruct注解修饰,因此会自动被调用,断点如下:
在上篇博客中,讲到了往Spring容器中,添加了两个监听器,分别是:DubboBootstrapApplicationListener、DubboLifecycleComponentApplicationListener,这两个监听器会监听两种事件,分别是ContextRefreshedEvent、ContextClosedEvent。先看看DubboBootstrapApplicationListener,代码如下:
先看看DubboBootstrap#initialize()方法,代码如下:
在该方法中,又调用了七个方法,分别看看,代码如下:
① DubboBootstrap#initFrameworkExts()方法:
处理默认的配置中心,这里没有配置,因此均为空:
② DubboBootstrap#startConfigCenter()方法:
先看看我在DubboConfiguration配置的配置中心配置类ConfigCenterConfig,代码如下:
其中,group、configFile、namespace我都没有指定,将使用默认值,当然也可以种指定,默认值分别为:
然后我在Nacos上的配置为如下:
dubbo.properties 内容为:
目前我看的Dubbo版本应该是不支持yml/yaml的,继续往下看,重点看看DubboBootstrap#prepareEnvironment()方法,代码如下:
顺便提了下,为了实现配置中心的功能,我在pom文件中加了一个依赖:
<dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo-configcenter-nacos</artifactId><version>2.7.10</version></dependency>
因此,我应该在该模块中找一个文件名为org.apache.dubbo.configcenter.support.nacos.NacosDynamicConfigurationFactory,也确实让我找到了,如下:
而它的名字也确实是nacos,符合预期,OK,回到代码继续往下看:
地址是怎么拼接的,也简单,就不带大家看了,感兴趣的自己看看。再回到DubboBootstrap#prepareEnvironment()方法,看看DynamicConfiguration#getProperties(),代码如下:
讲到这里就很清晰了,不必深究,因为后面我会专门写两篇博客,讲Nacos的注册中心源码和配置中心源码,敬请期待。再回到DubboBootstrap#prepareEnvironment()方法,打断点看看是否获取到了配置,如下:
结果符合预期,剩下的就是解析获取到的内容,也就是调用ConfigurationUtils#parseProperties()方法,代码如下:
最后调用Environment#updateExternalConfigurationMap()方法,将解析后的配置,即Map,设置到Environment的externalConfigurationMap属性中。
并且我没有看到把这些属性设置到Spring的环境变量对象中去的代码,因此,很可能这些配置只能从Dubbo的环境变量对象获取到,但是无法从Spring的环境变量对象获取到对应的属性值,这个也可以打断点验证一下:
确实如此,这是一个问题,还有一个问题是,如果我修改了dubbo.properties的内容,服务感知不到,需要自己实现(下文我做个示例,如何实现),这个就有点坑爹。当然我看的这个版本确实有些地方还不完善,后续版本肯定会做处理的,后面有空我再研究下Dubbo 3.X的版本,如果支持了,我在本篇博客中补充一下。至于感知Nacos配置中心是如何做到了,我也提一嘴,代码如下:
我在这里处理做个示例,代码如下:
修改Nacos配置,看看结果:
从修改到感知需要花4~5s的样子,反正获取的是全量的配置。然后可以直接调用之前讲的ConfigurationUtils#parseProperties()方法解析配置,再调用Environment#updateExternalConfigurationMap()方法,更新配置即可。
限于篇幅,剩下的内容将在下一篇博客中继续讲,敬请期待,如有错误,还望在留言中指正,感谢!
相关文章:

Dubbo源码深度解析(二)
接着《Dubbo源码深度解析(一)》继续讲,上篇博客主要讲Dubbo提供的三个注解的作用,即:EnableDubbo、DubboComponentScan、EnableDubboConfig。其中后两个注解是在EnableDubbo上的,因此在启动类上加上EnableDubbo注解,等…...
RocketMQ 的高可用性:主从复制与多副本保证
RocketMQ 是一款开源的分布式消息队列系统,广泛应用于大规模分布式应用中。高可用性是 RocketMQ 的核心特性之一,通过主从复制和多副本保证,RocketMQ 能够确保消息的可靠传递和系统的高可用性。 什么是高可用性? 高可用性&#…...

Linux系统驱动(四)自动创建设备节点
自动创建设备节点 (一)创建设备节点的机制 1. mknod 将驱动编译到内核中,在内核启动时驱动自动被安装执行 2.devfs(2.4内核) 3. udev(2.6内核至今) 注:hotplug — 热插拔 &…...
Webpack、Vite区别知多少?
前端的项目打包,我们常用的构建工具有Webpack和Vite,那么Webpack和Vite是两种不同的前端构建工具,那么你们又是否了解它们的区别呢?我们在做项目时要如何选择呢? 一、工具定义 1、Webpack:是一个强大的静态模块打包工…...
《剑指编程之巅:大学新生,以诗心驭代码》
《剑指编程之巅:大学新生,以诗心驭代码》 月华如水,洒落书窗,吾辈学子,正逢盛世,编程之术,已成必修之课。然则,编程语言如繁星点点,学习资源浩瀚如海,新生初…...

【八股文】网络基础
1.简述一下TCP和UDP的区别? 特性TCP(Transmission Control Protocol)UDP(User Datagram Protocol)连接类型面向连接,需要建立三次握手连接无连接,发送数据无需建立连接数据传输提供可靠的数据传…...

Nginx进阶-常见配置(一)
一、nginx Proxy 反向代理 1、代理原理 反向代理产生的背景: 在计算机世界里,由于单个服务器的处理客户端(用户)请求能力有一个极限,当用户的接入请求蜂拥而入时,会造成服务器忙不过来的局面,…...
九/十:C语言-扫雷游戏实现与函数递归
九:数组和函数实践:扫雷游戏 1.扫雷游戏的分析和设计 (1)扫雷游戏功能说明: 使用控制台实现经典的扫雷游戏游戏可以通过菜单实现暂停或者退出游戏扫雷的游戏界面是9*9的格子默认随机布置10个雷可以排查雷࿱…...

【Android Studio】gradle文件、配置、版本下载、国内源(gradle版本以及gradle-plugin版本)
文章目录 AS查看gradle-plugin版本及gradle版本(图形)查看gradle-plugin版本及gradle版本(配置文件)配置文件分析解决gradle下载失败、版本错乱等问题。 Gradle 是一个基于 Apache Ant 和 Apache Maven 概念的自动化构建工具&…...
主要的软件设计模式及其在Kotlin中的实现示例
软件设计模式(Software Design Patterns)是面向对象设计中常用的解决方案,它们为常见的软件设计问题提供了一些被证明有效的解决方案。以下是一些主要的软件设计模式及其在Kotlin中的实现示例。 创建型模式(Creational Patterns&…...
FFmpeg音频重采样基本流程
目录 流程概述用到的APItipsdemo样例附录 - SwrContext结构体字段 流程概述 音频重采样的基本流程为: 申请重采样器上下文设置重采样去上下文的参数初始化重采样器申请数据存放的缓冲区空间进行重采样 注意,要先设置参数再对重采样器初始化 用到的API…...

无人机无人车固态锂电池技术详解
随着无人机和无人车技术的飞速发展,对高性能、高安全性电池的需求日益迫切。固态锂电池作为下一代电池技术的代表,正逐步从实验室走向市场,为无人机和无人车等应用领域带来革命性的变化。相比传统液态锂电池,固态锂电池在能量密度…...

ElementUI元件库在Axure中使用
一、ElementUI元件库介绍 ElementUI 是一套为开发者、UI/UX设计师和产品经理准备的基于Vue 2.0的桌面端组件库。它以其优雅的设计和丰富的组件,极大地提升了Web应用的开发效率与用户体验。ElementUI的组件设计精致且符合现代UI规范,包括按钮、表单、弹窗…...

联想M7615DNA打印机复印证件太黑的解决方法及个人建议
打印机在使用过程中,可能会出现复印的文字或图片太黑的问题,这会影响到打印或复印的效果。下面我们来了解一下这种情况的原因和解决方法;以下所述操作仅供大家参考,如有不足请大家提出宝贵意见; 证件包括:…...

【算法题】无重复字符的最长子串(滑动窗口)
目录 一、题目描述 二、解题思路 1、什么是滑动窗口算法? 2、滑动窗口一般解题模板 三、参考答案 一、题目描述 无重复字符的最长子串 给定一个字符串s ,请你找出其中不含有重复字符的最长子串的长度。 示例 1: 输入: s "abcabcbb"…...
Hikari连接池 最大连接数与最小空闲连接数配置多少合适?
spring:datasource: # 数据源的相关配置type: com.zaxxer.hikari.HikariDataSource # 数据源类型:HikariCPdriver-class-name: com.mysql.jdbc.Driver # mysql驱动url: jdbc:mysql://localhost:3306/t…...
【2.4 python中的基本输入和输出】
2.4 python中的基本输入和输出 在Python中,基本输入和输出是通过内置的input()函数和print()函数来实现的。这两个函数提供了与用户或其他程序进行交互的基本方式。 1. input() 函数 input() 函数用于从标准输入设备(通常是键盘)接收一行文…...
netty长连接集群方案
背景 公司某拍卖系统使用的netty服务不支持集群部署,不能进行横向扩展;并且和用户聚合服务耦合在一起,服务多节点部署不能提高拍卖性能,不能支撑更多用户使用拍卖。 目前需要改造并出一个集群的方案。 思路 因为是长连接的服务做集群,需要我们在客户端和服务器建立链接…...
Python面试题:结合Python技术,如何使用Keras进行神经网络建模
使用Keras进行神经网络建模是机器学习和深度学习领域中常用的方法之一。Keras是一个高级神经网络API,能够在TensorFlow、Theano等后端上运行,提供了简单易用的接口。下面是使用Keras进行神经网络建模的基本步骤: 安装Keras Keras是集成在Te…...

dll文件丢失怎么恢复?超简单的5个方法,1分钟搞定dll文件修复!
DLL,或称动态链接库,是一种重要的文件类型,包含了一系列用于运行几乎所有程序的指令,这些程序在win11、win10、win8和win7系统中都广泛使用。如果Windows操作系统中的dll文件丢失,您可能无法正常启动所需的程序或应用。…...

wordpress后台更新后 前端没变化的解决方法
使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…...

地震勘探——干扰波识别、井中地震时距曲线特点
目录 干扰波识别反射波地震勘探的干扰波 井中地震时距曲线特点 干扰波识别 有效波:可以用来解决所提出的地质任务的波;干扰波:所有妨碍辨认、追踪有效波的其他波。 地震勘探中,有效波和干扰波是相对的。例如,在反射波…...
Cesium1.95中高性能加载1500个点
一、基本方式: 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...
c++ 面试题(1)-----深度优先搜索(DFS)实现
操作系统:ubuntu22.04 IDE:Visual Studio Code 编程语言:C11 题目描述 地上有一个 m 行 n 列的方格,从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子,但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...

剑指offer20_链表中环的入口节点
链表中环的入口节点 给定一个链表,若其中包含环,则输出环的入口节点。 若其中不包含环,则输出null。 数据范围 节点 val 值取值范围 [ 1 , 1000 ] [1,1000] [1,1000]。 节点 val 值各不相同。 链表长度 [ 0 , 500 ] [0,500] [0,500]。 …...

页面渲染流程与性能优化
页面渲染流程与性能优化详解(完整版) 一、现代浏览器渲染流程(详细说明) 1. 构建DOM树 浏览器接收到HTML文档后,会逐步解析并构建DOM(Document Object Model)树。具体过程如下: (…...

Java-41 深入浅出 Spring - 声明式事务的支持 事务配置 XML模式 XML+注解模式
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...

相机从app启动流程
一、流程框架图 二、具体流程分析 1、得到cameralist和对应的静态信息 目录如下: 重点代码分析: 启动相机前,先要通过getCameraIdList获取camera的个数以及id,然后可以通过getCameraCharacteristics获取对应id camera的capabilities(静态信息)进行一些openCamera前的…...
Rapidio门铃消息FIFO溢出机制
关于RapidIO门铃消息FIFO的溢出机制及其与中断抖动的关系,以下是深入解析: 门铃FIFO溢出的本质 在RapidIO系统中,门铃消息FIFO是硬件控制器内部的缓冲区,用于临时存储接收到的门铃消息(Doorbell Message)。…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...