Dubbo的SPI机制

news2025/1/15 21:08:37

目录

什么是 SPI

Java SPI 示例

Java SPI 源码分析

想一下 Java SPI 哪里不好

Dubbo SPI

Dubbo SPI 简单实例

Dubbo 源码分析

getExtensionClasses

Adaptive 注解 - 自适应扩展

Adaptive 注解在类上

Adaptive 注解在方法上

WrapperClass - AOP

injectExtension - IOC

Activate 注解

总结


注意:本文参考  三歪问我Dubbo的SPI机制是啥?

什么是 SPI

首先我们得先知道什么叫 SPI。

SPI (Service Provider Interface),主要是用来在框架中使用的,最常见和莫过于我们在访问数据库时候用到的java.sql.Driver接口了。

你想一下首先市面上的数据库五花八门,不同的数据库底层协议的大不相同,所以首先需要定制一个接口,来约束一下这些数据库,使得 Java 语言的使用者在调用数据库的时候可以方便、统一的面向接口编程。

数据库厂商们需要根据接口来开发他们对应的实现,那么问题来了,真正使用的时候到底用哪个实现呢?从哪里找到实现类呢?

这时候 Java SPI 机制就派上用场了,不知道到底用哪个实现类和找不到实现类,我们告诉它不就完事了呗。

大家都约定好将实现类的配置写在一个地方,然后到时候都去哪个地方查一下不就知道了吗?

Java SPI 就是这样做的,约定在 Classpath 下的 META-INF/services/ 目录里创建一个以服务接口命名的文件,然后文件里面记录的是此 jar 包提供的具体实现类的全限定名

这样当我们引用了某个 jar 包的时候就可以去找这个 jar 包的 META-INF/services/ 目录,再根据接口名找到文件,然后读取文件里面的内容去进行实现类的加载与实例化。

比如我们看下 MySQL 是怎么做的。

再来看一下文件里面的内容。 

MySQL 就是这样做的,为了让大家更加深刻的理解我再简单的写一个示例。

Java SPI 示例

 

然后我在 META-INF/services/ 目录下建了个以接口全限定名命名的文件,内容如下

com.demo.spi.NuanNanAobing
com.demo.spi.ShuaiAobing

运行之后的结果如下

 

Java SPI 源码分析

之前的文章我也提到了 Dubbo 并没有用 Java 实现的 SPI,而是自定义 SPI,那肯定是 Java SPI 有什么不方便的地方或者劣势。

因此丙带着大家先深入了解一下 Java SPI,这样才能知道哪里不好,进而再和 Dubbo SPI 进行对比的时候会更加的清晰其优势。

大家看到源码不要怕,丙已经给大家做了注释,并且逻辑也不难的,想要变强源码不可或缺。为了让大家更好的理解,丙在源码分析完了之后还会画个图,帮大家再理一下思路。

从上面我的示例中可以看到ServiceLoader.load()其实就是 Java SPI 入口,我们来看看到底做了什么操作。

 

我用一句话概括一下,简单的说就是先找当前线程绑定的 ClassLoader,如果没有就用 SystemClassLoader,然后清除一下缓存,再创建一个 LazyIterator。

那现在重点就是 LazyIterator了,从上面代码可以看到我们调用了 hasNext() 来做实例循环,通过 next() 得到一个实例。而 LazyIterator 其实就是 Iterator 的实现类。我们来看看它到底干了啥。

 

不管进入 if 分支还是 else 分支,重点都在我框出来的代码,接下来就进入重要时刻了!

 

可以看到这个方法其实就是在约定好的地方找到接口对应的文件,然后加载文件并且解析文件里面的内容。

我们再来看一下 nextService()。

 

所以就是通过文件里填写的全限定名加载类,并且创建其实例放入缓存之后返回实例。

