面试常问的dubbo的spi机制到底是什么?(下)

前文回顾

前一篇文章主要是讲了什么是spi机制,spi机制在java、spring中的不同实现的分析,同时也剖析了一下dubbo spi机制的实现ExtensionLoader的实现中关于实现类加载以及实现类分类的源码。

一、实现类对象构造

看实现类对象构造过程之前,先看获取,因为获取不到才构造,也就是java中spi没有的功能,按需加载。


获取实现类对象的方法是getExtension方法,传入的name参数就是短名称,也就是spi文件的键,wrap是是否包装的意思,true的意思就是对你获取的目标对象进行包装(具体什么是包装,如何包装后面会讲),wrap默认是true

接下来我们就着重分析getExtension方法

图片

前面两个if我说一下,

第一个if比较简单,就是简单的参数校验,name参数不能为空


第二个if判断name是不是字符串true,是的话就调用getDefaultExtension,getDefaultExtension这个方法通过名称也能看出来就是获取接口默认的实现,什么是默认实现?在 面试常问的dubbo的spi机制到底是什么?(上)一文中在实现类加载的时候我提到过,默认的实现就是@SPI注解中的名称对应的实现类。

前面两个if之后就是真正获取实现了。在获取之前,先根据你是否包装构建缓存的键值,如果没有包装,就会在短名称后加上 _origin  ,这主要是为了区分包不包装,然后进入getOrCreateHolder方法

图片

里面其实就是通过缓存名称从cachedInstances获取一个Holder,获取不到就new一个Holder然后放到cachedInstances中,然后返回。Holder其实本身并没有什么意义,可以理解为一个空壳,里面放的才是真正最终返回的对象。

第一次,不用说Holder肯定没有,那么这个Holder肯定是刚new出来的。

跳出getOrCreateHolder方法,继续往下看。

图片

从Holder中获取实现类,此时肯定是null,接下来就是synchronized,然后又是非空判断。这里其实是典型的单例模式中的双重检查机制,保证并发安全。其实从这里可以看出Holder的作用。这里是为了减少锁冲突的,因为一个实现类对象对应一个Holder对象,这样不同的实现类在创建的时候,由于Holder的不同,synchronized就不是同一个锁对象,这就起到了并发时候减少锁冲突的作用,从这可以看出dubbo设计的时候的细节是很到位的。

第一次都是null,接下来进入createExtension方法,构建对象的过程

图片

先从实现类的缓存中获取到短名称对应的实现类,面试常问的dubbo的spi机制到底是什么?(上)一文中说到,实现类加载之后会放到内部的一个缓存中。

这个if条件判断一般肯定是false的,但是有些情况,就比如第一次构建对象抛出异常,此时第二次来构建这个对象,那么不用说肯定也会有问题,dubbo为了快速知道哪些实现类对象构造的时候会出异常,就在第一次构建对象抛异常的时候缓存了实现类的短名称到unacceptableExceptions中,当第二次来构建的时候,能够快速知道,抛出异常,减少资源的浪费。


接下来就会从extensionInstances中获取实例,这个实例是没有包装的实例,也就是说如果你获取的不带包装的实例,就是这个实例。我们看看这个实例是怎么构建出来的,这里我根据构建的不同阶段进行划分为以下几个步骤。

第一步:实例化对象

通过实例化策略InstantiationStrategy进行实例化,默认是通过无参构造器构造的。

图片

第二步 :初始化前ExtensionPostProcessor 回调

调用 ExtensionPostProcessor的postProcessBeforeInitialization方法,ExtensionPostProcessor跟spring中的BeanPostProcessor有点像,就是对目标对象进行扩展的作用。

图片

第三步 :依赖注入

接下来调用injectExtension方法,这个方法就是依赖注入的实现方法。

依赖注入:说白了就是dubbo会自动调用需要依赖注入的方法,传入相应的参数

哪些方法是需要依赖注入的方法?

dubbo约定 方法名以set开头,方法的参数只有一个,方法上没有加@DisableInject注解 ,方法是public的,符合这类的方法就是需要依赖注入的方法,dubbo在构建对象的时候会自动调用这些方法,传入相应的参数。

接下来进入源码

图片

可以看出,先通过反射获取到所有的方法,然后遍历每个方法,进入两个if判断,这个判断就是判断是不是需要依赖注入的方法,也就是上面说的条件就在这个体现。

假设是需要依赖注入的方法,接下来看看如何获得需要被注入的对象,也就是方法的参数。

图片

首先获取需要set的对象的class类型,就是方法的参数类型

然后通过getSetterProperty方法获取属性名,可以理解为bean的名称,

getSetterProperty就是方法去掉set然后第一个字母小写之后就是属性的名称,举个例子方法叫setUser,那么属性名就叫user,如果叫setUserName,属性名就叫userName,就这么简单。

