Java、Spring、Dubbo三者SPI机制原理与区别

news2024/10/1 23:45:39

Java、Spring、Dubbo三者SPI机制原理与区别

什么是SPI

SPI全称为Service Provider Interface,是一种动态替换发现的机制,一种解耦非常优秀的思想,SPI可以很灵活的让接口和实现分离,让api提供者只提供接口,第三方来实现,然后可以使用配置文件的方式来实现替换或者扩展,在框架中比较常见,提高框架的可扩展性。

简单来说SPI是一种非常优秀的设计思想,它的核心就是解耦、方便扩展

Java SPI机制–ServiceLoader

ServiceLoader是Java提供的一种简单的SPI机制的实现,Java的SPI实现约定了以下两件事:

  • 文件必须放在META-INF/services/目录底下
  • 文件名必须为接口的全限定名,内容为接口实现的全限定名

这样就能够通过ServiceLoader加载到文件中接口的实现。

来个demo

第一步,需要一个接口及其实现类

public interface LoadBalance {
}

public class RandomLoadBalance implements LoadBalance{
}

第二步,在META-INF/services/目录创建一个文件名LoadBalance全限定名的文件,文件内容为RandomLoadBalance的全限定名

image-20240417220053449

内容为实现类全限定名

image-20240417220139791

测试类

public class ServiceLoaderDemo {
    public static void main(String[] args) {
        ServiceLoader<LoadBalance> loadBalanceServiceLoader = ServiceLoader.load(LoadBalance.class);
        for (LoadBalance loadBalance : loadBalanceServiceLoader) {
            System.out.println("获取到负载均衡策略:" + loadBalance);
        }
    }
}

运行结果

image-20240417220312848

在实际的框架设计中,LoadBalance接口定义在框架内部,对于框架的使用者来说,要想自定义LoadBalance实现,嵌入到框架,仅仅只需要写接口的实现和spi文件即可。

实现原理

ServiceLoader中一段核心代码

image-20240417222043382

image-20240417222125291

首先获取一个fullName,就是META-INF/services/接口的全限定名

image-20240417222515865

然后通过ClassLoader获取到资源,接口的全限定名文件对应的资源,然后交给parse方法解析资源

image-20240417222651752

parse方法其实就是通过IO流读取文件的内容,这样就可以获取到接口的实现的全限定名

再后面就是通过反射实例化对象。

ServiceLoader实现原理比较简单,总结起来就是通过IO流读取META-INF/services/接口的全限定名文件的内容,然后反射实例化对象。

优缺点

  1. 浪费资源,例子中只有一个实现类,但是实际情况下可能会有很多实现类,而Java的SPI会全进行实例化,但是这些实现了不一定都用得着,所以就会白白浪费资源。
  2. 无法区分具体的实现,也就是这么多实现类,到底该用哪个实现呢?如果要判断具体使用哪个,只能依靠接口本身的设计,比如接口可以设计为一个策略接口,又或者接口可以设计带有优先级的,但是不论怎样设计,框架作者都得写代码进行判断。

总结来说,ServiceLoader无法做到按需加载或者按需获取某个具体的实现。

使用场景

  • 不需要选择具体的实现,每个被加载的实现都需要被用到
  • 虽然需要选择具体的实现,但是可以通过对接口的设计来解决

Spring SPI机制–SpringFactoriesLoader

Spring提供了一种SPI的实现SpringFactoriesLoader。

Spring的SPI机制的约定如下:

  • 配置文件必须在META-INF/目录下,文件名必须为spring.factories
  • 文件内容为键值对,一个键可以有多个值,只需要用逗号分割就行,同时键值都需要是类的全限定名,键和值可以没有任何类与类之间的关系,当然也可以有实现的关系。

Spring的SPI机制跟Java的不论是文件名还是内容约定都不一样。

来个demo

META-INF/目录下创建spring.factories文件,LoadBalance全限定名为键,RandomLoadBalance全限定名为值

image-20240417224501313

image-20240417224606838

配置类:

image-20240417224834969

测试类:

public class SpringFactoriesLoaderDemo {

    public static void main(String[] args) {
        List<LoadBalance> loadBalances = SpringFactoriesLoader.loadFactories(LoadBalance.class, MyConfig.class.getClassLoader());
        for (LoadBalance loadBalance : loadBalances) {
            System.out.println("获取到LoadBalance策略:" + loadBalance);
        }

    }
}