整体的 Java SPI 的源码解析已经完毕,是不是很简单?就是约定一个目录,根据接口名去那个目录找到文件,文件解析得到实现类的全限定名,然后循环加载实现类和创建其实例。

我再用一张图来带大家过一遍。

 

想一下 Java SPI 哪里不好

相信大家一眼就能看出来,Java SPI 在查找扩展实现类的时候遍历 SPI 的配置文件并且将实现类全部实例化,假设一个实现类初始化过程比较消耗资源且耗时,但是你的代码里面又用不上它,这就产生了资源的浪费。

所以说 Java SPI 无法按需加载实现类。

Dubbo SPI

因此 Dubbo 就自己实现了一个 SPI,让我们想一下按需加载的话首先你得给个名字,通过名字去文件里面找到对应的实现类全限定名然后加载实例化即可。

Dubbo 就是这样设计的,配置文件里面存放的是键值对,我截一个 Cluster 的配置。

并且 Dubbo SPI 除了可以按需加载实现类之外,增加了 IOC 和 AOP 的特性,还有个自适应扩展机制。

我们先来看一下 Dubbo 对配置文件目录的约定,不同于 Java SPI ,Dubbo 分为了三类目录。

META-INF/services/ 目录:该目录下的 SPI 配置文件是为了用来兼容 Java SPI 。

META-INF/dubbo/ 目录:该目录存放用户自定义的 SPI 配置文件。

META-INF/dubbo/internal/ 目录:该目录存放 Dubbo 内部使用的 SPI 配置文件。

Dubbo SPI 简单实例

用法很是简单,我就拿官网上的例子来展示一下。

首先在  META-INF/dubbo 目录下按接口全限定名建立一个文件,内容如下:

optimusPrime = org.apache.spi.OptimusPrime
bumblebee = org.apache.spi.Bumblebee

然后在接口上标注@SPI 注解,以表明它要用SPI机制,类似下面这个图(我就是拿 Cluster 的图举个例子,和这个示例代码定义的接口不一样)。

 

接着通过下面的示例代码即可加载指定的实现类。

 

再来看一下运行的结果。 

Dubbo 源码分析

此次分析的源码版本是 2.6.5

相信通过上面的描述大家已经对 Dubbo SPI 已经有了一定的认识,接下来我们来看看它的实现。

从上面的示例代码我们知道 ExtensionLoader 好像就是重点,它是类似 Java SPI 中 ServiceLoader 的存在。

我们可以看到大致流程就是先通过接口类找到一个 ExtensionLoader ,然后再通过 ExtensionLoader.getExtension(name) 得到指定名字的实现类实例。

我们就先看下 getExtensionLoader() 做了什么。

 

很简单,做了一些判断然后从缓存里面找是否已经存在这个类型的 ExtensionLoader ,如果没有就新建一个塞入缓存。最后返回接口类对应的 ExtensionLoader 。

我们再来看一下 getExtension() 方法,从现象我们可以知道这个方法就是从类对应的 ExtensionLoader 中通过名字找到实例化完的实现类。

 

可以看到重点就是 createExtension(),我们再来看下这个方法干了啥。

 

整体逻辑很清晰,先找实现类,判断缓存是否有实例,没有就反射建个实例,然后执行 set 方法依赖注入。如果有找到包装类的话,再包一层。

到这步为止我先画个图,大家理一理,还是很简单的。

 

那么问题来了 getExtensionClasses() 是怎么找的呢?injectExtension() 如何注入的呢(其实我已经说了set方法注入)?为什么需要包装类呢?

getExtensionClasses

这个方法进去也是先去缓存中找,如果缓存是空的,那么调用 loadExtensionClasses,我们就来看下这个方法。

 

而 loadDirectory里面就是根据类名和指定的目录,找到文件先获取所有的资源,然后一个一个去加载类,然后再通过loadClass去做一下缓存操作。

 