最后就是根据属性名和参数类型通过 ExtensionInjector 获取需要被注入的对象。

ExtensionInjector 接口讲解

ExtensionInjector就是注入器,通过这个可以获取到被依赖注入的对象,这是个接口,有很多实现,这里是 AdaptiveExtensionInjector 实现类,也是通过spi机制获取的,ExtensionLoader构造的时候获取的。

下面列举了ExtensionInjector有的实现:

AdaptiveExtensionInjector:自适应的,本身没有实际的意义,就是遍历所有其它的ExtensionInjector实现来获取,一旦有一个获取到,就不会再调用下一个ExtensionInjector来获取的

图片

SpiExtensionInjector:顾名思义,就是通过spi机制来获取,获取的是自适应的实现

SpringExtensionInjector:这个是通过spring容器获取实现,所以你通过dubbo的spi机制可以注入spring的bean

ScopeBeanExtensionInjector:通过dubbo内部的组件BeanFactory来获取的,BeanFactory是dubbo内部用来在一定范围的bean的容器,主要是为了对象的重复利用来的。

假设这里获取到了对象,那么接下来就是通过反射调用set方法,进行依赖注入,然后依赖注入就完成了。

第四步:ExtensionAccessorAware接口回调

图片

如果你的接口实现了ExtensionAccessorAware接口,那么会注入给你的bean一个 ExtensionDirector ,ExtensionDirector 可以想象成是ExtensionLoader工厂,可以获取每个接口的ExtensionLoader。

第五步: 初始化后ExtensionPostProcessor回调

图片

调用ExtensionPostProcessor的postProcessAfterInitialization方法对目标对象进行扩展的作用。

第六步:自动包装

到这一步实现类本身的对象就算构造好了,接下来就是进行自动包装,如果wrap是true的话。

自动包装:可以说是静态代理模式,就是对你的目标对象进行代理,怎么代理,就是通过包装类,什么是包装类,在面试常问的dubbo的spi机制到底是什么?(上)有说过,一个一个构造,慢慢构成一个调用链条,最终才会调用到真正的实现类

我们看看源码的实现

图片

@Wrapper注解是个匹配的作用,就是根据需要属性从包装类中选择一批可以用来包装的类。

构造其实很简单,就是当前instance当做包装类的构造参数通过反射构造,然后进行依赖注入,然后将构造出来的对象复制给instance,instance再进行回调之后再赋值给instance,这样往往复复就形成了一个链条。这里我画个图,让大家看看最后构造出来的对象是什么样。

图片

构造后的对象其实就是这样,你最终使用的对象其实是包装对象,如果你获取对象的时候传的wrap参数是true的话,当前默认情况下是true。最后调用的话就会先调用最外层的包装的方法(包装对象2),然后调用(包装对象1)一直调用,最后会调用到真正的目标对象的方法。

为什么需要包装?

很多人可能不清楚,为什么需要包装,其实很好理解,就是起到动态增强目标对象的作用。可以理解为spring中的aop,但是dubbo因为不像spring那样有完整的ioc和aop的实现,dubbo就通过这种包装的方式来实现动态增强目标对象功能的作用。

第七步:Lifecycle接口回调

接下来会调用initExtension方法,这个方法的作用就是判断你的实现类有没有实现Lifecycle接口,如果有的话会调用initialize()方法的实现

至此,一个可用的实现类对象就算完完全全构建完成了,你拿到的对象就是这个对象,然后就会返回这个对象,存到Holder对象中。

图片

最后来张图总结一下实现类构造的过程。

图片

这里我在简单说明一下,

1)包装不是必须的,得看你要获取的对象是什么,如果不要包装,就会回调原始对象的Lifecycle接口,不过dubbo内部的框架基本上获取的都是带包装的对象,而非原始的对象;

2)包装时暴露出去的是包装类的对象,在调用的时候,最先调用的也是包装类的对象,然后一层一层的调用,最终调用到实现类对象。

二、自适应机制

自适应:自适应扩展类的含义是说,基于参数,在运行时动态选择到具体的目标类,然后执行。在 Dubbo 中,很多拓展都是通过 SPI 机制进行加载的,比如 Protocol、Cluster、LoadBalance 等。有时,有些拓展并不想在框架启动阶段被加载,而是希望在拓展方法被调用时,根据运行时参数进行加载。这听起来有些矛盾。拓展未被加载,那么拓展方法就无法被调用(静态方法除外)。拓展方法未被调用,拓展就无法被加载。对于这个矛盾的问题,Dubbo 通过自适应拓展机制很好的解决了。自适应拓展机制的实现逻辑比较复杂,首先 Dubbo 会为拓展接口生成具有代理功能的代码。然后通过 javassist 或 jdk 编译这段代码,得到 Class 类。最后再通过反射创建代理类,整个过程比较复杂。

