Java 的 SPI

49 阅读3分钟

🔍 什么是 Java SPI?

Java 的 SPI(Service Provider Interface) 是一种服务发现机制,用于在运行时自动发现接口的实现类,无需硬编码地指定具体的实现。通常用于实现 插件式架构、模块化扩展、解耦依赖

🌰 举个生活中的类比:

假设你在开一家 “烤肉自助餐厅”

你想让不同的 “烤肉师傅” 来帮你烤肉。

但是你不想关心具体是谁来了,你也希望提前约定:只要你叫一声“来帮忙烤肉的人”,他们自动就出现了,并且能按规矩干活。

那怎么做呢?你贴一个告示:

“来申请工作的烤肉师傅,都按这个模板写申请表,就知道你是谁了。”

这个申请表就是:你定的一个接口(比如叫 BarbecueCooker ,然后:

  • 小王写了个 “川式烤肉” 的实现类;
  • 阿毛写了个 “韩式烤肉” 的实现类;
  • 小芳写了个 “新疆烤肉” 的实现类。

只要他们都按照这个接口实现了方法,并在自己的骨头盒里贴上告示(配置文件),你就自动认识他们了。

你不需要硬编码说“今天让阿毛来”,而是说:"你们这些注册过的师傅,谁在我这里能干活,都出来吧。"

🎯 那么,什么是 SPI?

你可以把它理解为:

“Java 的插件机制”:一个服务接口(比如支付接口),可以有多个服务提供者(比如支付宝、微信、银联)在不同时候自动注册进来,Java 会帮你自动扫描、加载、实例化这些实现类,只需要你提前约定一个接口。

🔧 SPI 的基本使用步骤

以数据库驱动为例:java.sql.Driver 是接口,各个数据库厂商提供不同的实现(MySQL、PostgreSQL 等),通过 SPI 被自动加载。

✅ 步骤如下:

第一步:定义接口(抽象模块,像个招聘启事)

public interface PaymentService {
    void pay(double amount);
}

你可以理解为这是你的招工标准,谁按照我这个接口写类,谁就能干这里的活。

第二步:编写多个实现类(扩展模块,各个烤肉师傅写简历)

public class AlipayService implements PaymentService {
    @Override
    public void pay(double amount) {
        System.out.println("支付宝支付:" + amount);
    }
}

public class WechatPayService implements PaymentService {
    @Override
    public void pay(double amount) {
        System.out.println("微信支付:" + amount);
    }
}

第三步:创建配置文件(贴告示,说“谁是能干这个的”)

resources/META-INF/services/ 目录下新建一个文件,文件名是接口的全限定名:

src/main/resources/META-INF/services/com.example.PaymentService

文件内容为多个实现类的全限定类名:

com.example.AlipayService
com.example.WechatPayService

你可以理解为这就是你的注册表,谁有能力就能来注册这本花名册上。

第四步:使用 ServiceLoader 加载实现类

public class Test {
    public static void main(String[] args) {
        ServiceLoader<PaymentService> load = ServiceLoader.load(PaymentService.class);
        for (PaymentService paymentService : load) {
            paymentService.pay(100);
        }
    }
}

运行结果会是:

支付宝支付:100.0
微信支付:100.0

Java 帮你自动加载了所有在配置文件里注册的实现类。

📚 完整代码示例

项目结构如下

src/
└── main/
    ├── java/
    │   └── com/
    │       └── example/
    │             ├── Test.java
    │             ├── PaymentService.java
    │             ├── AlipayService.java
    │             └── WechatPayService.java
    └── resources/
        └── META-INF/
             └── services/
                   └── com.example.PaymentService

文件内容

# META-INF/services/com.example.PaymentService
com.example.AlipayService
com.example.WechatPayService

💬 与 Spring IOC / Spring Boot 自动装配的区别:

特性Java SPISpring IOC
类加载机制JVM 原生Spring 容器
配置方式META-INF/services 文件XML 或 注解(@Component)
是否支持依赖注入❌ 不支持✅ 强大的依赖管理
扩展方式需要显示配置 .class通过 spring.factories

🔗 总结

  • SPI 是 Java 的一种“找帮手”方法,controller 贴接口,帮工贴上自己的类名,Java 会按图索骥自动找到。
  • 📄 告示文件放在:src/main/resources/META-INF/services/接口的完整类名
  • ✅ 用在解耦、扩展、插件系统中非常有用。
  • ⚠️ 不建议用于依赖复杂的业务逻辑加载。
  • 📌 与 Spring 的自动装配机制不同,它更原始也更直接。
  • ⚖️ 实现类只需要提供类名(不写参数、构造器不能复杂),不需要在代码里 import 或 new。
  • 👥 谁想加入你系统,只需提供 jar 包,并在 jar 包里写个告示,你就能识别出来。