可以看到,loadClass 之前已经加载了类,loadClass  只是根据类上面的情况做不同的缓存。分别有 Adaptive 、WrapperClass 和普通类这三种,普通类又将Activate记录了一下。至此对于普通的类来说整个 SPI 过程完结了。 

接下来我们分别看不是普通类的几种东西是干啥用的。

Adaptive 注解 - 自适应扩展

在进入这个注解分析之前,我们需要知道 Dubbo 的自适应扩展机制。

我们先来看一个场景,首先我们根据配置来进行 SPI 扩展的加载,但是我不想在启动的时候让扩展被加载,我想根据请求时候的参数来动态选择对应的扩展。

怎么做呢?

Dubbo 通过一个代理机制实现了自适应扩展,简单的说就是为你想扩展的接口生成一个代理类,可以通过JDK 或者 javassist 编译你生成的代理类代码,然后通过反射创建实例。

这个实例里面的实现会根据本来方法的请求参数得知需要的扩展类,然后通过 ExtensionLoader.getExtensionLoader(type.class).getExtension(从参数得来的name),来获取真正的实例来调用。

我从官网搞了个例子,大家来看下。

 

现在大家应该对自适应扩展有了一定的认识了,我们再来看下源码,到底怎么做的。 

这个注解就是自适应扩展相关的注解,可以修饰类和方法上,在修饰类的时候不会生成代理类,因为这个类就是代理类,修饰在方法上的时候会生成代理类。

Adaptive 注解在类上

比如这个 ExtensionFactory 有三个实现类,其中一个实现类就被标注了 Adaptive 注解。

 

在 ExtensionLoader 构造的时候就会去通过getAdaptiveExtension 获取指定的扩展类的 ExtensionFactory。 

我们再来看下 AdaptiveExtensionFactory 的实现。

 

可以看到先缓存了所有实现类,然后在获取的时候通过遍历找到对应的 Extension。

我们再来深入分析一波 getAdaptiveExtension 里面到底干了什么。

 

到这里其实已经和上文分析的 getExtensionClasses中loadClass 对 Adaptive 特殊缓存相呼应上了。 

Adaptive 注解在方法上

注解在方法上则需要动态拼接代码,然后动态生成类,我们以 Protocol 为例子来看一下。

 

Protocol 没有实现类注释了 Adaptive ,但是接口上有两个方法注解了 Adaptive ,有两个方法没有。

因此它走的逻辑应该应该是 createAdaptiveExtensionClass

 

具体在里面如何生成代码的我就不再深入了,有兴趣的自己去看吧,我就把成品解析一下,就差不多了。 

我美化一下给大家看看。 

可以看到会生成包,也会生成 import 语句,类名就是接口加个$Adaptive,并且实现这接口,没有标记 Adaptive 注解的方法调用的话直接抛错。

我们再来看一下标注了注解的方法,我就拿 export 举例。

 

就像我前面说的那样,根据请求的参数,即 URL 得到具体要调用的实现类名,然后再调用 getExtension 获取。

整个自适应扩展流程如下。

 

WrapperClass - AOP

包装类是因为一个扩展接口可能有多个扩展实现类,而这些扩展实现类会有一个相同的或者公共的逻辑,如果每个实现类都写一遍代码就重复了,并且比较不好维护。

因此就搞了个包装类,Dubbo 里帮你自动包装,只需要某个扩展类的构造函数只有一个参数,并且是扩展接口类型,就会被判定为包装类,然后记录下来,用来包装别的实现类。

 

简单又巧妙,这就是 AOP 了。

injectExtension - IOC

直接看代码,很简单,就是查找 set 方法,根据参数找到依赖对象则注入。

 

这就是 IOC。

Activate 注解

这个注解我就简单的说下,拿 Filter 举例,Filter 有很多实现类,在某些场景下需要其中的几个实现类,而某些场景下需要另外几个,而 Activate 注解就是标记这个用的。

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

总结

先放个上述过程完整的图。

 