自适应对象获取的方法,就是getAdaptiveExtension方法。构建自适应对象的方法就是createAdaptiveExtension方法的实现。

图片

源码很简单,就是得到自适应的实现类,然后就是普通反射构造,然后经过初始化前,依赖注入,初始化之后,Lifecycle接口回调操作,构造出对象。

自适应的类有两种来源,一种是自己在实现类上加@Adaptive注解,指定自适应实现类,上面提到的AdaptiveExtensionInjector就是指定的自适应实现类,类上加了@Adaptive注解,如果不指定,dubbo框架会按照一定的规则来动态生成一个自适应的类,构造过程在createAdaptiveExtensionClass方法实现,最终会调用AdaptiveClassCodeGenerator生成代码

图片

三、自动激活

所谓的自动激活,就是根据你的入参,动态的选择一批实现类返回给你。至于怎么找到,就是通过注解@Activate来实现的。dubbo内部自动激活的主要用在Filter中,Filter是个接口,有很多实现。不论是在provider端还是consumer端,在调用之前,都会经过一个由Filter实现构成的链,这条链的不同实现就是根据入参的不同来区分是每个Filter的实现属于provider的还是consumer端的。

@Activate它有三个重要属性,group 表示修饰在哪个端,是 provider 还是 consumer,value 表示在 URL参数中出现才会被激活,order 表示实现类的顺序。

图片

总结

本文接着上篇 面试常问的dubbo的spi机制到底是什么?(上)从源码的角度剖析了dubbo spi机制的功能,包扩了在构建对象时的ioc和自动包装的机制、自适应对象机制、自动激活机制。整体而言,dubbo的spi机制不是很难,所以大家看了两篇文章之后如果自己再过一遍源码的话那么收获会更大。dubbo的spi机制其实非常重要,如果不理解dubbo的spi机制的特性的话,在阅读dubbo源码的时候,很难读懂,因为你可能都不知道,你拿到的对象到底是什么样的,这样就很难理解一些功能的实现。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

当使用RSA加密,从手机前端到服务器后端的请求数据存在+

将转成了空格,导致解密出错 将空格转成了

MySQL系列(二)——日志篇

一、有哪些日志 MySQL应该是我们用的最多,也算是最熟悉的数据库了。那么,MySQL中有哪些日志了,或者你知道的有哪些日志了? 首先,我们能接触到的,一般我们排查慢查询时,会去看慢查询日志。如果…

红队专题-开源资产扫描系统-ARL资产灯塔系统

ARL资产灯塔系统 安装说明问题 : 安装说明 源码地址 https://github.com/TophantTechnology/ARL https://github.com/TophantTechnology/ARL/wiki/Docker-%E7%8E%AF%E5%A2%83%E5%AE%89%E8%A3%85-ARL 安装环境 uname -a Linux VM-24-12-centos 3.10.0-1160.49.1.e…

Git多人协作(二)

个人主页:Lei宝啊 愿所有美好如期而遇 前言 上节:Git多人协作(一) 上次我们模拟了多人在一个分支上进行开发,并且是在远程直接新建的分支,而后我们本地进行拉取;本节我们将模拟多人分别在多分支上进行开发&#xf…

Azure Machine Learning - Azure OpenAI 服务使用 GPT-35-Turbo and GPT-4

通过 Azure OpenAI 服务使用 GPT-35-Turbo and GPT-4 环境准备 Azure 订阅 - 免费创建订阅已在所需的 Azure 订阅中授予对 Azure OpenAI 服务的访问权限。 目前,仅应用程序授予对此服务的访问权限。 可以填写 https://aka.ms/oai/access 处的表单来申请对 Azure Op…

网络基础入门---使用udp协议改进程序

目录标题 前言改进一:单词翻译程序准备工作transform函数的实现init_dictionary函数的实现transform函数的实现其他地方的修改测试 改进二:远程指令执行程序popenexecCommand函数实现测试 改进三:群聊程序Usr类onlineUser类adduserdelUserisO…

mixamo根动画导入UE5问题:滑铲

最近想做一个跑酷游戏,从mixamo下载滑铲动作后,出了很多动画的问题。花了两周时间,终于是把所有的问题基本上都解决了。 常见问题: 1.【动画序列】人物不移动。 2.【动画序列】人物移动朝向错误。 3.【蒙太奇】人物移动后会被拉回…

TensorRT Provider 与TensorRT Native的对比

TensorRT Provider 的优势为: TensorRT EP 可以实现与本机 TensorRT 的性能等价。使用 TensorRT EP 的好处之一是,如果模型中存在不受支持的 TensorRT 操作,就可以运行不能在本机 TensorRT 中运行的模型。这些操作将自动退回到其他 EP&#…

