Java SPI 机制:从原理到实战
Java SPI 是一种服务发现机制,通过 ServiceLoader 和配置文件实现接口与实现的解耦。文章介绍了 SPI 原理、配置方式及加载流程,并通过 JDBC 驱动、多数据源切换、支付系统整合等实战案例演示了应用方法。同时对比了 Dubbo 对 SPI 的改进,强调了其在扩展性和灵活性上的优势,帮助开发者理解开源框架设计思想并应用于实际架构。

Java SPI 是一种服务发现机制,通过 ServiceLoader 和配置文件实现接口与实现的解耦。文章介绍了 SPI 原理、配置方式及加载流程,并通过 JDBC 驱动、多数据源切换、支付系统整合等实战案例演示了应用方法。同时对比了 Dubbo 对 SPI 的改进,强调了其在扩展性和灵活性上的优势,帮助开发者理解开源框架设计思想并应用于实际架构。

SPI(Service Provider Interface),服务提供者接口,是 Java 提供的一种服务发现机制。它允许第三方服务提供商为核心库或接口提供实现,而不需要修改核心代码。
在实际开发中,我们经常遇到这样的场景:定义了一个接口,但具体实现由不同厂商或不同场景提供。例如:
传统做法是通过硬编码或配置文件指定实现类,这样存在以下问题:
SPI 机制通过约定配置文件的方式,实现了服务的动态发现和加载。
| 特性 | API(Application Programming Interface) | SPI(Service Provider Interface) |
|---|---|---|
| 调用方向 | 应用 → 框架/库 | 框架/库 → 服务提供者 |
| 定义者 | 框架/库定义 | 框架/库定义 |
| 实现者 | 应用实现 | 第三方提供者实现 |
| 使用场景 | 应用使用框架功能 | 框架调用扩展实现 |
简单理解:API 是给应用用的,SPI 是给框架扩展用的。

Java SPI 主要依赖 java.util.ServiceLoader 类,位于 JDK 核心库中。
public final class ServiceLoader<S> implements Iterable<S> {
// 服务接口的 Class 对象
private final Class<S> service;
// 类加载器
private final ClassLoader loader;
// 已加载的服务提供者缓存
private LinkedHashMap<String, S> providers = new LinkedHashMap<>();
// 懒加载查找迭代器
private LazyIterator lookupIterator;
// 通过类加载器和接口类型加载服务
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
// 通过指定类加载器加载服务
public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) {
return new ServiceLoader<>(service, loader);
}
}
SPI 机制通过约定配置文件来发现服务实现。配置文件位置必须为:
META-INF/services/ └── com.example.spi.SomeService # 文件名为接口的全限定名
文件内容为实现类的全限定名,每行一个:
com.example.spi.impl.SomeServiceImpl
com.example.spi.impl.AnotherServiceImpl

