❝
想象你买了一个多功能扩展坞,插上U盘能读文件,插上键盘能打字,插上显卡还能外接显示器——
而Java的SPI机制,就是代码世界的“万能扩展坞”!
❞
类比:
USB接口 = Java的ServiceLoader
U盘/键盘 = 第三方实现类(如MySQL驱动、支付渠道)
即插即用 = 无需修改主程序代码,直接加载新功能
SPI(Service Provider Interface):
“你定接口规则,别人填实现,程序运行时自动发现!”
官方话:一种服务发现机制,通过
META-INF/services
配置文件动态加载实现类。人话:“甩锅架构”——把实现类的锅甩给别人,自己只定义接口。
// 你定义的接口(USB标准)
public interface PaymentService {
void pay();
}
// 别人实现的“U盘”(支付宝/微信支付)
public class AlipayService implements PaymentService {
@Override
public void pay() { System.out.println("支付宝付款"); }
}
秘密:java.sql.Driver
就是SPI接口,数据库厂商(MySQL/Oracle)自己实现驱动!
实战演示:
打开MySQL的jar包,找到META-INF/services/java.sql.Driver
文件
文件内容:com.mysql.cj.jdbc.Driver
(这就是MySQL的“U盘”实现)
// JDBC核心代码(背后是ServiceLoader在干活)
Class.forName("com.mysql.cj.jdbc.Driver"); // 实际可省略!SPI自动加载
Connection conn = DriverManager.getConnection(url, user, password);
步骤1:定义SPI接口
public interface PaymentService {
String getType();
void pay(double amount);
}
步骤2:实现两个“支付U盘”
// 支付宝“U盘”
public class AlipayService implements PaymentService {
public String getType() { return "alipay"; }
public void pay(double amount) {
System.out.println("支付宝支付:" + amount + "元");
}
}
// 微信支付“U盘”
public class WechatPayService implements PaymentService {
public String getType() { return "wechat"; }
public void pay(double amount) {
System.out.println("微信支付:" + amount + "元");
}
}
步骤3:创建META-INF/services配置文件
文件路径:src/main/resources/META-INF/services/com.example.PaymentService
文件内容:
com.example.AlipayService
com.example.WechatPayService
步骤4:主程序调用(自动发现所有实现!)
ServiceLoader services = ServiceLoader.load(PaymentService.class);
for (PaymentService service : services) {
if (service.getType().equals("wechat")) { // 按需选择
service.pay(100.0);
break;
}
}
效果:新增一个BankPayService只需加jar包和配置文件,主程序无需重新编译!
错误示例:META-INF/service
(少了个s)→ 加载失败!
必须严格匹配路径和文件名(区分大小写)。
Spring的spring.factories
(SPI加强版):
支持批量加载(如自动配置类org.springframework.boot.autoconfigure.EnableAutoConfiguration
)
示例:
# spring-boot-autoconfigure.jar中的配置
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.MyAutoConfiguration
场景 | 举例 | 替代方案 |
---|---|---|
需要动态扩展功能 | 支付渠道、数据库驱动 | 工厂模式+反射 |
希望解耦接口和实现 | JDBC、日志门面(SLF4J) | 依赖注入(DI) |
第三方库需要自由实现 | SpringBoot Starter机制 | 配置文件+ClassLoader |
彩蛋:SPI面试三连
SPI和API的区别?
API是“调用方制定规则”,SPI是“实现方制定规则”。
SPI破坏了双亲委派模型吗?
是的!ServiceLoader用线程上下文类加载器(TCCL)加载实现类。
为什么Dubbo/SpringBoot不用JDK的SPI?
JDK的SPI只能全量加载,它们需要按需加载和注解支持。