执行结果

image-20240417224917237

核心原理

image-20240417225522252

image-20240417225700763

跟Java实现的差不多,只不过读的是META-INF/目录下spring.factories文件内容,然后解析出来键值对。

使用场景

Spring的SPI机制在内部使用的非常多,尤其在SpringBoot中大量使用,SpringBoot启动过程中很多扩展点都是通过SPI机制来实现的。

1、自动装配

在SpringBoot3.0之前的版本,自动装配是通过SpringFactoriesLoader来加载的。

image-20240418225803440

但是SpringBoot3.0之后不再使用SpringFactoriesLoader,而是Spring重新从META-INF/spring/目录下的org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中读取了。

image-20240418225907822

2、PropertySourceLoader的加载

PropertySourceLoader用来解析application配置文件

image-20240418230007786

SpringBoot默认提供了 PropertiesPropertySourceLoader 和 YamlPropertySourceLoader两个实现,就是对应properties和yaml文件格式的解析。

image-20240418230147124

SpringBoot在加载PropertySourceLoader时就用了SPI机制

image-20240418230443560

image-20240418230545401

与Java SPI机制对比

  1. Spring的SPI机制对Java的SPI机制对进行了一些简化,Java的SPI每个接口都需要对应的文件,而Spring的SPI机制只需要一个spring.factories文件。
  2. 文件内容,Java的SPI机制文件内容必须为接口的实现类,而Spring的SPI并不要求键值对必须有什么关系,更加灵活。
  3. Spring的SPI机制提供了获取类限定名的方法loadFactoryNames,而Java的SPI机制是没有的。通过这个方法获取到类限定名之后就可以将这些类注入到Spring容器中,用Spring容器加载这些Bean,而不仅仅是通过反射。
  4. Spring的SPI也同样没有实现获取指定某个指定实现类的功能,所以要想能够找到具体的某个实现类,还得依靠具体接口的设计。(依旧无法获取指定实现类,依赖具体接口设计)

PropertySourceLoader就是一个策略接口,当配置文件是properties格式的时候,他可以找到解析properties格式的PropertiesPropertySourceLoader对象来解析配置文件。

Dubbo SPI机制–ExtensionLoader

ExtensionLoader是dubbo的SPI机制的实现类。每一个接口都会有一个自己的ExtensionLoader实例对象,这点跟Java的SPI机制是一样的。

Dubbo的SPI机制也做了以下几点约定:

  • 接口必须要加@SPI注解
  • 配置文件可以放在META-INF/services/META-INF/dubbo/internal/ META-INF/dubbo/META-INF/dubbo/external/这四个目录底下,文件名也是接口的全限定名
  • 内容为键值对,键为短名称(可以理解为spring中Bean的名称),值为实现类的全限定名

来个demo

接口上添加@SPI注解

@SPI
public interface LoadBalance {
}

修改Java的SPI机制测试时配置文件内容,改为键值对,因为Dubbo的SPI机制也可以从META-INF/services/目录下读取文件,所以没重写文件

image-20240421224847010

测试类&运行结果:

public class ExtensionLoaderDemo {

    public static void main(String[] args) {
        ExtensionLoader<LoadBalance> extensionLoader = ExtensionLoader.getExtensionLoader(LoadBalance.class);
        LoadBalance loadBalance = extensionLoader.getExtension("random");
        System.out.println("获取到random键对应的实现类对象:" + loadBalance);
    }
}

image-20240421225142120

通过ExtensionLoader的getExtension方法,传入键值对key值,这样就可以精确地找到key值对的实现类。

Dubbo的SPI机制解决了前面提到的无法获取指定实现类的问题。

dubbo核心机制

1、自适应机制

自适应,自适应扩展类的含义是说,基于参数,在运行时动态选择到具体的目标类,然后执行。

每个接口有且只能有一个自适应类,通过ExtensionLoader的getAdaptiveExtension方法就可以获取到这个类的对象,这个对象可以根据运行时具体的参数找到目标实现类对象,然后调用目标对象的方法。

@Adaptive
public class RandomLoadBalance implements LoadBalance{
}

测试一下:

public class ExtensionLoaderDemo {

    public static void main(String[] args) {
        ExtensionLoader<LoadBalance> extensionLoader = ExtensionLoader.getExtensionLoader(LoadBalance.class);

        LoadBalance adaptiveExtension = extensionLoader.getAdaptiveExtension();
        System.out.println("获取自适应 LoadBalance对象:" + adaptiveExtension);
    }
}
2、IOC和AOP

Dubbo实现一个轻量级的IOC和AOP的功能。

2.1、依赖注入

Dubbo依赖注入是通过setter注入的方式,注入的对象默认就是上面提到的自适应的对象,在Spring环境下可以注入Spring Bean。

public class RoundRobinLoadBalance implements LoadBalance {

    private LoadBalance loadBalance;
    
  	// 设置接口自适应对象
    public void setLoadBalance(LoadBalance loadBalance) {
        this.loadBalance = loadBalance;
    }

}

解读:RoundRobinLoadBalance中有一个setLoadBalance方法,参数LoadBalance,在创建RoundRobinLoadBalance的时候,在非Spring环境底下,Dubbo就会找到LoadBalance自适应对象然后通过反射注入。

记得文件中添加键值对:

roundrobin=com.lkl.spi.demo.RoundRobinLoadBalance

2.2、接口回调

Dubbo也提供了一些类似于Spring的一些接口的回调功能,如果类实现了Lifecycle接口,那么创建或者销毁的时候就会回调以下几个方法:

image-20240421230308592

在dubbo3.x的某个版本之后,dubbo提供了更多接口回调,比如说ExtensionPostProcessor、ExtensionAccessorAware,命名跟Spring的非常相似,作用也差不多。

2.3、自动包装

自动包装其实就是aop的功能实现,对目标对象进行代理,并且这个aop功能在默认情况下就是开启的。

在Dubbo中SPI接口的实现中,有一种特殊的类,被称为Wrapper类,这个类的作用就是来实现AOP的。

判断Wrapper类的唯一标准:这个类中必须要有这么一个构造参数,这个构造方法的参数只有一个,并且参数类型就是接口的类型,如下:

public class RoundRobinLoadBalance implements LoadBalance {

    private final LoadBalance loadBalance;

    public RoundRobinLoadBalance(LoadBalance loadBalance) {
        this.loadBalance = loadBalance;
    }

}

此时RoundRobinLoadBalance就是一个Wrapper类。

测试一下

image-20240421232239826

此时,虽然指定了random,但是实际获取到的是RoundRobinLoadBalance,而RoundRobinLoadBalance内部引用了RandomLoadBalance。

如果有很多的包装类,那么就会形成一个责任链条,一个套一个。

所以dubbo的aop跟spring的aop实现是不一样的,spring的aop底层是基于动态代理来的,而dubbo的aop其实算是静态代理,dubbo会自动组装这个代理,形成一条责任链。

默认类

@SPI注解的值对应的实现类

@SPI("random")
public interface LoadBalance {

}

此时random这个key对应的实现类就是默认实现,通过getDefaultExtension这个方法就可以获取到默认实现对象。

3、自动激活

所谓的自动激活,就是根据你的入参,动态地选择一批实现类返回给你。

自动激活的实现类上需要加上@Activate注解

@Activate
public interface RandomLoadBalance {

}

此时RandomLoadBalance就属于可以被自动激活的类。

获取自动激活类的方法是getActivateExtension,所以根据这个方法的入参,可以动态选择一批实现类。

自动激活这个机制在Dubbo一个核心的使用场景就是Filter过滤器链中。

Filter是dubbo中的一个扩展点,可以在请求发起前或者是响应获取之后就行拦截,作用有点像Spring MVC中的HandlerInterceptor。

image-20240421232932941

如上Filter有很多实现,为了能够区分Filter的实现是作用于provider的还是consumer端,可以用自动激活的机制来根据入参来动态选择一批Filter实现。

比如说ConsumerContextFilter这个Filter就作用于Consumer端。

image-20240421233024604

小结

  • Java的SPI实现比较简单,并没有什么其它功能;
  • Spring得益于自身的ioc和aop的功能,也没有实现太复杂的SPI机制,仅仅是对Java做了一点简化和优化;
  • dubbo的SPI机制为了满足自身框架的使用要求,实现的功能比较多,不仅将ioc和aop的功能集成到SPI机制中,还提供注入自适应和自动激活等功能,还具有获取指定接口实现类的功能。

