⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 网络 「网络」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

先搞懂一个问题:你每天都在用 SPI,只是不知道

你写过 Class.forName("com.mysql.cj.jdbc.Driver") 吗?从 JDBC 4.0 开始,这行代码就不需要了。数据库驱动是怎么被”自动找到”的?答案就是 SPI。

你用 SLF4J 打日志,从来没写过”请用 Logback 作为实现”,但它就是用了 Logback。怎么做到的?还是 SPI。

Spring Boot 的自动配置为什么加个依赖就生效?底层的 SpringFactoriesLoader 本质上就是 SPI 的变种。

SPI 不是什么高深概念——它是 Java 生态”可插拔”设计的基础设施,只是大多数人用了却不知道自己在用。

API vs SPI:一个比方就懂

API 像餐厅的菜单。 厨师(提供方)决定菜单上有什么,你(调用方)只能从菜单里点。HashMap.put()、HttpClient.send(),都是人家写好的能力,你来调。

SPI 像餐厅的加盟协议。 总部(框架)定义了”炒菜必须用铁锅、摆盘必须用白瓷盘”的标准,各个加盟商(第三方)按标准各自实现,总部巡店时自动发现所有加盟商。

换成技术语言:API 的控制权在提供方——“我写好了,你来用”;SPI 的控制权在框架方——“我定好规矩,你来实现,我来找你”。

ServiceLoader:四步搞定的优雅设计

Java SPI 的核心就一个类:java.util.ServiceLoader。整个机制简洁到令人发指:

框架定义接口 → 第三方实现接口 → JAR 包里写个配置文件 → ServiceLoader 自动发现

关键细节: 配置文件放在 META-INF/services/ 目录下,文件名是接口的全限定名,文件内容是实现类的全限定名(每行一个)。ServiceLoader.load() 扫描 ClassPath 下所有 JAR 包里的这个文件,反射实例化每个声明的实现类。

精妙之处:框架代码一行不改,ClassPath 上多一个 JAR 包就能接入全新功能。 这就是教科书里说的”开闭原则”的工业级实现。

代码示例:一看就懂

场景:搜索框架,允许不同搜索引擎提供商接入。

第一步:框架定义 SPI 接口

// 项目:search-framework(框架方)
package com.example.spi;

import java.util.List;

/**
* 搜索引擎 SPI 接口——框架只定义"契约",不关心谁来实现
*/
public interface SearchEngine {
/** 返回引擎名称,方便调试 */
String name();
/** 执行搜索,返回结果列表 */
List<String> search(String keyword);
}

第二步:第三方提供实现(独立 JAR 包)

// 项目:google-search-provider(第三方实现)
package com.example.provider.google;

import com.example.spi.SearchEngine;
import java.util.Arrays;
import java.util.List;

public class GoogleSearchEngine implements SearchEngine {
@Override
public String name() { return "Google"; }

@Override
public List<String> search(String keyword) {
// 实际场景:调用 Google API
return Arrays.asList("Google结果1: " + keyword, "Google结果2: " + keyword);
}
}

在 google-search-provider 的资源目录下创建配置文件:

📁 src/main/resources/
└── META-INF/
└── services/
└── com.example.spi.SearchEngine ← 文件名 = 接口全限定名

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

com.example.provider.google.GoogleSearchEngine

第三步:框架自动发现并使用

// 项目:search-framework(框架方)
import com.example.spi.SearchEngine;
import java.util.ServiceLoader;

public class SearchService {
public static void main(String[] args) {
// ServiceLoader 扫描 ClassPath 下所有 META-INF/services/ 配置文件
ServiceLoader<SearchEngine> engines = ServiceLoader.load(SearchEngine.class);

for (SearchEngine engine : engines) {
System.out.println("发现搜索引擎: " + engine.name());
engine.search("Java SPI").forEach(System.out::println);
}
// 输出:
// 发现搜索引擎: Google
// Google结果1: Java SPI
// Google结果2: Java SPI
}
}

注意看:SearchService 里没有任何 Google 相关的 import。想换成百度搜索?换个 JAR 包就行,框架代码一行不动。

API vs SPI:一张表讲清楚

维度 API SPI
接口所有权 提供方定义并实现 调用方(框架)定义,第三方实现
调用方向 调用方主动调用提供方 框架被动发现并加载第三方实现
核心关注点 如何使用一个服务(功能导向) 如何扩展一个框架(扩展性导向)
耦合方向 调用方依赖提供方 实现方依赖框架的接口规范
典型例子 java.util.List, HttpClient 的方法 JDBC Driver, SLF4J 的桥接器, Spring Boot 自动配置

SPI 的优势、坑点与实战场景

优势:两个字,解耦

  • 可插拔架构 – 添加 JAR 包即接入新功能,完美实现开闭原则。
  • 面向抽象 – 框架代码只依赖接口,对具体实现完全无感知。

坑点:不得不知

  • 全量加载是最大的坑 – ServiceLoader 一次性实例化所有实现类,不管你用不用。10 个实现只用 1 个?全给你 new 出来。Dubbo 正是受不了这个,才自研了增强版 SPI。
  • 无法按需加载 – 没有 IOC 容器那样的别名管理和懒加载能力,灵活性有限。
  • 配置文件是命门 – META-INF/services/ 下的文件格式错误就直接加载失败,报错信息还不友好。
  • 别啥都叫 SPI – 面试中的 SPI 通常特指 ServiceLoader 机制或其变种(如 Spring 的 SpringFactoriesLoader),不是泛指一切可扩展接口。

实战:这些框架全在用 SPI

  • JDBC – java.sql.Driver 由各数据库厂商实现,JDK 通过 SPI 发现驱动。你以为 Class.forName("com.mysql.cj.jdbc.Driver") 是必须的?从 JDBC 4.0 开始,SPI 早就帮你自动搞定了。
  • SLF4J – 日志门面通过 SPI 绑定 Logback、Log4j2 等具体实现,换日志框架只需换依赖。
  • Spring Boot – SpringFactoriesLoader 加载 spring.factories 文件,这就是自动配置(Auto-Configuration)的底层引擎。
  • Dubbo – 自研增强版 SPI,支持按名加载、自动包装、依赖注入,是 Dubbo 扩展体系的基石。

总结

API 是”给你用的契约”,SPI 是”给你扩展的契约”。 前者的控制权在提供方,后者的控制权在框架。理解这一点,Java 生态里无数框架的”可插拔”设计你就全看透了。

文章目录
  1. 1. 先搞懂一个问题:你每天都在用 SPI,只是不知道
  2. 2. API vs SPI:一个比方就懂
  3. 3. ServiceLoader:四步搞定的优雅设计
    1. 3.1. 代码示例:一看就懂
    2. 3.2. API vs SPI:一张表讲清楚
    3. 3.3. SPI 的优势、坑点与实战场景
      1. 3.3.1. 优势:两个字,解耦
      2. 3.3.2. 坑点:不得不知
      3. 3.3.3. 实战:这些框架全在用 SPI
  4. 4. 总结