Java实现SPI机制案例

wen java案例 2

Java SPI 机制实现案例

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

Java实现SPI机制案例

案例场景

我们将实现一个简单的日志系统,使用 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) 扫描配置并加载实现
延迟加载 实现类按需加载,可懒加载
解耦 服务提供者与使用者完全解耦,可灵活扩展

实际应用场景

  1. JDBC 驱动:通过 DriverManager 使用 SPI 加载数据库驱动
  2. 日志框架:SLF4J 通过 SPI 加载不同的日志实现
  3. Spring Boot Auto Configuration:自动配置加载机制
  4. Dubbo:服务发现和加载机制
  5. 数据库连接池:如 HikariCP 的选择和加载

注意事项

  • 配置文件中每行一个实现类,支持注释(以 开头)
  • 接口和配置文件必须放在 classpath 的 META-INF/services/ 目录下
  • 多个实现时默认按配置文件的顺序加载
  • 实现类必须有公共无参构造函数
  • 注意线程安全问题,ServiceLoader 不是线程安全的

这个案例展示了 SPI 的核心用法,实际应用中可以根据需要扩展更复杂的功能,如条件加载、缓存、销毁回调等。

抱歉,评论功能暂时关闭!