服务加载流程如下:
META-INF/services/接口全限定名 文件让我们通过一个完整的示例来演示 Java SPI 的使用。
首先定义一个数据库操作接口:
package com.example.spi;
/**
* 数据库操作接口
*/
public interface DatabaseOperation {
/**
* 执行查询
* @param sql SQL 语句
* @return 查询结果
*/
String query(String sql);
/**
* 获取数据库类型
* @return 数据库类型名称
*/
String getDatabaseType();
/**
* 获取版本信息
* @return 版本号
*/
String getVersion();
}
package com.example.spi.impl;
import com.example.spi.DatabaseOperation;
/**
* MySQL 数据库操作实现
*/
public class MySQLDatabaseOperation implements DatabaseOperation {
@Override
public String query(String sql) {
System.out.println("[MySQL] 执行 SQL: " + sql);
return "MySQL 查询结果集";
}
@Override
public String getDatabaseType() {
return "MySQL";
}
@Override
public String getVersion() {
return "8.0.32";
}
}
package com.example.spi.impl;
import com.example.spi.DatabaseOperation;
/**
* Oracle 数据库操作实现
*/
public class OracleDatabaseOperation implements DatabaseOperation {
@Override
public String query(String sql) {
System.out.println("[Oracle] 执行 SQL: " + sql);
return "Oracle 查询结果集";
}
@Override
public String getDatabaseType() {
return "Oracle";
}
@Override
public String getVersion() {
return "19c";
}
}
在 src/main/resources/META-INF/services/ 目录下创建配置文件:
文件名:com.example.spi.DatabaseOperation
文件内容:
com.example.spi.impl.MySQLDatabaseOperation
com.example.spi.impl.OracleDatabaseOperation
package com.example.spi;
import java.util.ServiceLoader;
/**
* SPI 服务加载器
*/
public class SPIDemo {
public static void main(String[] args) {
System.out.println("=== Java SPI 机制演示 ===\n");
// 加载所有 DatabaseOperation 实现
ServiceLoader<DatabaseOperation> loader = ServiceLoader.load(DatabaseOperation.class);
// 遍历所有实现
for (DatabaseOperation operation : loader) {
System.out.println("发现数据库实现:" + operation.getClass().getSimpleName());
System.out.println("数据库类型:" + operation.getDatabaseType());
System.out.println("版本信息:" + operation.getVersion());
String result = operation.query("SELECT * FROM users");
System.out.println("查询结果:" + result);
System.out.println("----------------------");
}
// 再次遍历,验证缓存机制
System.out.println("\n=== 第二次遍历(从缓存读取)===");
for (DatabaseOperation operation : loader) {
System.out.println("缓存中的实现:" + operation.getClass().getSimpleName());
}
}
}
JDBC 是 Java SPI 机制的典型应用场景。让我们深入分析 JDBC 如何通过 SPI 加载驱动。
在 JDBC 4.0 之前,需要显式加载驱动:
// 显式加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection(url, user, password);
这种方式存在以下问题:
从 JDBC 4.0 开始,通过 SPI 机制自动发现并加载驱动。

驱动注册流程:
MySQL 驱动实现类的静态初始化块会注册驱动:
package com.mysql.cj.jdbc;
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException e) {
throw new RuntimeException("Can't register driver!");
}
}
}
当 DriverManager 类被加载时,会触发静态初始化块:
public class DriverManager {
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
private static void loadInitialDrivers() {
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try {
while (driversIterator.hasNext()) {
driversIterator.next();
}
} catch (Throwable t) {
// 处理异常
}
}
}
MySQL 驱动 jar 包中的 SPI 配置文件:
# 文件路径:META-INF/services/java.sql.Driver
com.mysql.cj.jdbc.Driver
使用 SPI 机制后,获取数据库连接变得非常简单:
package com.example.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
/**
* JDBC SPI 使用示例
*/
public class JdbcSPIDemo {
public static void main(String[] args) throws Exception {
// 无需显式加载驱动,SPI 自动发现
String url = "jdbc:mysql://localhost:3306/test";
String user = "root";
String password = "password";
try (Connection conn = DriverManager.getConnection(url, user, password)) {
String sql = "SELECT id, username, email FROM users WHERE status = ?";
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setInt(1, 1);
try (ResultSet rs = pstmt.executeQuery()) {
while (rs.next()) {
System.out.println("ID: " + rs.getInt("id"));
System.out.println("用户名:" + rs.getString("username"));
System.out.println("邮箱:" + rs.getString("email"));
}
}
}
}
}
}
场景:支持多数据源动态切换
package com.example.jdbc;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.ServiceLoader;
/**
* 多数据源管理器
*/
public class MultiDataSourceManager {
private final Map<String, DataSource> dataSourceMap = new HashMap<>();
public MultiDataSourceManager() {
// 通过 SPI 加载所有 DataSource 实现
ServiceLoader<DataSourceProvider> providers = ServiceLoader.load(DataSourceProvider.class);
for (DataSourceProvider provider : providers) {
DataSource ds = provider.createDataSource();
dataSourceMap.put(provider.getDataSourceName(), ds);
System.out.println("注册数据源:" + provider.getDataSourceName());
}
}
public Connection getConnection(String dataSourceName) throws SQLException {
DataSource ds = dataSourceMap.get(dataSourceName);
if (ds == null) {
throw new IllegalArgumentException("未找到数据源:" + dataSourceName);
}
return ds.getConnection();
}
/**
* 动态切换数据源执行查询
*/
public void executeQuery(String dataSourceName, String sql) throws SQLException {
try (Connection conn = getConnection(dataSourceName)) {
try (var stmt = conn.createStatement(); var rs = stmt.executeQuery(sql)) {
System.out.println("[" + dataSourceName + "] 查询结果:");
while (rs.next()) {
// 处理结果集
}
}
}
}
}
虽然 Java SPI 提供了服务发现机制,但存在一些不足:
Dubbo 实现了自己的 SPI 机制,解决了这些问题。