参考资料

3种SPI机制

需要看的

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

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

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

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

相关文章

【嵌入式】keil5安装(同时兼容C51和STM32)

最近在开发STM32的时候&#xff0c;安装Keil5&#xff0c;遇到STM32和C51的共存的问题&#xff0c;在网上找了很多方法&#xff0c;又遇到一些bug&#xff0c;最终还是弄好了。因此将处理的过程记录下来&#xff0c;希望对遇到相同问题的朋友一些启发。 1、下载安装包 Keil P…

判断水仙花数(C语言)

一、N-S流程图&#xff1b; 二、运行结果&#xff1b; 三、源代码&#xff1b; # define _CRT_SECURE_NO_WARNINGS # include <stdio.h>int main() {//初始化变量值&#xff1b;int n 0;int b 0;int s 0;int g 0;int m 0;//提示用户&#xff1b;printf("请输入…

贪吃蛇游戏实现(VS编译环境)

贪吃蛇游戏 &#x1f955;个人主页&#xff1a;开敲&#x1f349; &#x1f525;所属专栏&#xff1a;C语言&#x1f353; &#x1f33c;文章目录&#x1f33c; 0. 前言 1. 游戏背景 2. 实现后游戏画面展示 3. 技术要求 4. Win32 API介绍 4.1 Win32 API 4.2 控制台程序 4.…

开启农业新篇章:山海鲸智慧农业解决方案全面解析

在农业领域&#xff0c;数字化转型已经成为推动农业发展的重要力量。山海鲸&#xff0c;作为农业科技创新的引领者&#xff0c;推出了全新的智慧农业解决方案&#xff0c;通过运用先进的物联网、大数据和人工智能等技术&#xff0c;为农业生产提供智能化、精准化的管理服务&…

c++模拟实现list——详讲双链表--链表

在C语言中我们已经模拟实现了list&#xff0c;现在对比c看看二者的区别 双链表————详讲 个人博客主页&#xff1a; 个人主页 个人码云 码云代码 文章目录 目录 文章目录 ​编辑 前言 一、list是什么&#xff1f; 二、list的使用 三、模拟实现list和搭建list的结构 1.节点结…

孩子用什么样的灯对眼睛没有伤害?分享五款防近视护眼台灯

随着生活条件逐渐提升&#xff0c;对台灯的需求也越来越大&#xff0c;不管在生活中还是工作中&#xff0c;灯具是必不可少的照明工具了&#xff0c;尤其是对于学生而言。很多家长都在寻找孩子用什么样的灯对眼睛没有伤害&#xff1f;建议最好选择一款合格、专业的护眼台灯&…

SpringBootWeb请求

文章目录 前言一、Postman介绍 二、简单参数三、实体参数四、数组集合参数五、日期参数六、JSON参数七、路径参数 前言 在上一篇文章中&#xff0c;已经基于SpringBoot的方式开发一个web应用&#xff0c;浏览器发起请求 /hello 后 &#xff0c;给浏览器返回字符串 “Hello Wor…

STM32之HAL开发——FSMC—扩展外部SRAM

SRAM读写时序 对SRAM进行读写数据时&#xff0c;它各个信号线的时序流程如下图 &#xff08;图一&#xff09;SRAM的读时序 &#xff08;图二&#xff09;SRAM的写时序 流程解释 主机使用地址信号线发出要访问的存储器目标地址&#xff1b;控制片选信号CS1#及CS2#使能存储器…

力扣HOT100 - 25. K 个一组翻转链表

解题思路&#xff1a; class Solution {public ListNode reverseKGroup(ListNode head, int k) {ListNode dum new ListNode(0, head);ListNode pre dum;ListNode end dum;while (end.next ! null) {for (int i 0; i < k && end ! null; i) {end end.next;}if …

生成式AI产品图谱全览:投资人、产品经理必备指南

以下是生成式AI产品图谱的核心要点&#xff0c;供投资人、产品经理等关注生成性AI领域的专业人士参考&#xff1a; 技术领域细分&#xff1a;依据AI技术所处理的媒介类型进行划分&#xff0c;包括文本处理、代码生成、图像处理、语音识别、视频分析、3D模型构建、音乐创作和游戏…

