面试常问的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源码的时候,很难读懂,因为你可能都不知道,你拿到的对象到底是什么样的,这样就很难理解一些功能的实现。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

量子力学:探索微观世界的奇妙之旅

量子力学:探索微观世界的奇妙之旅 引言 在21世纪初,我们逐渐进入了一个以信息技术为主导的新时代。在这个时代,量子力学作为一门研究物质世界微观结构、粒子间相互作用以及能量与信息转换的基础科学,对我们的生活产生了深远的影响…

http和https的区别有哪些

目录 HTTP(HyperText Transfer Protocol) HTTPS(HyperText Transfer Protocol Secure) 区别与优势 应用场景 未来趋势 当我们浏览互联网时,我们经常听到两个常用的协议:HTTP(HyperText Tra…

【MATLAB源码-第96期】基于simulink的光伏逆变器仿真,光伏,boost,逆变器(IGBT)。

操作环境: MATLAB 2022a 1、算法描述 1. 光伏单元(PV Cell) 工作原理:光伏单元通过光电效应将太阳光转换为直流电。它们的输出取决于光照强度、单元温度和负载条件。Simulink建模:在Simulink中,光伏单元…

编程怎么学才能快速入门,分享一款中文编程工具快速学习编程思路,中文编程工具之分组框构件简介

一、前言: 零基础自学编程,中文编程工具下载,中文编程工具构件之扩展系统菜单构件教程 编程系统化教程链接 https://jywxz.blog.csdn.net/article/details/134073098?spm1001.2014.3001.5502 给大家分享一款中文编程工具,零基础…

【设计模式-4.3】行为型——责任链模式

说明:本文介绍设计模式中行为型设计模式中的,责任链模式; 审批流程 责任链模式属于行为型设计模式,关注于对象的行为。责任链模式非常典型的案例,就是审批流程的实现。如一个报销单的审批流程,根据报销单…

Matlab数学建模详解之发电机的最佳调度实现

🔗 运行环境:Matlab、Python 🚩 撰写作者:左手の明天 🥇 精选专栏:《python》 🔥 推荐专栏:《算法研究》 #### 防伪水印——左手の明天 #### 💗 大家好🤗&am…

从零构建属于自己的GPT系列3:模型训练2(训练函数解读、模型训练函数解读、代码逐行解读)

🚩🚩🚩Hugging Face 实战系列 总目录 有任何问题欢迎在下面留言 本篇文章的代码运行界面均在PyCharm中进行 本篇文章配套的代码资源已经上传 从零构建属于自己的GPT系列1:数据预处理 从零构建属于自己的GPT系列2:模型训…

Java 中 char 和 Unicode、UTF-8、UTF-16、ASCII、GBK 的关系

Unicode、UTF-8、UTF-16、UTF-32、ASCII、GBK、GB2312、ISO-8859-1 它们之间是什么关系? 关于这几种字符编码的关系,经过各种资料研究,总结如下图(请右键在新标签页打开查看或者下载后使用看图工具放大查看): 我们应该从历史的顺序看待这些字符编码的由来: ASCII(早期…

Python之random和string库学习

一、random库 random是python中用来生存随机数的库。具体用法如下: 1、生成一个0到1随机浮点数 random.random() 2、生成一个a到b的随机浮点数 random.uniform(1,2) 3、生成一个a到b之间的整数 random.randint(a,b) 4、随机从序列元素中取出一个值,…

Hazelcast分布式内存网格(IMDG)基本使用,使用Hazelcast做分布式内存缓存

文章目录 一、Hazelcast简介1、Hazelcast概述2、Hazelcast之IMDG3、数据分区 二、Hazelcast配置1、maven坐标2、集群搭建(1)组播自动搭建 3、客户端4、集群分组5、其他配置 三、Hazelcast分布式数据结构1、IMap2、IQueue:队列3、MultiMap4、I…

LINUX:如何以树形结构显示文件目录结构

tree tree命令用于以树状图列出目录的内容。 第一步,先安装tree这个包 sudo apt-get install tree 第二步,在指定文件目录输入下面命令,7代表7级子目录 tree -L 7 第三步,效果图 第四步,拓展学习 颜色显示 tree -C显…

mysql中除了InnoDB以外的其它存储引擎

参考资料:https://dev.mysql.com/doc/refman/8.0/en/storage-engines.html MyISAM存储引擎 https://dev.mysql.com/doc/refman/8.0/en/myisam-storage-engine.html MyISAM 存储引擎是基于比较老的ISAM存储引擎(ISAM已经不再可用)&#xff…

09、pytest多种调用方式

官方用例 # content of myivoke.py import sys import pytestclass MyPlugin:def pytest_sessionfinish(self):print("*** test run reporting finishing")if __name__ "__main__":sys.exit(pytest.main(["-qq"],plugins[MyPlugin()]))# conte…

正则表达式(5):常用符号

正则表达式(5):常用符号 小结 本博文转载自 在本博客中,”正则表达式”为一系列文章,如果你想要从头学习怎样在Linux中使用正则,可以参考此系列文章,直达链接如下: 在Linux中使用正…

AWS re:Invent 2023-亚马逊云科技全球年度技术盛会

一:会议地址 2023 re:Invent 全球大会主题演讲 - 亚马逊云科技从基础设施和人工智能/机器学习创新,到云计算领域的最新趋势与突破,倾听亚马逊云科技领导者谈论他们最关心的方面。https://webinar.amazoncloud.cn/reInvent2023/keynotes.html北京时间2023年12月1日00:30-02:…

用23种设计模式打造一个cocos creator的游戏框架----(三)外观模式模式

1、模式标准 模式名称:外观模式 模式分类:结构型 模式意图:为一组复杂的子系统提供了一个统一的简单接口。这个统一接口位于所有子系统之上,使用户可以更方便地使用整个系统。 结构图: 适用于: 当你想为…

【FPGA图像处理实战】- VGA接口与时序详解

VGA接口是一个很有历史的接口,全称为Video Graphics Array(VGA)视频图形阵列,是IBM公司在1987年随着PS/2一起推出的使用模拟信号的一种视频传输标准。 时至今日,这个接口依然还在大量使用,因为这个接口具有成本低、结构简单、应用灵活的优点。 一、VGA接口与电路原理图…

这些Java并发容器,你都了解吗?

文章目录 前言并发容器1.ConcurrentHashMap 并发版 HashMap示例 2.CopyOnWriteArrayList 并发版 ArrayList示例 3.CopyOnWriteArraySet 并发 Set示例 4.ConcurrentLinkedQueue 并发队列 (基于链表)示例 5.ConcurrentLinkedDeque 并发队列 (基于双向链表)示例 6.ConcurrentSkipL…

C练习题13

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

AUTOSAR汽车电子嵌入式编程精讲300篇-基于加密算法的车载CAN总线安全通信

目录 前言 研究现状 系统架构研究 异常检测研究 认证与加密研究 相关技术 2.1车联网 2.2车载网络及总线 2.2.1 CAN总线基础 2.2.2 CAN总线网络安全漏洞 2.2.3 CAN总线信息安全需求 2.3密码算法 2.3.1 AES算法 2.3.2 XTEA算法 CAN网络建模与仿真 3.1 CAN网络建模…