java SPI机制
创始人
2025-05-28 04:41:08

1.SPI介绍:

        spi就是接口在调用方这边,调用方规定了接口的标准,实现方根据接口的规定来实现.将调用方和服务提供者实现解耦,能够提升程序的扩展性和可维护性.修改或者替换服务不需要修改调用方,只需要替换服务提供者即可.slf4j是经典的spi机制.

        和api区别对比图如下:

一般模块之间都是通过通过接口进行通讯,那我们在服务调用方和服务实现方(也称服务提供者)之间引入一个“接口”。

当实现方提供了接口和实现,我们可以通过调用实现方的接口从而拥有实现方给我们提供的能力,这就是 API ,这种接口和实现都是放在实现方的。

当接口存在于调用方这边时,就是 SPI ,由接口调用方确定接口规则,然后由不同的厂商去根据这个规则对这个接口进行实现,从而提供服务。

举个通俗易懂的例子:公司 H 是一家科技公司,新设计了一款芯片,然后现在需要量产了,而市面上有好几家芯片制造业公司,这个时候,只要 H 公司指定好了这芯片生产的标准(定义好了接口标准),那么这些合作的芯片公司(服务提供者)就按照标准交付自家特色的芯片(提供不同方案的实现,但是给出来的结果是一样的)。

Java 中的 SPI 机制就是在每次类加载的时候会先去找到 class 相对目录下的 META-INF 文件夹下的 services 文件夹下的文件,将这个文件夹下面的所有文件先加载到内存中,然后根据这些文件的文件名和里面的文件内容找到相应接口的具体实现类,找到实现类后就可以通过反射去生成对应的对象,保存在一个 list 列表里面,所以可以通过迭代或者遍历的方式拿到对应的实例对象,生成不同的实现。

所以会提出一些规范要求:文件名一定要是接口的全类名,然后里面的内容一定要是实现类的全类名,实现类可以有多个,直接换行就好了,多个实现类的时候,会一个一个的迭代加载。

2.ServiceLoader 具体实现

想要使用java的SPI机制是需要依赖ServiceLoader来实现的,ServiceLoader是位于package java.util包下的.是一种加载服务的实现工具.

是由final修饰的不可修改的类型的,同时实现了Iterable接口,之所以实现迭代器是方便后面通过迭代器在配置文件中找到全部符合接口模板要求的的实现类.

下图是一个模板实践类的结构,在META-INF路径下我们可以看到其services的文件名Logger是spi模板的名字.

主要流程:

1.通过URL工具类从jar包的/META-INF/services目录下找到对应的文件

2.读取这个文件的名称,找到对应的spi接口(在实现类的META-INF 目录下这个文件的名字就是要实现的spi接口的名字)

3.通过InputStream将文件里面的具体实现类的全类名读取出来.

4.根据全类名来判断跟spi的接口是否是同一类型(service.isAssignableFrom(clazz)),如果是同一类型则通过反射将对象创建出来对应实例的对象.

5.将构造出来的实例对象添加到provicers的列表中.

package edu.jiangxuan.up.service;import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;public class MyServiceLoader {// 对应的接口 Class 模板private final Class service;// 对应实现类的 可以有多个,用 List 进行封装private final List providers = new ArrayList<>();// 类加载器private final ClassLoader classLoader;// 暴露给外部使用的方法,通过调用这个方法可以开始加载自己定制的实现流程。public static  MyServiceLoader load(Class service) {return new MyServiceLoader<>(service);}// 构造方法私有化private MyServiceLoader(Class service) {this.service = service;this.classLoader = Thread.currentThread().getContextClassLoader();doLoad();}// 关键方法,加载具体实现类的逻辑private void doLoad() {try {// 读取所有 jar 包里面 META-INF/services 包下面的文件,
//这个文件名就是接口名,然后文件里面的内容就是具体的实现类的路径加全类名Enumeration urls = classLoader.getResources("META-INF/services/" + service.getName());// 挨个遍历取到的文件while (urls.hasMoreElements()) {// 取出当前的文件URL url = urls.nextElement();System.out.println("File = " + url.getPath());// 建立链接URLConnection urlConnection = url.openConnection();urlConnection.setUseCaches(false);// 获取文件输入流InputStream inputStream = urlConnection.getInputStream();// 从文件输入流获取缓存BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));// 从文件内容里面得到实现类的全类名String className = bufferedReader.readLine();while (className != null) {// 通过反射拿到实现类的实例Class clazz = Class.forName(className, false, classLoader);// 如果声明的接口跟这个具体的实现类是属于同一类型,(可以理解为Java的一种多态,接口跟实现类、父类和子类等等这种关系。)则构造实例if (service.isAssignableFrom(clazz)) {Constructor constructor = (Constructor) clazz.getConstructor();S instance = constructor.newInstance();// 把当前构造的实例对象添加到 Provider的列表里面providers.add(instance);}// 继续读取下一行的实现类,可以有多个实现类,只需要换行就可以了。className = bufferedReader.readLine();}}} catch (Exception e) {System.out.println("读取文件异常。。。");}}// 返回spi接口对应的具体实现类列表public List getProviders() {return providers;}
}

        其实不难发现,SPI 机制的具体实现本质上还是通过反射完成的。即:我们按照规定将要暴露对外使用的具体实现类在 META-INF/services/ 文件下声明。

