Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >理解的Java中SPI机制

理解的Java中SPI机制

作者头像
每周聚焦
发布于 2025-03-20 10:02:42
发布于 2025-03-20 10:02:42
8100
代码可运行
举报
运行总次数:0
代码可运行

本文通过探析JDK提供的,在开源项目中比较常用的Java SPI机制,希望给大家在实际开发实践、学习开源项目提供参考。

1 SPI是什么

SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件。

整体机制图如下:

Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制。

系统设计的各个抽象,往往有很多不同的实现方案,在面向的对象的设计里,一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。 Java SPI就是提供这样的一个机制:为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。所以SPI的核心思想就是解耦

2 使用场景

概括地说,适用于:调用者根据实际使用需要,启用、扩展、或者替换框架的实现策略

比较常见的例子:

  • 数据库驱动加载接口实现类的加载 JDBC加载不同类型数据库的驱动
  • 日志门面接口实现类加载 SLF4J加载不同提供商的日志实现类
  • Spring Spring中大量使用了SPI,比如:对servlet3.0规范对ServletContainerInitializer的实现、自动类型转换Type Conversion SPI(Converter SPI、Formatter SPI)等
  • Dubbo Dubbo中也大量使用SPI的方式实现框架的扩展, 不过它对Java提供的原生SPI做了封装,允许用户扩展实现Filter接口

3 使用介绍

要使用Java SPI,需要遵循如下约定:

  • 1、当服务提供者提供了接口的一种具体实现后,在jar包的META-INF/services目录下创建一个以“接口全限定名”为命名的文件,内容为实现类的全限定名;
  • 2、接口实现类所在的jar包放在主程序的classpath中;
  • 3、主程序通过java.util.ServiceLoder动态装载实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到JVM;
  • 4、SPI的实现类必须携带一个不带参数的构造方法;

示例代码

步骤1、定义一组接口 (假设是org.foo.demo.IShout),并写出接口的一个或多个实现,(假设是org.foo.demo.animal.Dog、org.foo.demo.animal.Cat)。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 代码解读复制代码public interface IShout {
    void shout();
}
public class Cat implements IShout {
    @Override
    public void shout() {
        System.out.println("miao miao");
    }
}
public class Dog implements IShout {
    @Override
    public void shout() {
        System.out.println("wang wang");
    }
}

步骤2、在 src/main/resources/ 下建立 /META-INF/services 目录, 新增一个以接口命名的文件 (org.foo.demo.IShout文件),内容是要应用的实现类(这里是org.foo.demo.animal.Dog和org.foo.demo.animal.Cat,每行一个类)。

文件位置

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 代码解读复制代码- src
    -main
        -resources
            - META-INF
                - services
                    - org.foo.demo.IShout

文件内容

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 代码解读复制代码org.foo.demo.animal.Dog
org.foo.demo.animal.Cat

步骤3、使用 ServiceLoader 来加载配置文件中指定的实现。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 代码解读复制代码public class SPIMain {
    public static void main(String[] args) {
        ServiceLoader<IShout> shouts = ServiceLoader.load(IShout.class);
        for (IShout s : shouts) {
            s.shout();
        }
    }
}

代码输出:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 代码解读复制代码wang wang
miao miao

4 原理解析

首先看ServiceLoader类的签名类的成员变量:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 代码解读复制代码public final class ServiceLoader<S> implements Iterable<S>{
private static final String PREFIX = "META-INF/services/";

    // 代表被加载的类或者接口
    private final Class<S> service;

    // 用于定位,加载和实例化providers的类加载器
    private final ClassLoader loader;

    // 创建ServiceLoader时采用的访问控制上下文
    private final AccessControlContext acc;

    // 缓存providers,按实例化的顺序排列
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

    // 懒查找迭代器
    private LazyIterator lookupIterator;
  
    ......
}

