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 集成 |
五、关键注意事项
- 类全限定名必须准确:两种 SPI 的配置文件中,接口和实现类的全限定类名(包名 + 类名)不能写错,否则加载失败且无明显报错;
- 实现类必须有无参构造器 :SPI 机制通过反射实例化实现类,默认调用无参构造器,若实现类只有有参构造器,会抛出
InstantiationException; - 配置文件无多余空格 / 空行:Java SPI 的配置文件每行一个实现类,Spring SPI 的等号 / 逗号前后无空格,否则会被识别为类名的一部分,导致类加载失败;
- Spring Boot 2.7+ 配置变更 :Spring Boot 2.7 开始推荐使用
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports替代spring.factories,文件内容为自动配置类的全限定类名(每行一个),适用于自动配置场景,普通 SPI 仍可使用spring.factories; - 类加载器一致性:加载 SPI 实现类时,需保证使用的类加载器能加载到接口和实现类(通常使用当前类的类加载器即可),避免类加载器隔离导致的加载失败。