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

news2024/11/29 2:28:09

前文回顾

前一篇文章主要是讲了什么是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源码的时候,很难读懂,因为你可能都不知道,你拿到的对象到底是什么样的,这样就很难理解一些功能的实现。

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

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

相关文章

8051的存储结构组成--程序存储空间、数据存储卡空间、特殊功能寄存器、位地址空间

AT89S51的存储结构 本文主要涉及8051单片机的存储结构,它的存储结构主要包括四部分,分别是出程序存储空间、数据存储卡空间、特殊功能寄存器、位地址空间四部分,下面将会展开描述。 文章目录 AT89S51的存储结构一、 程序存储空间二、 数据存储…

总结|哪些平台有大模型知识库的Web API服务

截止2023/12/6 笔者个人的调研,有三家有大模型知识库的web api服务: 平台类型文档数量文档上传并解析的结构api情况返回页码文心一言插件版多文档有问答api,文档上传是通过网页进行上传有,而且是具体的chunk id,需要设…

CentOS服务器网页版Rstudio-server及R包批量安装最佳实践

CentOS服务器安装网页版Rstudio-server及R包批量安装 以下为CentOS 7/8的Rstudio-server安装、配置和R包安装操作 1. 软件包安装 Centos 7安装 # 下载安装包,大小115.14 MB wget -c https://download2.rstudio.org/server/centos7/x86_64/rstudio-server-rhel-…

【工厂方法】设计模式项目实践

前言 以采集数据处理逻辑为例,数据采集分为不同种类如:MQTT、MODBUS、HTTP等,不同的采集数据有不同的解析处理逻辑。但总体解析处理步骤是固定的。可以使用工厂方法设计模式简化代码,让代码变得更加优雅。 代码实践 抽象类 总体…

每天五分钟计算机视觉:通过残差块搭建卷积残差神经网络Resnet

本文重点 随着深度神经网络的层数的增加,神经网络会变得越来越难以训练,之所以这样就是因为存在梯度消失和梯度爆炸问题。本节课程我们将学习跳跃连接方式,它可以从某一网络层获取激活a,然后迅速反馈给另外一层,甚至是神经网络的更深层,从而解决梯度消失的问题。 传统的…

SMART PLC求余指令

SMART PLC求余功能块在伪随机数发生器上的应用请查看下面文章链接: https://rxxw-control.blog.csdn.net/article/details/123793176https://rxxw-control.blog.csdn.net/article/details/123793176待续....

Qt之QGraphicsView —— 笔记1.2:将QGraphicsView放置主窗口上,绘制简单图元(附完整源码)

效果 相关类介绍 QGraphicsView类提供了一个小部件,用于显示QGraphicsScene的内容。QGraphicsView在可滚动视口中可视化。QGraphicsView将滚动其视口,以确保该点在视图中居中。 QGraphicsScene类 提供了一个用于管理大量二维图形项的场景。请注意,QGraphicsScene没有自己的视…

Nginx配置反向代理与负载均衡

Nginx配置反向代理与负载均衡 一、代理服务1.正向代理2.反向代理 二、实战场景-反向代理1.修改nginx配置 -> nginx.conf文件2.修改前端路径 三、实战场景-负载均衡1.热备2.轮询3.加权轮询4.ip_hash ​ Nginx (“engine x”) 是一个高性能的HTTP和反向代理服务器,…

ubuntu22 安装 cuda12.0

本文是先安装显卡驱动后进行的操作 查看显卡驱动支持CUDA的最新版本12.0 nvidia-smi 检查gcc版本 gcc -v 查看系统支持的gcc版本 https://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html 选择对应的安装cuda命令 https://developer.nvidia.com/cuda-too…

居民最低生活保障人数数据,2020-2022年数据,shp矢量及表格数据均有!

居民最低生活保障,是国家对共同生活的家庭成员人均收入低于当地最低生活保障标准,且符合当地最低生活保障家庭财产状况规定的家庭,给予最低生活保障。 基本信息. 数据名称: 居民最低生活保障人数数据 数据格式: shpexcel 数据几何类型: 面…