参考具体ServiceLoader具体源码,代码量不多,加上注释一共587行,梳理了一下,实现的流程如下:

  • 1 应用程序调用ServiceLoader.load方法 ServiceLoader.load方法内先创建一个新的ServiceLoader,并实例化该类中的成员变量,包括:
    • loader(ClassLoader类型,类加载器)
    • acc(AccessControlContext类型,访问控制器)
    • providers(LinkedHashMap<String,S>类型,用于缓存加载成功的类)
    • lookupIterator(实现迭代器功能)
  • 2 应用程序通过迭代器接口获取对象实例 ServiceLoader先判断成员变量providers对象中(LinkedHashMap<String,S>类型)是否有缓存实例对象,如果有缓存,直接返回。 如果没有缓存,执行类的装载,实现如下:
  • (1) 读取META-INF/services/下的配置文件,获得所有能被实例化的类的名称,值得注意的是,ServiceLoader可以跨越jar包获取META-INF下的配置文件,具体加载配置的实现代码如下:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 代码解读复制代码        try {
            String fullName = PREFIX + service.getName();
            if (loader == null)
                configs = ClassLoader.getSystemResources(fullName);
            else
                configs = loader.getResources(fullName);
        } catch (IOException x) {
            fail(service, "Error locating configuration files", x);
        }
  • (2) 通过反射方法Class.forName()加载类对象,并用instance()方法将类实例化。
  • (3) 把实例化后的类缓存到providers对象中,(LinkedHashMap<String,S>类型) 然后返回实例对象。

5 总结

优点: 使用Java SPI机制的优势是实现解耦,使得第三方服务模块的装配控制的逻辑与调用者的业务代码分离,而不是耦合在一起。应用程序可以根据实际业务情况启用框架扩展或替换框架组件。

缺点

  • 虽然ServiceLoader也算是使用的延迟加载,但是基本只能通过遍历全部获取,也就是接口的实现类全部加载并实例化一遍。如果你并不想用某些实现类,它也被加载并实例化了,这就造成了浪费。获取某个实现类的方式不够灵活,只能通过Iterator形式获取,不能根据某个参数来获取对应的实现类。
  • 多个并发多线程使用ServiceLoader类的实例是不安全的。

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
JAVA SPI 是怎么实现的?
SPI(Service Provider Interface) ,是 JDK 内置的一种提供发现机制。SPI 是一种动态替换发现的机制。
王小明_HIT
2020/06/04
7460
JAVA SPI  是怎么实现的?
【Java——SPI机制详解】
SPI(Service Provider Interface),是JDK内置的一种服务提供发现机制,可以用来启用框架扩展和替换组件,主要是被框架的开发人员使用,比如java.sql.Driver接口,其他不同厂商可以针对同一接口做出不同的实现,MySQL和PostgreSQL都有不同的实现提供给用户,而Java的SPI机制可以为某个接口寻找服务实现。Java中SPI机制主要思想是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要,其核心思想就是 解耦。
奥耶可乐冰
2024/05/31
1.6K0
Java SPI (Service Provider Interface) 机制详解
SPI 全称:Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的接口,它可以用来启用框架扩展和替换组件。
一个会写诗的程序员
2021/02/04
9.3K0
java Spi与SpringFactoriesLoader
spring的SpringFactoriesLoader是spring框架内部工具类,在 Spring boot 应用启动的过程中,这个类的工作很重要, 启动逻辑使用该类从classpath上所有jar包中找出各自的 META-INF/spring.factories 属性文件,并分析出其中定义的工厂类。这些工厂类进而被启动逻辑使用,应用于进一步初始化工作。其使用的方式和java的spi基本类似,我们可以先学习java的spi而且进一步学习SpringFactoriesLoader。
用户5546570
2021/02/22
4510
java Spi与SpringFactoriesLoader
聊聊 Java SPI 机制
Java SPI 是基于接口的编程+策略模式+约定配置文件组合实现的动态加载机制,能够很方便的为某个接口寻找服务实现的机制。
码猿技术专栏
2023/05/01
2850
聊聊 Java SPI 机制
Java-深入理解ServiceLoader类与SPI机制
对于Java中的Service类和SPI机制的透彻理解,也算是对Java类加载模型的掌握的不错的一个反映。
Fisherman渔夫
2020/02/17
3.1K0
锦囊篇|Java中的SPI机制
Service Provider Interface翻译成中文就是服务提供接口,简称SPI,它是JDK内置的一种机制,用途就是本地服务发现和提供。
ClericYi
2020/09/10
5030
锦囊篇|Java中的SPI机制
SPI都不知道?还敢说懂Dubbo?面试官怼的我哑口无言啊!!!
 为什么要讲SPI呢?因为在Dubbo就用到了SPI机制,所以掌握了这部分对于后面的学习还是很有帮助的。
