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 的核心用法,实际应用中可以根据需要扩展更复杂的功能,如条件加载、缓存、销毁回调等。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播