然后我们再来总结一下,今天丙先带大家了解了下什么是 SPI,写了个简单示例,并且进行了 Java SPI 源码分析。

得知了 Java SPI 会一次加载和实例化所有的实现类。

而 Dubbo SPI 则自己实现了 SPI,可以通过名字实例化指定的实现类,并且实现了 IOC 、AOP 与 自适应扩展 SPI 。

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

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

相关文章

webpack 官方文档解读一(详细使用教程) 起步

什么是webpack 就是个打包工具。通过一系列插件帮你优化项目,压缩,混淆等。总之什么脏活累活都能干。 入门案例 创建一个目录,并安装webpack和webpack-cli这两个包。webpack包是webpack本体,webpack-cli是他提供的工具包。 mk…

RTL8380M/RTL8382M管理型交换机系统软件操作指南二:转发表

前面介绍了端口配置,这次对转发表进行详细的描述,主要包括以下三方面内容:基础配置、转发表、删除1.1 基础配置 1.1.1 老化时间 老化时间是一个影响交换机学习进程的参数。从一个地址记录加入地址表以后开始计时,如果在老化时间内各端口未收…

ResNet网络详解

ResNet ResNet在2015年由微软实验室提出,斩获当年lmageNet竞赛中分类任务第一名,目标检测第一名。获得coco数据集中目标检测第一名,图像分割第一名。 ResNet亮点 1.超深的网络结构(突破1000层) 2.提出residual模块 3.使用Batch Normalizat…

java项目-第147期ssm社区生活超市管理系统_(spring+springmvc+mybatis+jsp)_java毕业设计_计算机毕业设计

java项目-第147期ssm社区生活超市管理系统_(springspringmvcmybatisjsp)_java毕业设计_计算机毕业设计 【源码请到资源专栏下载】 今天分享的项目是《ssm社区生活超市管理系统》 该项目分为3个角色,管理员、用户、供应商角色。 用户可以浏览前台商品,进行…

[附源码]java毕业设计软件项目过程管理系统

项目运行 环境配置: Jdk1.8 Tomcat7.0 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目技术: SSM mybatis Maven Vue 等等组成,B/S模式 M…

牛客小白月赛 61 E 排队

题目大意: n个数,共有n!种排列方式,记Pi(a)表示序列a的第i种排队方式,cnt(Pi(a))表示P(i)的逆序对个数,PLMM想知道这n!种排列方式共有多少对逆序对 给定一个 nnn 个数,在所有排列顺序…

Windows 11 Insider Preview Build 25247.1000(rs_prerelease)更新内容

微软于今日推出了新的Dev预览版25247.1000,引入了电源设置的新能源建议,“帐户”页面现在会在 OneDrive 存储空间不足时显示警告。下面就和小编一起来看看详细的更新内容吧。 更新内容 此版本包括一些新功能,包括能源建议、任务管理器的一些改…

MySQL8.0优化 - 锁 - 从数据操作的类型划分:读锁、写锁

文章目录学习资料锁的不同角度分类锁的分类图如下从数据操作的类型划分:读锁、写锁读锁写锁锁定读MySQL8.0新特性写操作学习资料 【MySQL数据库教程天花板,mysql安装到mysql高级,强!硬!-哔哩哔哩】 【阿里巴巴Java开…

【21-业务开发-基础业务-商品模块-分类管理-商品系统三级分类的新增类别前后端代码实现-商品系统三级分类的更新类别前后端代码实现-之前错误的Bug修正】

一.知识回顾 【0.三高商城系统的专题专栏都帮你整理好了,请点击这里!】 【1-系统架构演进过程】 【2-微服务系统架构需求】 【3-高性能、高并发、高可用的三高商城系统项目介绍】 【4-Linux云服务器上安装Docker】 【5-Docker安装部署MySQL和Redis服务】…

2022 全网最全最新 Java 面试题 - 独家内部教材