深度学习项目设置超参数 parser or dictionary

见惯了parser 有的人却是用字典读的&#xff1a; 将配置文件config.yaml读取到一个dictionary类型的变量cfg中&#xff0c; 后面出现了这样的语句&#xff1a;cfg["trainer"].get("sup_only_epoch", 1): 意思是&#xff1a;在config.yaml文件里key为trai…

Java客户端如何直接调用es的API

Java客户端如何直接调用es的API 一. 问题二. withJson 前言 这是我在这个网站整理的笔记,有错误的地方请指出&#xff0c;关注我&#xff0c;接下来还会持续更新。 作者&#xff1a;神的孩子都在歌唱 一. 问题 今天做项目的时候&#xff0c;想要直接通过java客户端调用es的api…

PHP反序列化漏洞原理(附带pikachu靶场演示)

1.反序列化概念 序列化:是将变量转换为可保存或传输的字符串的过程;实现函数是serialize()反序列化:就是在适当的时候把这个字符串再转化成原来的变量使用&#xff0c;就是序列化的逆过程。实现函数是unserialize() 直白一点就是&#xff1a;序列化是把对象转换成字节流&#…

基于java+springboot+vue实现的图书借还管理系统小程序(文末源码+Lw+ppt)23-1

摘 要 随着社会的发展&#xff0c;图书借还的管理形势越来越严峻。越来越多的借阅者利用互联网获得信息&#xff0c;但图书借还信息量大。为了方便借阅者更好的获得本图书借还信息&#xff0c;因此&#xff0c;设计一种安全高效的“共享书角”图书借还管理系统极为重要。 为…

python安装pytorch@FreeBSD

先上结论&#xff0c;最后在conda下安装成功了&#xff01; PyTorch是一个开源的人工智能深度学习框架&#xff0c;由Facebook人工智能研究院&#xff08;FAIR&#xff09;基于Torch库开发并维护。PyTorch提供了一个高效、灵活且易于使用的工具集&#xff0c;用于构建和训练深…

Matlab进阶绘图第51期—带填充等高线的三维特征渲染散点图

带填充等高线的三维特征渲染散点图是填充等高线图与特征渲染三维散点图的组合。 其中&#xff0c;填充等高线图与特征渲染的三维散点图的颜色用于表示同一个特征。 由于填充等高线图无遮挡但不直观&#xff0c;特征渲染的三维散点图直观但有遮挡&#xff0c;而将二者组合&…

【MySQL探索之旅】多表查询

&#x1f4da;博客主页&#xff1a;爱敲代码的小杨. ✨专栏&#xff1a;《Java SE语法》 | 《数据结构与算法》 | 《C生万物》 |《MySQL探索之旅》 |《Web世界探险家》 ❤️感谢大家点赞&#x1f44d;&#x1f3fb;收藏⭐评论✍&#x1f3fb;&#xff0c;您的三连就是我持续更…

快速排序题目SelectK问题(力扣75.颜色分类、力扣215.数组中的第K个最大元素、面试题17.14最小K个数)

力扣75.颜色分类 给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums &#xff0c;原地对它们进行排序&#xff0c;使得相同颜色的元素相邻&#xff0c;并按照红色、白色、蓝色顺序排列。 我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。 必须在不使用库内置的 sor…

使用Nexus搭建npm私服库

优质博文&#xff1a;IT-BLOG-CN 【1】下载nexus http://www.sonatype.com/download-oss-sonatype解压到本地即可&#xff1b; 【2】打开nexus-3.2.0-01-win64\nexus-3.2.0-01\bin&#xff1b;打开cmd&#xff08;必须使用cmd&#xff09; 执行nexus.exe /run&#xff1b;需要使…

贪吃蛇的简单实现(c语言)

前言&#xff1a;学完了C语言的基础语法&#xff0c;和一点数据结构的知识&#xff0c;拿贪吃蛇来练练手&#xff0c;并熟悉以前的知识。写完之后&#xff0c;有一种成就感&#xff0c;为以后的学习饱满激情。 注意这里的讲解是由部分到整体的思路。 目录 控制台不能是终端&am…