Java SPI 与 Spring SPI

Java SPI 是JDK 原生 的服务发现机制,基于META-INF/services/目录实现,遵循「接口定义 - 实现类 - 配置文件」的约定;Spring SPI 是 Spring 框架对 Java SPI 的扩展增强 ,核心基于SpringFactoriesLoader类,通过META-INF/spring.factories(Spring Boot 2.7 + 推荐META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports)实现,支持按类型批量加载实现类,更适配 Spring 生态。

下面通过统一的业务场景 (定义一个MessageService消息服务接口,提供不同实现),分别实现 Java SPI 和 Spring SPI,包含完整代码、配置步骤和测试用例。

一、基础准备:定义公共接口

先创建一个通用的服务接口,作为两种 SPI 机制的统一规范,后续所有实现类都需实现该接口。

java

运行

复制代码
/**
 * 公共服务接口:消息服务
 * 作为Java SPI和Spring SPI的统一规范
 */
public interface MessageService {
    /**
     * 发送消息方法
     * @return 消息内容
     */
    String sendMessage();
}

二、Java SPI 完整实现

步骤 1:创建 Java SPI 的接口实现类

创建 2 个实现类,分别模拟「短信消息服务」和「邮件消息服务」,实现MessageService接口:

java

运行

复制代码
/**
 * Java SPI 实现类1:短信消息服务
 */
public class SmsMessageService implements MessageService {
    @Override
    public String sendMessage() {
        return "Java SPI -> 发送短信消息";
    }
}

/**
 * Java SPI 实现类2:邮件消息服务
 */
public class EmailMessageService implements MessageService {
    @Override
    public String sendMessage() {
        return "Java SPI -> 发送邮件消息";
    }
}

步骤 2:创建 Java SPI 配置文件(核心约定)

Java SPI 要求严格遵循目录约定 :在项目的resources目录下,创建META-INF/services/子目录,然后创建一个以接口全限定类名为文件名 的纯文本文件,文件内容为实现类的全限定类名(多个实现类每行一个)。

目录结构:

plaintext

复制代码
resources/
└── META-INF/
    └── services/
        └── com.example.spi.MessageService  # 接口全限定类名作为文件名
文件内容(com.example.spi.MessageService):

plaintext

复制代码
com.example.spi.impl.SmsMessageService
com.example.spi.impl.EmailMessageService

注意:文件中不能有多余的空格、空行(避免加载失败),实现类必须是完整的全限定类名(包名 + 类名)。

步骤 3:Java SPI 服务加载与测试

使用 JDK 原生的java.util.ServiceLoader类加载服务实现,该类是 Java SPI 的核心加载器 ,通过load(接口类)方法加载所有配置的实现类,遍历即可使用。

java

运行

复制代码
import java.util.ServiceLoader;

/**
 * Java SPI 测试类
 */
public class JavaSpiTest {
    public static void main(String[] args) {
        // 1. 通过ServiceLoader加载MessageService的所有实现类
        ServiceLoader<MessageService> serviceLoader = ServiceLoader.load(MessageService.class);
        
        // 2. 遍历加载的实现类并调用方法
        for (MessageService messageService : serviceLoader) {
            System.out.println(messageService.sendMessage());
        }
    }
}

Java SPI 运行结果:

plaintext

复制代码
Java SPI -> 发送短信消息
Java SPI -> 发送邮件消息

三、Spring SPI 完整实现(基于 SpringFactoriesLoader)

Spring SPI 核心依赖org.springframework.core.io.support.SpringFactoriesLoader类,无需手动遍历,直接通过静态方法批量加载所有实现类,支持 Spring 的 Bean 生命周期管理,是 Spring 框架自动配置、扩展点实现的核心机制。

步骤 1:创建 Spring SPI 的接口实现类

同样实现MessageService接口,新增 2 个实现类(模拟「微信消息服务」和「钉钉消息服务」):

java

运行

复制代码
/**
 * Spring SPI 实现类1:微信消息服务
 */
public class WechatMessageService implements MessageService {
    @Override
    public String sendMessage() {
        return "Spring SPI -> 发送微信消息";
    }
}

/**
 * Spring SPI 实现类2:钉钉消息服务
 */
public class DingdingMessageService implements MessageService {
    @Override
    public String sendMessage() {
        return "Spring SPI -> 发送钉钉消息";
    }
}

步骤 2:创建 Spring SPI 配置文件(核心约定)

Spring SPI 核心配置目录为META-INF/,配置文件名为spring.factories (Spring Boot 1.x/2.x 主流用法,2.7 + 虽推荐新方式,但 spring.factories 仍兼容),文件格式为「接口全限定类名 = 实现类全限定类名 」,多个实现类用英文逗号分隔。

目录结构:

plaintext

复制代码
resources/
└── META-INF/
    └── spring.factories  # Spring SPI 核心配置文件
