Java SPI 机制实现案例
SPI(Service Provider Interface)是 Java 提供的一种服务发现机制,允许第三方提供实现,并在运行时动态加载,下面通过一个完整案例来演示。

案例场景
我们将实现一个简单的日志系统,使用 SPI 机制支持不同的日志实现(控制台日志、文件日志)。
项目结构
spi-demo/
├── spi-api/ # 接口定义模块
│ └── src/main/java/
│ └── com/example/spi/
│ ├── Logger.java
│ └── LoggerManager.java
├── spi-console/ # 控制台日志实现
│ └── src/main/java/
│ └── com/example/spi/
│ └── ConsoleLogger.java
│ └── src/main/resources/
│ └── META-INF/services/
│ └── com.example.spi.Logger
├── spi-file/ # 文件日志实现
│ └── src/main/java/
│ └── com/example/spi/
│ └── FileLogger.java
│ └── src/main/resources/
│ └── META-INF/services/
│ └── com.example.spi.Logger
└── spi-test/ # 测试模块
└── src/main/java/
└── com/example/spi/
└── Main.java
实现代码
1 接口定义(spi-api)
Logger.java
package com.example.spi;
public interface Logger {
void info(String message);
void error(String message);
}
LoggerManager.java - 服务管理器
package com.example.spi;
import java.util.Iterator;
import java.util.ServiceLoader;
public class LoggerManager {
public static Logger getLogger() {
ServiceLoader<Logger> loader = ServiceLoader.load(Logger.class);
Iterator<Logger> iterator = loader.iterator();
// 返回第一个可用的实现
if (iterator.hasNext()) {
return iterator.next();
}
throw new IllegalStateException("No Logger implementation found!");
}
public static void logAll(String message) {
ServiceLoader<Logger> loader = ServiceLoader.load(Logger.class);
for (Logger logger : loader) {
logger.info(message);
}
}
}
2 控制台日志实现(spi-console)
ConsoleLogger.java
package com.example.spi;
import java.time.LocalDateTime;
public class ConsoleLogger implements Logger {
@Override
public void info(String message) {
System.out.println("[INFO] " + LocalDateTime.now() + " - " + message);
}
@Override
public void error(String message) {
System.err.println("[ERROR] " + LocalDateTime.now() + " - " + message);
}
}
服务配置文件 META-INF/services/com.example.spi.Logger:
com.example.spi.ConsoleLogger
3 文件日志实现(spi-file)
FileLogger.java
package com.example.spi;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.time.LocalDateTime;
public class FileLogger implements Logger {
private static final String LOG_FILE = "spi-demo.log";
@Override
public void info(String message) {
writeToFile("[INFO]", message);
}
@Override
public void error(String message) {
writeToFile("[ERROR]", message);
}
private void writeToFile(String level, String message) {
try (PrintWriter writer = new PrintWriter(new FileWriter(LOG_FILE, true))) {
writer.println(level + " " + LocalDateTime.now() + " - " + message);
System.out.println("已写入日志文件: " + message);
} catch (IOException e) {
System.err.println("写入日志失败: " + e.getMessage());
}
}
}
服务配置文件 META-INF/services/com.example.spi.Logger:
com.example.spi.FileLogger
4 测试模块(spi-test)
Main.java
package com.example.spi;
public class Main {
public static void main(String[] args) {
System.out.println("========== SPI 日志系统演示 ==========\n");
// 1. 获取默认实现
System.out.println("--- 获取第一个可用实现 ---");
Logger defaultLogger = LoggerManager.getLogger();
defaultLogger.info("这是默认日志实现");
defaultLogger.error("这是一个错误消息");
// 2. 遍历所有实现
System.out.println("\n--- 遍历所有实现 ---");
LoggerManager.logAll("这是广播消息");
// 3. 动态加载特定实现
System.out.println("\n--- 使用 ServiceLoader 直接加载 ---");
ServiceLoader<Logger> loader = ServiceLoader.load(Logger.class);
for (Logger logger : loader) {
String implName = logger.getClass().getSimpleName();
System.out.println("实现类: " + implName);
logger.info("来自 " + implName + " 的消息");
}
}
}
构建配置
pom.xml(父项目)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>spi-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>spi-api</module>
<module>spi-console</module>
<module>spi-file</module>
<module>spi-test</module>
</modules>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
</project>
spi-test 的 pom.xml
<project>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>spi-demo</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>spi-test</artifactId>
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>spi-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>spi-console</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>spi-file</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
运行结果
========== SPI 日志系统演示 ==========
--- 获取第一个可用实现 ---
[INFO] 2024-01-15T10:30:45.123 - 这是默认日志实现
[ERROR] 2024-01-15T10:30:45.124 - 这是一个错误消息
--- 遍历所有实现 ---
[INFO] 2024-01-15T10:30:45.125 - 这是广播消息
已写入日志文件: 这是广播消息
--- 使用 ServiceLoader 直接加载 ---
实现类: ConsoleLogger
[INFO] 2024-01-15T10:30:45.126 - 来自 ConsoleLogger 的消息
实现类: FileLogger
已写入日志文件: 来自 FileLogger 的消息
进阶示例:Spring Boot 中的 SPI(@AutoConfiguration)
现代 Spring Boot 使用 spring.factories 文件实现类似 SPI 机制:
// MyAutoConfiguration.java
@Configuration
public class MyAutoConfiguration {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
# META-INF/spring.factories org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.example.MyAutoConfiguration
关键要点总结
| 要点 | 说明 |
|---|---|
| 核心接口 | 定义服务接口,作为 SPI 契约 |
| 配置文件 | 在 META-INF/services/ 下创建以接口全限定名为名的文件 |
| 加载机制 | ServiceLoader.load(Interface.class) 扫描配置并加载实现 |
| 延迟加载 | 实现类按需加载,可懒加载 |
| 解耦 | 服务提供者与使用者完全解耦,可灵活扩展 |
实际应用场景
- JDBC 驱动:通过
DriverManager使用 SPI 加载数据库驱动 - 日志框架:SLF4J 通过 SPI 加载不同的日志实现
- Spring Boot Auto Configuration:自动配置加载机制
- Dubbo:服务发现和加载机制
- 数据库连接池:如 HikariCP 的选择和加载
注意事项
- 配置文件中每行一个实现类,支持注释(以 开头)
- 接口和配置文件必须放在 classpath 的
META-INF/services/目录下 - 多个实现时默认按配置文件的顺序加载
- 实现类必须有公共无参构造函数
- 注意线程安全问题,
ServiceLoader不是线程安全的
这个案例展示了 SPI 的核心用法,实际应用中可以根据需要扩展更复杂的功能,如条件加载、缓存、销毁回调等。