8051单片机的CPU组成与四个并行I/O端口

AT89S51的CPU与并行I/O端口 本文主要涉及8051的CPU组成以及并行的4个I/O端口。CPU,主要由运算器(ALU)和控制器(CU)构成;4个双向的8位并行I/O端口,分别记为P0、P1、P2和P3 文章目录 AT89S51的CPU…

Swagger——接口文档自动生成和测试

目录 1 介绍2 使用步骤 1 介绍 Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务(https://swagger.io/)。 它的主要作用是: 使得前后端分离开发更加方便,有利于团队协作 接口的文档在线自动生成&…

厦门排水管网监测系统,实时感知城市健康

在厦门城市化建设的步伐中,有一个不可或缺的环节,那就是排水管网监测系统。它就像城市生命线上的守护者,默默地守护着城市的正常运行,防止内涝等问题的出现。 排水管网监测系统是城市基础设施建设中的重要一环,其重要性…

金鸣表格文字识别大师扫描仪使用技巧

所需硬件:PC(电脑)、扫描仪 所需软件:金鸣表格文字识别大师5.0以上版本(以下简称“本软件”) 实现功能:直接用扫描仪扫描图片并将其转换为可编辑的excel或word. 实现原理:本软件利…

Qt OpenCV 学习(二):两个简单图片识别案例

1. 寻找匹配物体 1.1 mainwindow.h #ifndef MAINWINDOW_H #define MAINWINDOW_H#include <QMainWindow> #include <opencv2/opencv.hpp>#include <QImage> #include <QString> #include <QPixmap>QT_BEGIN_NAMESPACE namespace Ui { class Main…

配置OSS后如何将服务器已有文件上传至OSS,推荐使用ossutil使用

1.下载安装ossutil sudo -v ; curl https://gosspublic.alicdn.com/ossutil/install.sh | sudo bash2.交互式配置生成配置文件 ossutil config 根据提示分别设置配置文件路径、设置工具的语言、Endpoint、AccessKey ID、AccessKey Secret和STSToken参数&#xff0c;STSToken留…

【QT】容器类的迭代

迭代器(iterator)为访问容器类里的数据项提供了统一的方法&#xff0c;Qt有两种迭代器类&#xff1a;Java类型的迭代器和STL类型的迭代器。 Java类型的迭代器更易于使用&#xff0c;且提供一些高级功能&#xff0c;而STL类型的迭代器效率更高。 Qt还提供一个关键字foreach&…

Self-supervised Graph Learning for Recommendation 详解

目录 摘要 引言 预备知识 方法 3.1 图结构数据增强 3.2 对比学习 3.3 多任务学习 3.4 理论分析 摘要 基于用户-物品图的推荐表示学习已经从使用单一 ID 或交互历史发展到利用高阶邻居。这导致了图卷积网络(GCNs)在推荐方面的成功&#xff0c;如 PinSage 和 LightGCN。尽管具…

[Firefly-Linux] RK3568 pca9555芯片驱动详解

文章目录 一、PAC9555 介绍二、ITX-3568JQ PAC9555 使用2.1 原理图2.2 设备树三、RK3568 I2C 介绍四、PAC9555 驱动4.1 介绍4.2 数据结构4.3 驱动分析一、PAC9555 介绍 PAC9555 是一种高性能、低功耗 I/O 扩展芯片,能够提供 16 个 GPIO 通道,每个通道可以单独配置为输入或输…

C语言再学习 -- 单精度(float)和双精度(double)浮点数 与 十六进制(HEX) 之间转换(转载))

之前讲过浮点数部分&#xff0c;参看&#xff1a;C语言再学习 – 浮点数 现在程序中要将浮点数&#xff0c;通过TCP发送。那得先将其转换为十六进制才行呀。 那么问题就来了。 参看&#xff1a;C语言&#xff1a;单精度(float)和双精度(double)浮点数 与 十六进制(HEX) 之间…

完数的C语言实现xdoj30

时间限制: 2 S 内存限制: 10000 Kb 问题描述: 请写一个程序&#xff0c;给出指定整数范围[a&#xff0c;b]内的所有完数&#xff0c;0 < a < b < 10000。 一个数如果恰好等于除它本身外的所有因子之和&#xff0c;这个数就称为"完数"。 例如6是…

git 关于分支、merge、commit提交

最近开始用git终端提交代码&#xff0c;梳理了一些知识点 一 关于分支 关于分支&#xff0c;git的分支分为本地分支远程分支两种分支&#xff0c;在上传代码时&#xff0c;我们要确保当前本地分支连接了一个远程分支。 我们可以通过下面代码查看当前的本地分支&#xff1a; g…