怎样才能拿到大厂的 offer,没有掌握绝对的技术,那么就要不断的学习 从疫情破局而出,又在毕业季一路过关斩将,我是如何笑面试官,拿到阿里,腾讯等八家大厂的 offer 的呢,在这里分享我的秘密武器&…

kubernetes(K8S)学习笔记P3:集群 YAML 文件(部署)

集群 YAML 文件(部署)4.集群 YAML 文件(部署)4.1 YAML 文件概述4.2YAML 文件书写格式4.2.1YAML 介绍4.2.2YAML 基本语法4.2.3YAML 支持的数据结构4.3资源清单描述方法4.3.1常用字段4.3.2字段解释4.4快速编写yml-->kubdectl cre…

数据结构由中序序列和后序序列构造二叉树

2022.11.19 由中序序列和后序序列构造二叉树任务描述相关知识编程要求测试说明C/C代码任务描述 本关任务要求采用中序遍历序列和后序遍历序列构造二叉树。 相关知识 给定一棵二叉树的中序遍历序列和后序遍历序列可以构造出这棵二叉树。例如后序序列是DEBFGCA,中序…

MySQL8.0优化 - 锁 - 从对待锁的态度划分:乐观锁、悲观锁

文章目录学习资料锁的不同角度分类锁的分类图如下从对待锁的态度划分:乐观锁、悲观锁悲观锁(Pessimistic Locking)乐观锁(Optimistic Locking)两种锁的适用场景学习资料 【MySQL数据库教程天花板,mysql安装…

Ajax笔记

Ajax笔记资源的请求方式一、概念1、Ajax作用2、jQuery中的Ajax二、$.get()函数的语法$.get()发起不带参数的请求$.get()发起带参数的请求三、$.post()函数的语法$.post()向服务器提交数据<font colorred>四、$.ajax()函数的语法使用$.ajax()发起GET请求使用$.ajax()发起P…

JSP使用

目录 简介 作用 创建 结构 常用脚本 声名脚本 表达式脚本 代码脚本 注释 九大内置对象 四大域对象 out与response.getWriter 静态引入 动态引入 EL表达式 作用 语法 取值顺序 获取指定参数 输出指定对象的数据 运算符 算数运算符 关系比较 逻辑运算符…

【Vue】使用 axios 发送ajax 请求

在 Vue 里面我们如何去发送一些 Ajax(阿贾克斯)请求 从远程的网站上获取一些数据。 假如我们有这样的接口的地址&#xff1a; https://www.xxxx.site 假设它是一个能跨域访问的接口。‍‍‍‍ 如果我们想去在我们的代码里面发这种请求&#xff0c;我该怎么做&#xff1f; 首…

Ubuntu 桌面系统升级

本文介绍 Ubuntu 桌面系统升级的两种方式&#xff0c;通过 UI 或命令行的方式&#xff0c;演示为 20.04 升级为 22.04。并介绍了 windows 的 Linux 子系统 wsl 的升级注意事项。 背景 之前在学习 ROS2 时&#xff0c;安装 ros-humble-desktop 出现依赖错误&#xff1a;无法修正…

[附源码]java毕业设计食材采购平台论文

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

AI智工作室11.19练习题解

F CodeForces - 710A 训练1(共12题) - Virtual Judge 我的代码 #include<iostream> using namespace std; int main() {char arr[10][10],a;int b,c,k0;cin>>a>>c; ba-a1; // cout<<b<<" "<<c<<endl; for(int i0;i<9…

使用VSCode编辑与编译WSL2下源代码

1. 安装WSL2 2. windows下安装VSCode 3. VSCode安装插件Remote Development 北京时间2019年5月3日&#xff0c;在 PyCon 2019 大会上&#xff0c;微软发布了 VS Code Remote&#xff0c;开启了远程开发的新时代&#xff01;这次发布包含了三款核心的全新插件&#xff0c;它们…