文件内容(spring.factories):

properties

复制代码
# Spring SPI 配置:MessageService接口的实现类
com.example.spi.MessageService=com.example.spi.impl.WechatMessageService,com.example.spi.impl.DingdingMessageService

注意:等号前后无多余空格,多个实现类逗号后无空格,否则会导致类加载失败。

步骤 3:Spring SPI 服务加载与测试

使用 Spring 核心的SpringFactoriesLoader类,通过loadFactories(接口类, 类加载器)方法批量加载所有实现类(自动实例化),直接调用方法即可。

依赖说明:

需引入 Spring 核心依赖(Maven),否则无法使用SpringFactoriesLoader

xml

复制代码
<!-- Spring Core 核心依赖 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>5.3.29</version> <!-- 适配Spring Boot 2.7.x,可根据实际版本调整 -->
    <scope>compile</scope>
</dependency>
测试代码:

java

运行

复制代码
import org.springframework.core.io.support.SpringFactoriesLoader;
import java.util.List;

/**
 * Spring SPI 测试类
 */
public class SpringSpiTest {
    public static void main(String[] args) {
        // 1. 通过SpringFactoriesLoader批量加载MessageService的所有实现类(自动实例化)
        List<MessageService> messageServices = SpringFactoriesLoader.loadFactories(
                MessageService.class,
                SpringSpiTest.class.getClassLoader() // 类加载器,使用当前类的类加载器即可
        );
        
        // 2. 遍历实现类并调用方法
        for (MessageService messageService : messageServices) {
            System.out.println(messageService.sendMessage());
        }
    }
}

Spring SPI 运行结果:

plaintext

复制代码
Spring SPI -> 发送微信消息
Spring SPI -> 发送钉钉消息

四、Java SPI 与 Spring SPI 核心区别

特性 Java SPI(JDK 原生) Spring SPI(SpringFactoriesLoader)
核心加载类 java.util.ServiceLoader org.springframework.core.io.support.SpringFactoriesLoader
配置文件目录 / 名称 META-INF/services/接口全限定类名 META-INF/spring.factories
配置格式 每行一个实现类全限定类名 接口全限定类名 = 实现类 1, 实现类 2(逗号分隔)
加载方式 懒加载(遍历迭代器时才实例化) 立即加载(调用方法直接返回所有实例化对象)
遍历方式 需手动遍历ServiceLoader迭代器 无需手动遍历,直接返回List集合
依赖与生态 无依赖,纯 JDK 原生 依赖 Spring Core,适配 Spring 生态
扩展能力 基础服务发现,无额外扩展 支持按类型加载、集成 Spring Bean 生命周期
典型应用 JDBC 驱动加载、SLF4J 日志适配 Spring Boot 自动配置、MyBatis-Spring 集成

五、关键注意事项

  1. 类全限定名必须准确:两种 SPI 的配置文件中,接口和实现类的全限定类名(包名 + 类名)不能写错,否则加载失败且无明显报错;
  2. 实现类必须有无参构造器 :SPI 机制通过反射实例化实现类,默认调用无参构造器,若实现类只有有参构造器,会抛出InstantiationException
  3. 配置文件无多余空格 / 空行:Java SPI 的配置文件每行一个实现类,Spring SPI 的等号 / 逗号前后无空格,否则会被识别为类名的一部分,导致类加载失败;
  4. Spring Boot 2.7+ 配置变更 :Spring Boot 2.7 开始推荐使用META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports替代spring.factories,文件内容为自动配置类的全限定类名(每行一个),适用于自动配置场景,普通 SPI 仍可使用spring.factories
  5. 类加载器一致性:加载 SPI 实现类时,需保证使用的类加载器能加载到接口和实现类(通常使用当前类的类加载器即可),避免类加载器隔离导致的加载失败。
相关推荐
小猪咪piggy1 小时前
【Python】(3) 函数
开发语言·python
夜鸣笙笙1 小时前
交换最小值和最大值
python
2301_822363602 小时前
使用Pandas进行数据分析:从数据清洗到可视化
jvm·数据库·python
摇滚侠2 小时前
Maven 教程,Maven 安装及使用,5 小时上手 Maven 又快又稳
java·maven
倔强菜鸟2 小时前
2026.2.2--Jenkins的基本使用
java·运维·jenkins
hai74252 小时前
在 Eclipse 的 JSP 项目中引入 MySQL 驱动
java·mysql·eclipse
码界奇点2 小时前
基于Flask与OpenSSL的自签证书管理系统设计与实现
后端·python·flask·毕业设计·飞书·源代码管理
瑞雪兆丰年兮2 小时前
[从0开始学Java|第十一天]学生管理系统
java·开发语言
看世界的小gui2 小时前
Jeecgboot通过Maxkey实现单点登录完整方案
java·spring boot·jeecgboot