用户4919348
2021/07/29
3540
SPI都不知道?还敢说懂Dubbo?面试官怼的我哑口无言啊!!!
SPIJava SPIDubbo SPI案例原理
SPI 全称为(Service Provider Interface),是JDK内置的一种服务提供发现机制。一个服务(Service)通常指的是已知的接口或者抽象类,服务提供方就是对这个接口或者抽象类的实现,然后按照SPI 标准存放到资源路径META-INF/services目录下,文件的命名为该服务接口的全限定名。
spilledyear
2018/12/18
6080
JAVA的SPI机制
SPI(Service Provider Interface): ,“服务提供者接口”,是指在 服务使用方 角度提出的“接口要求”,是对“服务提供方”提出的约定,简单说就是:“我需要这样的服务,现在你们来满足”。
Java宝典
2021/01/28
3720
不懂Java SPI机制,怎么进大厂
在日常的项目开发中,我们为了提升程序的扩展性,经常使用面向接口的编程思想进行编程。这不仅体现了程序设计对于修改关闭,对于扩展开放的程序设计原则,同时也实现了程序可插拔。那么本文所阐述的SPI机制正是这种编程思想的体现。今天就和大家聊聊SPI到底是个什么鬼。顺便和大家一起看下Seata框架中是怎么使用SPI机制来实现框架扩展的。
慕枫技术笔记
2023/03/20
6880
不懂Java SPI机制,怎么进大厂
JDBC【4】-- SPI底层原理解析
SPI,即是Service Provider Interface,是一种服务提供(接口实现)发现机制,可以通过ClassPath路径下的META-INF/Service文件查找文件,加载里面定义的类。
秦怀杂货店
2020/11/28
4870
JDBC【4】-- SPI底层原理解析
Java SPI机制浅析
在平时开发项目的过程中,相信很多读者都看到过这样的目录,/META-INF/services目录,该目录下的文件名是接口的全称,其内容是具体的接口实现。这就是使用了SPI机制。如:
孟君
2019/09/17
5020
Java SPI机制浅析
Java SPI机制总结系列之万字详细图解Java SPI机制源码分析
我在《Java SPI机制总结系列之开发入门实例》一文当中,分享了Java SPI的玩法,但是这只是基于表面的应用。若要明白其中的原理实现,还需深入到底层源码,分析一番。
朱季谦
2023/11/11
1.1K1
Java SPI机制总结系列之万字详细图解Java SPI机制源码分析
你说说对Java中SPI的理解吧
最近在面试的时候被问到SPI了,没回答上来,主要也是自己的原因,把自己给带沟里去了,因为讲到了类加载器的双亲委派模型,后面就被问到了有哪些是破坏了双亲委派模型的场景,然后我就说到了SPI,JNDI,以及JDK9的模块化都破坏了双亲委派。 然后就被问,那你说说对Java中的SPI的理解吧。然后我就一脸懵逼了,之前只是知道它会破坏双亲委派,也知道是个怎么回事,但是并没有深入了解,那么这次我就好好的来总结一下这个知识吧。
纪莫
2020/12/08
3460
温习 SPI 机制 (Java SPI 、Spring SPI、Dubbo SPI)
SPI 全称为 Service Provider Interface,是一种服务发现机制。
勇哥java实战
2024/10/29
2400
温习 SPI 机制 (Java SPI 、Spring SPI、Dubbo SPI)
深入理解 Java 中 SPI 机制
SPI(Service Provider Interface),是JDK内置的一种服务提供发现机制,本文由浅入深地介绍了Java SPI机制。
2020labs小助手
2019/09/09
8520
Java SPI机制的运行原理是什么?
SPI的全称是(Service Provider Interface)是服务提供接口的意思。如果我们不写框架性代码或者开发插件的话,对于SPI机制可能不会那么熟悉,但如果我们阅读诸如Dubbo、JDBC数据库驱动包、Spring以及最近比较流行的Spring Boot相关starter组件源码的话,就会发现SPI机制及其思想在这些框架中有大量的应用。
用户5927304
2019/07/30
1.9K0
Java SPI机制的运行原理是什么?
你了解过Java的SPI机制吗?再不了解你就Out了!
SPI在Java中的全称为Service Provider Interface,是JDK内置的一种服务提供发现机制,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件。
冰河
2020/10/29
9060
你了解过Java的SPI机制吗?再不了解你就Out了!
Java基础之SPI机制
在前几天的译文 Java中的类加载器 中有部分关于ContextClassLoader的内容,涉及到SPI机制,本文将学习下相关知识。
码代码的陈同学
2018/08/23
6380
Java基础之SPI机制
相关推荐
JAVA SPI 是怎么实现的?
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验