视频看了几百小时还迷糊?关注我,几分钟让你秒懂!


一、真实场景:为什么 JDBC 换数据库只需改配置?

你肯定写过这样的代码:

Connection conn = DriverManager.getConnection("jdbc:mysql://...", "user", "pwd");

但你有没有想过:

  • 为什么换 PostgreSQL 只需改 URL 和驱动类,代码一行不用动?
  • DriverManager 是怎么“自动”找到 MySQL 或 PG 的驱动实现的?

👉 答案就是:SPI(Service Provider Interface)机制!


二、SPI 是什么?(通俗定义)

SPI 是 Java 提供的一种“插件式”服务发现机制,允许第三方提供接口的具体实现,而核心框架无需硬编码依赖具体实现。

简单说:

  • 你定接口(规范)
  • 别人写实现(插件)
  • 运行时自动加载(解耦)

这正是“面向接口编程”的极致体现!


三、SPI vs API?别再混淆了!

对比项 API SPI
控制方 调用方主动调用 框架调用你的实现
谁写实现 框架提供实现 你或第三方提供实现
典型例子 List.add() JDBC 驱动、日志门面(SLF4J)、Spring Factories

一句话区分

  • API 是你调别人(如调 ArrayList
  • SPI 是别人调你(如 Tomcat 调你的 ServletContainerInitializer

四、动手实战:手写一个 SPI 扩展机制

1️⃣ 定义接口(由“框架”提供)

// src/main/java/com/example/spi/LogService.java
package com.example.spi;

public interface LogService {
    void log(String message);
}

2️⃣ 编写两个实现(由“插件开发者”提供)

// ConsoleLogService.java
package com.example.impl;

import com.example.spi.LogService;

public class ConsoleLogService implements LogService {
    @Override
    public void log(String message) {
        System.out.println("[CONSOLE] " + message);
    }
}

// FileLogService.java
package com.example.impl;

import com.example.spi.LogService;
import java.io.FileWriter;
import java.io.IOException;

public class FileLogService implements LogService {
    @Override
    public void log(String message) {
        try (FileWriter writer = new FileWriter("app.log", true)) {
            writer.write("[FILE] " + message + "\n");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

3️⃣ 注册实现(关键步骤!)

resources 目录下创建:

src/main/resources/META-INF/services/com.example.spi.LogService

文件内容(每行一个实现类全限定名):

com.example.impl.ConsoleLogService
com.example.impl.FileLogService

🔥 这就是 SPI 的“注册表”!JVM 会自动读取这个文件。


4️⃣ 使用 SPI 加载实现(框架代码)

// Main.java
package com.example;

import com.example.spi.LogService;

import java.util.ServiceLoader;

public class Main {
    public static void main(String[] args) {
        ServiceLoader<LogService> loader = ServiceLoader.load(LogService.class);
        
        for (LogService logService : loader) {
            logService.log("Hello from SPI!");
        }
    }
}

✅ 输出:

[CONSOLE] Hello from SPI!
[FILE] Hello from SPI!

🎯 成功加载所有实现!无需 new,无需配置类名!


五、Spring Boot 中的 SPI 应用:spring.factories

Spring Boot 并没有直接使用 Java 原生 SPI,而是自研了一套更强大的 SPI 机制——通过 META-INF/spring.factories

✅ 场景:自定义 Starter 自动配置

假设你开发了一个 my-spring-boot-starter,想让使用者引入后自动生效。

步骤1:编写自动配置类
// MyAutoConfiguration.java
@Configuration
public class MyAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public MyService myService() {
        return new MyServiceImpl();
    }
}
步骤2:在 spring.factories 中注册
# src/main/resources/META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.MyAutoConfiguration
步骤3:用户只需引入依赖
<dependency>
    <groupId>com.example</groupId>
    <artifactId>my-spring-boot-starter</artifactId>
    <version>1.0.0</version>
</dependency>

✅ 启动时,Spring Boot 会自动加载 MyAutoConfiguration,完成 Bean 注入!


六、反例警告 ❌ —— 新手常犯错误

❌ 反例1:文件路径或名称写错

# 错误1:文件名拼错
META-INF/services/com.example.spi.LogServcie  ❌(少了个 'e')

# 错误2:路径不对
src/main/java/META-INF/... ❌(必须在 resources 下!)

💥 结果:ServiceLoader 找不到实现,返回空集合!


❌ 反例2:实现类没有无参构造函数

public class BadLogService implements LogService {
    public BadLogService(String config) { // 有参构造
    }
    // ...
}

💥 ServiceLoader 内部通过 Class.newInstance() 创建实例,必须有 public 无参构造函数


❌ 反例3:在模块化项目(JPMS)中未声明 uses

如果你用了 module-info.java,必须显式声明:

// module-info.java
module com.example.app {
    uses com.example.spi.LogService; // 声明使用 SPI
}

否则会抛 ServiceConfigurationError


七、SPI 的典型应用场景

场景 说明
JDBC 驱动加载 mysql-connector-javaMETA-INF/services/java.sql.Driver 中注册
SLF4J 日志绑定 slf4j-log4j12 通过 SPI 绑定具体日志实现
Dubbo 扩展机制 基于 SPI 实现协议、序列化等插件化
Spring Boot 自动装配 spring.factories 是 SPI 的增强版
Java Security Provider 加密算法提供者通过 SPI 注册

八、注意事项总结 ⚠️

  1. SPI 文件必须放在 META-INF/services/ 下,文件名是接口全限定名
  2. 实现类必须有 public 无参构造函数
  3. 不要手动 new 实现类,应通过 ServiceLoader 加载
  4. Spring Boot 推荐用 spring.factories 而非原生 SPI(功能更强)
  5. SPI 实现是懒加载的,遍历时才实例化

九、结语

SPI 是 Java 生态中实现高扩展性、低耦合架构的核心机制。无论是 JDBC、日志框架,还是 Spring Boot 的自动配置,背后都有 SPI 的影子。

掌握它,你就能写出像 Spring 一样“可插拔”的优雅代码!

视频看了几百小时还迷糊?关注我,几分钟让你秒懂!

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