主要改进:
package com.example.dubbo.spi;
import org.apache.dubbo.common.extension.ExtensionLoader;
/**
* Dubbo SPI 使用示例
*/
public class DubboSPIDemo {
public static void main(String[] args) {
// 获取扩展加载器
ExtensionLoader<Robot> loader = ExtensionLoader.getExtensionLoader(Robot.class);
// 按需获取指定实现
Robot optimusPrime = loader.getExtension("optimusPrime");
optimusPrime.sayHello();
Robot bumblebee = loader.getExtension("bumblebee");
bumblebee.sayHello();
// 获取自适应扩展(根据 URL 参数自动选择)
Robot adaptiveRobot = loader.getAdaptiveExtension();
adaptiveRobot.sayHello();
}
}
SLF4J 定义日志门面接口,Logback 提供实现。
SLF4J 接口:
package org.slf4j;
public interface Logger {
void debug(String msg);
void info(String msg);
void warn(String msg);
void error(String msg);
}
Logback 实现:
# META-INF/services/org.slf4j.spi.SLF4JServiceProvider
ch.qos.logback.classic.spi.LogbackServiceProvider
使用示例:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LoggingService {
private static final Logger logger = LoggerFactory.getLogger(LoggingService.class);
public void processOrder(Order order) {
logger.info("开始处理订单:{}", order.getId());
try {
// 业务逻辑
logger.debug("订单金额:{}", order.getAmount());
} catch (Exception e) {
logger.error("处理订单失败", e);
throw e;
}
logger.info("订单处理完成");
}
}

