当前位置: 首页 > news >正文

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,最终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 {@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;}
}

        我在注释中写的很详细,这三个配置类都有一个相同的父类,即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()方法:

 

 

 

 

 

 

 

 

 

 

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • yum 方式下载安装 java 1.8
  • Android SurfaceFlinger——渲染开始帧(四十三)
  • MySQL基础练习题22-第二高的薪水
  • C#:通用方法总结—第15集
  • AGI思考探究的意义、价值与乐趣Ⅳ
  • 36k Star的开源大模型应用开发平台,太强了!
  • cdlinux虚拟机iso文件
  • Leetcode梦开始的地方--两数相加
  • 聊聊跨境电商平台与固定IP的那些事
  • ECMAScript 12 (ES12, ES2021) 新特性
  • C:关于static 和 extern 关键字的介绍-学习笔记
  • electron-updater实现electron全量更新和增量更新——渲染进程UI部分
  • 设计模式 之 —— 抽象工厂模式
  • C++生化危机1.5源码
  • C# Unity 面向对象补全计划 之 初识继承方法与多态
  • 【159天】尚学堂高琪Java300集视频精华笔记(128)
  • CentOS从零开始部署Nodejs项目
  • Java多线程(4):使用线程池执行定时任务
  • LeetCode29.两数相除 JavaScript
  • mysql外键的使用
  • Transformer-XL: Unleashing the Potential of Attention Models
  • 大快搜索数据爬虫技术实例安装教学篇
  • 翻译:Hystrix - How To Use
  • 前端自动化解决方案
  • 如何学习JavaEE,项目又该如何做?
  • 入职第二天:使用koa搭建node server是种怎样的体验
  • 三栏布局总结
  • 实战|智能家居行业移动应用性能分析
  • 小程序开发中的那些坑
  • 一个普通的 5 年iOS开发者的自我总结,以及5年开发经历和感想!
  • 终端用户监控:真实用户监控还是模拟监控?
  • 转载:[译] 内容加速黑科技趣谈
  • gunicorn工作原理
  • ​io --- 处理流的核心工具​
  • # Java NIO(一)FileChannel
  • #### go map 底层结构 ####
  • #Datawhale X 李宏毅苹果书 AI夏令营#3.13.2局部极小值与鞍点批量和动量
  • #define MODIFY_REG(REG, CLEARMASK, SETMASK)
  • #微信小程序(布局、渲染层基础知识)
  • #我与Java虚拟机的故事#连载01:人在JVM,身不由己
  • (19)夹钳(用于送货)
  • (C语言)编写程序将一个4×4的数组进行顺时针旋转90度后输出。
  • (ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_READ_ONLY)讲解
  • (二)Eureka服务搭建,服务注册,服务发现
  • (附源码)php新闻发布平台 毕业设计 141646
  • (附源码)spring boot公选课在线选课系统 毕业设计 142011
  • (附源码)ssm失物招领系统 毕业设计 182317
  • (附源码)计算机毕业设计SSM在线影视购票系统
  • (七)MySQL是如何将LRU链表的使用性能优化到极致的?
  • (十八)SpringBoot之发送QQ邮件
  • (五)网络优化与超参数选择--九五小庞
  • (一)RocketMQ初步认识
  • (转)德国人的记事本
  • (转贴)用VML开发工作流设计器 UCML.NET工作流管理系统
  • (状压dp)uva 10817 Headmaster's Headache