什么是SPI机制?

SPI机制( Service Provider Interface)是Java的一种服务发现机制,为了方便应用扩展。那什么是服务发现机制?简单来说,就是你定义了一个接口,但是不提供实现,接口实现由其他系统应用实现。你只需要提供一种可以找到其他系统提供的接口实现类的能力或者说机制.

SPI机制在Java中有很广泛的运用,比如:eclipse和idea里的插件使用就是通过SPI机制实现的。开发工具提供一个扩展接口,具体的实现由插件开发者实现,开发工具提供一种服务发现机制来找到具体插件的实现,这就达到了插件的安装效果。从而可以使用插件服务。如果不需要某一插件,只需要删除某一插件的实现类,开发工具找不到具体的插件实现,这就达到了插件的卸载效果。不管是安装还是卸载都不会影响其他代码,其他服务。非常方便的实现了可插拔的效果。

JDBC中数据库连接驱动也使用了SPI机制,来达到适配不同DB数据库的效果。

SPI机制除了在jdk里有运用,在springboot中也用到了。springboot自动装配中"查找spring.factories 文件步骤"就是基于SPI的部分设计思想实现的。

SPI 有如下的好处:

不需要改动源码就可以实现扩展,解耦。

实现扩展对原来的代码几乎没有侵入性。

只需要添加配置就可以实现扩展,符合开闭原则。

API 和 SPI 区别

API:大多数情况下,都是实现方制定接口并完成对接口的实现,调用方仅仅依赖接口调用。

SPI :是调用方来制定接口规范,提供给外部来实现,调用方在调用时则选择自己需要的外部实现。

SPI实现服务接口与服务实现的解耦:

  • 服务提供者(如 springboot starter)提供出 SPI 接口,让客户端去自定义实现。
  • 客户端(普通的 springboot 项目)即可通过本地注册的形式,将实现类注册到服务端,轻松实现可插拔。

简单实现

定义接口

package com.test.service;

public interface ISpi {
void say();
}

第一个实现类:

package com.test.service.impl;

import com.test.service.ISpi;

public class FirstSpiImpl implements ISpi {

    @Override
public void say() {
System.out.println("我是第一个SPI实现类");
}
}

第二个实现类:

package com.test.service.impl;

import com.test.service.ISpi;

public class SecondSpiImpl implements ISpi {

    @Override
public void say() {
System.out.println("我是第二个SPI实现类");
}
}

在resources目录下新建META-INF/services目录,并且在这个目录下新建一个与上述接口的全限定名一致的文件,在这个文件中写入接口的实现类的全限定名,并写上需要动态加载的实现类的全路径名。

#com.test.service.impl.FirstSpiImpl
com.test.service.impl.SecondSpiImpl

ServiceLoader

ServiceLoader是JDK提供的专门用于实现SPI机制的类。位于java.util.ServiceLoader

ServiceLoader类的构造函数被私有化了。所以构建ServiceLoader对象只能通过ServiceLoader.load()方法。该方法有两个重载

使用ServiceLoader时可选择是否用自定义类加载器来加载目标类。也可默认使用应用程序类加载器加载。

jdk通过ServiceLoader类去ClassPath下的 “META-INF/services/”(此路径约定成俗) 路径里查找相应的接口实现类。ServiceLoader类核心功能就两个点,都在ServiceLoader的内部类LazyIterator中:

  • 查找相应接口对应实现类:hasNextService()
  • 加载相应接口实现类到虚拟机内:nextService()
public final class ServiceLoader<S> implements Iterable<S> {

    //扫描目录前缀
private static final String PREFIX = "META-INF/services/"; // 被加载的类或接口
private final Class<S> service; // 用于定位、加载和实例化实现方实现的类的类加载器
private final ClassLoader loader; // 上下文对象
private final AccessControlContext acc; // 按照实例化的顺序缓存已经实例化的类
private LinkedHashMap<String, S> providers = new LinkedHashMap<>(); // 懒查找迭代器
private java.util.ServiceLoader.LazyIterator lookupIterator; // 私有内部类,提供对所有的service的类的加载与实例化
private class LazyIterator implements Iterator<S> {
Class<S> service;
ClassLoader loader;
Enumeration<URL> configs = null;
String nextName = null; //...
private boolean hasNextService() {
if (configs == null) {
try {
//获取目录下所有的类 扫描目录前缀(META-INF/services/)+ 相应接口全限定名
String fullName = PREFIX + service.getName();
//该loader是构造ServiceLoader类时设置。可传入自定义类加载器,如未传入,则默认应用程序类加载器
if (loader == null)
//在系统中查找资源,注意查找资源的加载器是从当前线程上下文中获取。也就是默认的应用程序类加载器。所以能加载到第三方jar包下的classpath路径。
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
} catch (IOException x) {
//...
}
//....
}
} private S nextService() {
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
//反射加载类
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
}
try {
//实例化
S p = service.cast(c.newInstance());
//放进缓存
providers.put(cn, p);
return p;
} catch (Throwable x) {
//..
}
//..
}
}
}

应用程序通过迭代器接口获取对象实例,这里首先会判断 providers 对象中是否有实例对象:

  • 有实例,那么就返回
  • 没有,执行类的装载步骤,具体类装载实现如下:

LazyIterator#hasNextService 读取 META-INF/services 下的配置文件,获得所有能被实例化的类的名称,并完成 SPI 配置文件的解析

LazyIterator#nextService 负责实例化 hasNextService() 读到的实现类,并将实例化后的对象存放到 providers 集合中缓存



应用案例

Java定义了一套JDBC的接口,但并未提供具体实现类,而是在不同厂商提供的数据库实现包。