通过 SPI 机制能够大大地提高接口设计的灵活性,但是 SPI 机制也存在一些缺点,比如:

  1. 遍历加载所有的实现类,这样效率还是相对较低的;
  2. 当多个 ServiceLoader 同时 load 时,会有并发问题。

参考资料地址:

Java SPI 机制详解 | JavaGuide(Java面试+学习指南)

        

相关内容

热门资讯

百度发布文心一言,我想说几句 大家好,我是记得诚。 今天下午百度公司正式发布了文心一言,算是国内第一个...
掌握CentOS7环境下的Do... 文章目录Docker容器的网络模式Docker桥接网络模式Docker主机host网络模式Docke...
TimesNet复现结果 复现环境:RTX3090,22312GB显存,torch&...
加密月解密:概述,基础篇 加密月解密:概述,基础篇 2022找工作是学历、能力和运气的超强结合体&...
天涯海角门票(三亚天涯海角攻略... 今天给各位分享天涯海角门票的知识,其中也会对三亚天涯海角攻略,天涯海角门票进行解释,如果能碰巧解决你...
狮子林门票(狮子林门票预约官网... 今天给各位分享狮子林门票的知识,其中也会对狮子林门票预约官网进行解释,如果能碰巧解决你现在面临的问题...
石家庄特价机票(石家庄特价机票... 今天给各位分享石家庄特价机票的知识,其中也会对石家庄特价机票99元进行解释,如果能碰巧解决你现在面临...
深圳到北京火车时刻表(去北京的... 今天给各位分享深圳到北京火车时刻表的知识,其中也会对去北京的火车时刻表进行解释,如果能碰巧解决你现在...
【SpringBoot】76、... JPA是Java Persistence API的简称,中文名Java持久层API&#...
LoRa烟雾报警器的安装方式 LoRa烟雾报警器是一种常用的消防安全设备,它能够在发现火灾或烟雾时迅速发出警报&#x...
如家快捷酒店上海(如家快捷酒店... 本篇文章极速百科给大家谈谈如家快捷酒店上海,以及如家快捷酒店上海人民广场对应的知识点,希望对各位有所...
梅河口酒店(梅河口酒店排行榜)... 本篇文章极速百科给大家谈谈梅河口酒店,以及梅河口酒店排行榜对应的知识点,希望对各位有所帮助,不要忘了...
宇豪海逸酒店(宇豪海逸酒店地址... 本篇文章极速百科给大家谈谈宇豪海逸酒店,以及宇豪海逸酒店地址对应的知识点,希望对各位有所帮助,不要忘...
温州金球国豪大酒店(温州金球国... 本篇文章极速百科给大家谈谈温州金球国豪大酒店,以及温州金球国豪大酒店餐饮预订电话对应的知识点,希望对...
[数据分析与可视化] Pyth... 本文主要介绍GeoPandas的基本使用方法,以绘制简单的地图。GeoPandas是一...
stm32H7内部flash存... 目录一、慎用固件库1.HAL_FLASH_Program有问题,写入不正常2.采用直接...
厦门环岛路客栈(厦门环岛路客栈... 本篇文章极速百科给大家谈谈厦门环岛路客栈,以及厦门环岛路客栈图片对应的知识点,希望对各位有所帮助,不...
欣燕都连锁酒店(欣燕都连锁酒店... 本篇文章极速百科给大家谈谈欣燕都连锁酒店,以及欣燕都连锁酒店北京前门店对应的知识点,希望对各位有所帮...
大连火车站附近旅店(大连火车站... 本篇文章极速百科给大家谈谈大连火车站附近旅店,以及大连火车站附近旅店哪家有小姐对应的知识点,希望对各...
厦门至石家庄机票(厦门到石家庄... 今天给各位分享厦门至石家庄机票的知识,其中也会对厦门到石家庄机票查询进行解释,如果能碰巧解决你现在面...
Web系统优化实战 1. 课程介绍2. 系统优化原理 1.1. Web系统优化概述 1.1.1. 提高网站速度 1.1....
笨鸟学数据结构(绪论) 数据结构的定义按某种逻辑关系组织起来的一批数据,按一定的映象方式把它存放在计算机的存储...
经典卷积模型回顾24—利用模型... 模型剪枝是指在训练深度学习模型时将某些不重要的部分剪掉,以实现更快速、更高效的模型精度...
关于重庆通信学院的信息 重庆通... 本篇文章极速百科给大家谈谈重庆通信学院,以及对应的知识点,希望对各位有所帮助,不要忘了收藏本站喔。本...
新民到沈阳的火车(新民到沈阳的... 今天给各位分享新民到沈阳的火车的知识,其中也会对新民到沈阳的火车时间表和票价进行解释,如果能碰巧解决...
中国携程网(中国携程网上订票官... 本篇文章极速百科给大家谈谈中国携程网,以及中国携程网上订票官网对应的知识点,希望对各位有所帮助,不要...
全国各地旅游景点(全国各地旅游... 今天给各位分享全国各地旅游景点的知识,其中也会对全国各地旅游景点大全地图进行解释,如果能碰巧解决你现...
《MongoDB入门教程》第2... 本文将会介绍 MongoDB 复合索引的概念,以及如何创建复合索引。 复合索引 复合索...
Python 关键字globa... 变量作用域 一般在函数体外定义的变量成为全局变量,在函数内部定义的变量称为局部变量。 ...
力扣-《剑指offer》-链表... 目录 第一题:从尾到头打印链表 第二题:删除链表的节点 第三题ÿ...