首页IT科技java中spi有什么作用(Java 中经常被提到的 SPI 到底是什么?)

java中spi有什么作用(Java 中经常被提到的 SPI 到底是什么?)

时间2025-05-02 14:41:02分类IT科技浏览4043
导读:Java 程序员在日常工作中经常会听到 SPI,而且很多框架都使用了 SPI 的技术,那么问题来了,到底什么是 SPI 呢?今天阿粉就带大家好好了解一下 SPI。...

Java 程序员在日常工作中经常会听到 SPI           ,而且很多框架都使用了 SPI 的技术                 ,那么问题来了     ,到底什么是 SPI 呢?今天阿粉就带大家好好了解一下 SPI           。

SPI 概念

SPI 全称是 Service Provider Interface      ,是一种 JDK 内置的动态加载实现扩展点的机制                 ,通过 SPI 技术我们可以动态获取接口的实现类           ,不用自己来创建                 。

这里提到了接口和实现类      ,那么 SPI 技术上具体有哪些技术细节呢?

接口:需要有一个功能接口; 实现类:接口只是规范                 ,具体的执行需要有实现类才行           ,所以不可缺少的需要有实现类; 配置文件:要实现 SPI 机制,必须有一个与接口同名的文件存放于类路径下面的 META-INF/services 文件夹中                 ,并且文件中的每一行的内容都是一个实现类的全路径; 类加载器 ServiceLoader:JDK 内置的一个类加载器                 ,用于加载配置文件中的实现类;

举个栗子

上面说了 SPI 的几个概念,接下来阿粉就通过一个栗子来带大家感受一下具体的用法     。

第一步

创建一个接口           ,这里我们创建一个解压缩的接口                 ,其中定义了压缩和解压的两个方法      。

package com.example.demo.spi; /** * <br> * <b>Function:</b><br> * <b>Author:</b>@author ziyou<br> * <b>Date:</b>2022-10-08 21:31<br> * <b>Desc:</b><br> */ public interface Compresser { byte[] compress(byte[] bytes); byte[] decompress(byte[] bytes); }

第二步

再写两个对应的实现类     ,分别是 GzipCompresser.java 和 WinRarCompresser.java 代码如下

package com.example.demo.spi.impl; import com.example.demo.spi.Compresser; import java.nio.charset.StandardCharsets; /** * <br> * <b>Function:</b><br> * <b>Author:</b>@author ziyou<br> * <b>Date:</b>2022-10-08 21:33<br> * <b>Desc:</b>无<br> */ public class GzipCompresser implements Compresser { @Override public byte[] compress(byte[] bytes) { return"compress by Gzip".getBytes(StandardCharsets.UTF_8); } @Override public byte[] decompress(byte[] bytes) { return "decompress by Gzip".getBytes(StandardCharsets.UTF_8); } } package com.example.demo.spi.impl; import com.example.demo.spi.Compresser; import java.nio.charset.StandardCharsets; /** * <br> * <b>Function:</b><br> * <b>Author:</b>@author ziyou<br> * <b>Date:</b>2022-10-08 21:33<br> * <b>Desc:</b>无<br> */ public class WinRarCompresser implements Compresser { @Override public byte[] compress(byte[] bytes) { return "compress by WinRar".getBytes(StandardCharsets.UTF_8); } @Override public byte[] decompress(byte[] bytes) { return "decompress by WinRar".getBytes(StandardCharsets.UTF_8); } }

第三步

创建配置文件           ,我们接着在 resources 目录下创建一个名为 META-INF/services 的文件夹                 ,在其中创建一个名为 com.example.demo.spi.Compresser 的文件     ,其中的内容如下:

com.example.demo.spi.impl.WinRarCompresser com.example.demo.spi.impl.GzipCompresser

注意该文件的名称必须是接口的全路径      ,文件里面的内容每一行都是一个实现类的全路径                 ,多个实现类就写在多行里面           ,效果如下                 。

第四步

有了上面的接口      ,实现类和配置文件                 ,接下来我们就可以使用 ServiceLoader 动态加载实现类           ,来实现 SPI 技术了,如下所示:

package com.example.demo; import com.example.demo.spi.Compresser; import java.nio.charset.StandardCharsets; import java.util.ServiceLoader; public class TestSPI { public static void main(String[] args) { ServiceLoader<Compresser> compressers = ServiceLoader.load(Compresser.class); for (Compresser compresser : compressers) { System.out.println(compresser.getClass()); } } }

运行的结果如下

可以看到我们正常的获取到了接口的实现类                 ,并且可以直接使用实现类的解压缩方法           。

原理

知道了如何使用 SPI 接下来我们来研究一下是如何实现的                 ,通过上面的测试我们可以看到,核心的逻辑是 ServiceLoader.load() 方法           ,这个方法有点类似于 Spring 中的根据接口获取所有实现类一样      。

点开 ServiceLoader 我们可以看到有一个常量 PREFIX                 ,如下所示     ,这也是为什么我们必须在这个路径下面创建配置文件           ,因为 JDK 代码里面会从这个路径里面去读取我们的文件                 。

同时又因为在读取文件的时候使用了 class 的路径名称                 ,因为我们使用 load 方法的时候只会传递一个 class     ,所以我们的文件名也必须是接口的全路径           。

通过 load 方法我们可以看到底层构造了一个 java.util.ServiceLoader.LazyIterator 迭代器。

在迭代器中的 parse 方法中      ,就获取了配置文件中的实现类名称集合                 ,然后在通过反射创建出具体的实现类对象存放到 LinkedHashMap<String,S> providers = new LinkedHashMap<>(); 中                 。

常用的框架

SPI 技术的使用非常广泛           ,比如在 Dubble      ,不过 Dubble 中的 SPI 有经过改造的                 ,还有我们很常见的数据库的驱动中也使用了 SPI           ,感兴趣的小伙伴可以去翻翻看,还有 SLF4J 用来加载不同提供商的日志实现类以及 Spring 框架等                 。

优缺点

前面介绍了 SPI 的原理和使用                 ,那 SPI 有什么优缺点呢?

优点

优点当然是解耦                 ,服务方只要定义好接口规范就好了,具体的实现可以由不同的 Jar 进行实现           ,只要按照规范实现功能就可以被直接拿来使用                 ,在某些场合会被进行热插拔使用     ,实现了解耦的功能。

缺点

一个很明显的缺点那就是做不到按需加载           ,通过源码我们看到了是会将所有的实现类都进行创建的                 ,这种做法会降低性能     ,如果某些实现类实现很耗时了话将影响加载时间           。同时实现类的命名也没有规范      ,让使用者不方便引用                 。

总结

阿粉今天给大家介绍了一个 SPI 的原理和实现                 ,感兴趣的小伙伴可以自己去尝试一下           ,多动手有利于加深记忆哦      ,如果觉得我们的文章有帮助                 ,欢迎点赞评论分享转发           ,让更多的人看到     。

更多优质内容欢迎关注公众号【Java 极客技术】,我准备了一份面试资料                 ,回复【bbbb07】免费领取           。希望能在这寒冷的日子里                 ,帮助到大家                 。

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
用u盘安装正版win10(如何使用U盘安装正版Win10系统 使用U盘安装Win10系统图文详细步骤) VMware如何安装wim系统(VMware 10 上安装Mac OS X 10.9 系统图文详解教程)