C练习题13

单项选择题(本大题共20小题,每小题2分,共40分。在每小题给出的四个备选项中,选出一个正确的答案,并将所选项前的字母填写在答题纸的相应位置上。) 1.结构化程序由三种基本结构组成、三种基本结构组成的算法是() A.可以完成任何复杂的任务 B. 只能完成部分复杂的任务 C. 只能完…

Fabric:链码的部署及执行

Hyperledger Fabric:V2.5.4 写在最前 使用Fabric搭建自定义网络参考:https://blog.csdn.net/yeshang_lady/article/details/134113296 使用Fabric创建应用通道参考:https://blog.csdn.net/yeshang_lady/article/details/134668458 接下来将介绍如何在自…

配置BFD多跳检测示例

BFD简介 定义 双向转发检测BFD(Bidirectional Forwarding Detection)是一种全网统一的检测机制,用于快速检测、监控网络中链路或者IP路由的转发连通状况。 目的 为了减小设备故障对业务的影响,提高网络的可靠性,网…

CSS属性 display和visibility的区别

在CSS中&#xff0c;有两种让元素隐藏的方式&#xff0c;分别是display和visibility&#xff0c;他们有什么区别呢&#xff1f; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport"…

数据结构 | 查漏补缺之ASL、线索二叉树、哈夫曼树、大根堆、邻接表邻接矩阵

目录 ASL 情形之一&#xff1a;二分查找 线索二叉树 参考博文 哈夫曼树 参考博文 大根堆 参考博文 邻接表&邻接矩阵 参考博文1 参考博文2 ASL 参考博文 关于ASL(平均查找长度)的简单总结_平均查找长度asl-CSDN博客 情形之一&#xff1a;二分查找 线索二叉树 …

记录 | python向上取整和向下取整

python 向上取整和向下取整 向上取整 使用 ceil()&#xff1a; import mathx 0.55 x0 math.ceil(x)向下取整 使用 floor()&#xff1a; import mathx 0.55 x1 math.floor(x)如下&#xff1a;

文章解读与仿真程序复现思路——中国电机工程学报EI\CSCD\北大核心《计及管网选型与潮流约束的区域综合能源系统分期协同规划》

这个标题涉及到区域综合能源系统的规划&#xff0c;其中考虑了两个关键因素&#xff1a;管网选型和潮流约束。下面对标题中的关键术语进行解读&#xff1a; 区域综合能源系统&#xff1a; 这指的是一个综合考虑多种能源形式&#xff08;比如电力、热能、气体等&#xff09;的系…

Jenkins+Maven+Gitlab+Tomcat 自动化构建打包,部署

环境准备Jenkins工具、环境、插件配置全局变量配置安装插件Deploy to containerMaven Integration plugin配置国内mvn源 创建maven项目 环境准备 1、安装服务 Jenkins工具、环境、插件配置 全局变量配置 Manage Jenkins>tools>JDK 安装 安装插件 Deploy to contai…

网站导航栏下滑隐藏,上滑显示,效果杠杆,兼容性强

前言 导航栏是网站必不可少的一部分&#xff0c;那么&#xff0c;导航栏应该怎么样子实现&#xff0c;可以高效自定义兼容开发呢&#xff1f;当然&#xff0c;不仅要实现&#xff0c;而且还要实现导航栏顶部固定位置&#xff0c;下拉隐藏&#xff0c;稍微往上滑动就会出现&…

Android View的 getHeight 和 getMeasuredHeight 的区别

前言 先简单复习一下Android View 的 绘制顺序&#xff1a; 1、onMeasure&#xff08;测量&#xff09;&#xff0c;先根据构造器传进来的LayoutParams&#xff08;布局参数&#xff09;&#xff0c;测量view宽高。 2、onLayout&#xff08;布局&#xff09;&#xff0c;再根…