场景:支持多种支付方式(支付宝、微信、银联)
支付接口定义:
package com.example.payment.spi;
/**
* 支付服务接口
*/
public interface PaymentService {
/**
* 创建支付订单
* @param request 支付请求
* @return 支付结果
*/
PaymentResult createPayment(PaymentRequest request);
/**
* 查询支付状态
* @param paymentId 支付订单号
* @return 支付状态
*/
PaymentStatus queryStatus(String paymentId);
/**
* 申请退款
* @param refundRequest 退款请求
* @return 退款结果
*/
RefundResult refund(RefundRequest refundRequest);
/**
* 获取支付方式
* @return 支付方式名称
*/
String getPaymentType();
}
支付宝实现:
package com.example.payment.impl;
import com.example.payment.spi.*;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.request.AlipayTradeCreateRequest;
import com.alipay.api.response.AlipayTradeCreateResponse;
/**
* 支付宝支付实现
*/
public class AlipayPaymentService implements PaymentService {
private AlipayClient alipayClient;
public AlipayPaymentService() {
// 初始化支付宝客户端
this.alipayClient = new DefaultAlipayClient(
"https://openapi.alipay.com/gateway.do",
"APP_ID",
"PRIVATE_KEY",
"json",
"UTF-8",
"ALIPAY_PUBLIC_KEY"
);
}
@Override
public PaymentResult createPayment(PaymentRequest request) {
try {
AlipayTradeCreateRequest alipayRequest = new AlipayTradeCreateRequest();
alipayRequest.setBizContent(String.format(
"{\"out_trade_no\":\"%s\",\"total_amount\":\"%s\",\"subject\":\"%s\"}",
request.getOrderNo(), request.getAmount(), request.getSubject()
));
AlipayTradeCreateResponse response = alipayClient.execute(alipayRequest);
if (response.isSuccess()) {
return PaymentResult.success(response.getTradeNo());
} else {
return PaymentResult.fail(response.getSubMsg());
}
} catch (Exception e) {
return PaymentResult.fail("支付异常:" + e.getMessage());
}
}
@Override
public PaymentStatus queryStatus(String paymentId) {
// 查询支付状态实现
return PaymentStatus.PAID;
}
@Override
public RefundResult refund(RefundRequest refundRequest) {
// 退款实现
return RefundResult.success();
}
@Override
public String getPaymentType() {
return "ALIPAY";
}
}
微信支付实现:
package com.example.payment.impl;
import com.example.payment.spi.*;
/**
* 微信支付实现
*/
public class WechatPaymentService implements PaymentService {
@Override
public PaymentResult createPayment(PaymentRequest request) {
// 微信支付统一下单实现
return PaymentResult.success("WX" + request.getOrderNo());
}
@Override
public PaymentStatus queryStatus(String paymentId) {
return PaymentStatus.PAID;
}
@Override
public RefundResult refund(RefundRequest refundRequest) {
return RefundResult.success();
}
@Override
public String getPaymentType() {
return "WECHAT";
}
}
支付服务工厂:
package com.example.payment;
import com.example.payment.spi.PaymentService;
import com.example.payment.spi.PaymentRequest;
import java.util.HashMap;
import java.util.Map;
import java.util.ServiceLoader;
/**
* 支付服务工厂
*/
public class PaymentServiceFactory {
private static final Map<String, PaymentService> SERVICE_MAP = new HashMap<>();
static {
// 通过 SPI 加载所有支付实现
ServiceLoader<PaymentService> loader = ServiceLoader.load(PaymentService.class);
for (PaymentService service : loader) {
SERVICE_MAP.put(service.getPaymentType(), service);
System.out.println("注册支付服务:" + service.getPaymentType());
}
}
/**
* 根据支付类型获取支付服务
*/
public static PaymentService getPaymentService(String paymentType) {
PaymentService service = SERVICE_MAP.get(paymentType);
if (service == null) {
throw new IllegalArgumentException("不支持的支付方式:" + paymentType);
}
return service;
}
/**
* 创建支付订单
*/
public static void createPayment(String paymentType, PaymentRequest request) {
PaymentService service = getPaymentService(paymentType);
System.out.println("使用 " + paymentType + " 支付...");
var result = service.createPayment(request);
if (result.isSuccess()) {
System.out.println("支付创建成功:" + result.getTransactionId());
} else {
System.out.println("支付失败:" + result.getErrorMessage());
}
}
}
配置文件:
# META-INF/services/com.example.payment.spi.PaymentService
com.example.payment.impl.AlipayPaymentService
com.example.payment.impl.WechatPaymentService
使用示例:
package com.example.payment;
import com.example.payment.spi.PaymentRequest;
import java.math.BigDecimal;
/**
* 支付系统使用示例
*/
public class PaymentSystemDemo {
public static void main(String[] args) {
// 创建支付请求
PaymentRequest request = new PaymentRequest();
request.setOrderNo("ORDER20240101001");
request.setAmount(new BigDecimal("99.99"));
request.setSubject("测试商品");
// 使用支付宝支付
PaymentServiceFactory.createPayment("ALIPAY", request);
// 使用微信支付
PaymentServiceFactory.createPayment("WECHAT", request);
}
}
Java SPI 机制是一种优雅的服务发现和扩展机制,它通过约定配置文件的方式,实现了:
掌握 SPI 机制,不仅能更好地理解开源框架的设计思想,还能在实际项目中实现更优雅的架构设计。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online