一般要根据自己使用的数据库驱动jar包,比如我们最常用的MySQL,其mysql-jdbc-connector.jar 里面就有:

小结

JDK中的SPI实现,是由ServiceLoader类根据自定义传入类加载器或者应用程序类加载器在约定好的固定路径下(ClassPath:META-INF/services/)去查找和加载第三方接口实现类。

注意:要使用JDK中的SPI机制有几个前提条件

  • 服务提供方必须实现目标接口
  • 服务提供方必须在自身ClassPath:META-INF/services/路径下建立文件,文件名为目标接口全限定名。文件内容为实现目标接口的具体实现类全限定名

Java 的 SPI 机制的更多相关文章

  1. java 的SPI机制

    今天看到spring mvc 使用Java Validation Api(JSR-303)进行校验,需要加载一个 其具体实现(比如Hibernate Validator), 本来没有什么问题,但是突然 ...

  2. Java的SPI机制与简单的示例

    一.SPI机制 这里先说下SPI的一个概念,SPI英文为Service Provider Interface单从字面可以理解为Service提供者接口,正如从SPI的名字去理解SPI就是Service ...

  3. Java之SPI机制

    之前开阿里的HSF框架,里面用到了Java的SPI机制,今天闲暇的时候去了解了一下,通过写博客来记录一下 SPI的全名为Service Provider Interface,我对于该机制的理解是为接口 ...

  4. Java的Spi机制心得

    Java spi : 是Java EE 给服务供应商提供的接口,供应商遵循接口契约提供自己的实现.. 简单来讲就是为某个接口寻找服务实现的机制. 在看JDBC源码当看到DriverManage.get ...

  5. 深入理解 Java 中 SPI 机制

    本文首发于 vivo互联网技术 微信公众号 链接:https://mp.weixin.qq.com/s/vpy5DJ-hhn0iOyp747oL5A作者:姜柱 SPI(Service Provider ...

  6. JAVA中SPI机制

    之前研究dubbo的时候就很好奇,里面各种扩展机制,期间也看过很多关于SPI的机制,今日有缘再度看到有文章总结,故记录一下, 首先了解一下 JAVA中SPI简单的用法 可参考这篇文章,https:// ...

  7. Java的SPI机制

    目录 1. 什么是SPI 2. 为什么要使用SPI 3. 关于策略模式和SPI的几点区别 4. 使用介绍或者说约定 4.1 首先介绍几个名词 4.2 约定 5. 具体的demo实现 5.1 创建服务提 ...

  8. Java spi机制浅谈

    最近看到公司的一些框架和之前看到的开源的一些框架的一些服务发现和接入都采用了java的spi机制. 所以简单的总结下java spi机制的思想. 我们系统里抽象的各个模块,往往有很多不同的实现方案,比 ...

  9. 聊聊Java SPI机制

    一.Java SPI机制 SPI(Service Provider Interface)是JDK内置的服务发现机制,用在不同模块间通过接口调用服务,避免对具体服务服务接口具体实现类的耦合.比如JDBC ...

  10. 你应该了解的 Java SPI 机制

    前言 不知大家现在有没有去公司复工,我已经在家办公将近 3 周了,同时也在家呆了一个多月:还好工作并没有受到任何影响,我个人一直觉得远程工作和 IT 行业是非常契合的,这段时间的工作效率甚至比在办公室 ...

随机推荐

  1. android 下载安装包更新app

    1下载 public void download(String url) { Utils.Loge("download",url); Uri uri = Uri.parse(url ...

  2. Spring 笔记二 IOC

    1.IOC IOC:Inversion  od  controller ,控制反转,控制的是资源的获取方式,由原来的自己创建 new 变成 Spring 容器创建. DI:Dependency  In ...

  3. 【Unity】2021接Bugly踩坑记录

    写在前面 因为在工作项目中用到Bugly,所以我在自己的测试工程中尝试接入Bugly,却没有成功,明明一切是按照说明书操作,为什么会不成功?当时在网上找了很久的资料,最后试成功了,这里把当时遇到的问题 ...

  4. Collectors类的静态工厂方法-《Java 8实战》笔记

  5. 拉勾java核心类库--String类

    String类 @author :magiclx 摘要:String类是用final进行修饰的,关于字符串的一个类,其中包含很多个方法 关键词:String,常量池,正则表达式,数组转化 正文: 参考 ...

  6. golang学习路线

    一.golang基础 李文周的博客:https://www.liwenzhou.com/ 视频教程:https://link.juejin.cn/?target=https%3A%2F%2Fwww.b ...

  7. 【前端样式】关于Element-plus 菜单 unique-opened不生效的问题

    需要保证el-sub-menu 的唯一性,即index得写在el-sub-menu上,否则 unique-opened 属性不生效.

  8. windows音频设备图像隔离audiodg.exe占用内存高(停止与重启audiodg服务)

    首先想到的办法是结束该进程,于是在任务管理器里结束进程后,内存是释放了,但是发现发现电脑没有声音去到电脑的system32目录下双击audiodg.exe后任然没有声音解决方法如下(重启audiodg ...

  9. VSCODE C# 运行 找不到任务"BUILD"----C#常用命令

    使用 Visual Studio Code 创建 .NET 类库 - .NET | Microsoft Docs 安装vscode.vscode c#相关拓展.MINIGW64 1.创建文件夹 2.用 ...

  10. Android 音视频 - MediaCodec 编解码音视频

    我们知道 Camera 采集回传的是 YUV 数据,AudioRecord 是 PCM,我们要对这些数据进行编码(压缩编码),这里我们来说在 Android 上音视频编解码逃不过的坑-MediaCod ...