一、SPI机制概述
SPI概念
通常情况下,我们设计一个接口,然后自己写实现类,这叫 API (Application Programming Interface),API 是调用方 依赖实现方。
但如果场景反过来呢?
比如你是 Java 官方,你定义了 java.sql.Driver(数据库驱动接口),但你并不知道用户是用 MySQL、Oracle 还是 PostgreSQL。这时候,你需要一种机制:由接口定义方制定标准,由第三方(服务提供商)去实现,并且程序能够在运行时动态发现并加载这些实现 ,这就是 SPI
SPI (Service Provider Interface)直译"服务提供接口",它是一种 解耦扩展机制。核心思想:面向接口编程 + 运行时动态加载具体实现类。SPI机制体现了以下几个设计原则:
- 开闭原则:对扩展开放,对修改关闭
- 依赖倒置原则:高层模块不依赖低层模块,二者都依赖抽象
- 控制反转:由框架控制组件的加载和初始化
二、JDK原生的SPI
Java 自身早在 JDK 1.6 时期就提供了 SPI 支持,核心类是 java.util.ServiceLoader。
2.1 约定与规范
JDK SPI 对目录结构有严格的约定:
在 classpath 下的 META-INF/services/ 目录中创建一个文件。
文件名必须是接口的全限定名(例如 org.example.spi.SearchService)。
文件内容是实现类的全限定名,每行一个。
2.2 使用方式
(1)定义接口,假设我们定义了一个搜索接口:
java
package org.example.spi;
public interface SearchService {
void search(String keyword);
}
(2)编写实现类
java
public class DbSearchServiceImpl implements SearchService {
@Override
public void search(String keyword) {
System.out.println("数据库搜索:" + keyword);
}
}
public class FileSearchServiceImpl implements SearchService {
@Override
public void search(String keyword) {
System.out.println("文件搜索:" + keyword);
}
}
(3)在 META-INF/services/ 目录下创建文件:org.example.spi.SearchService
内容写实现类完整类名:
java
org.example.spi.DbSearchServiceImpl
org.example.spi.FileSearchServiceImpl
(4)调用,使用 ServiceLoader 加载
java
public class SpiTest {
public static void main(String[] args) {
ServiceLoader<SearchService> loader = ServiceLoader.load(SearchService.class);
for (SearchService service : loader) {
System.out.println(" " + service.getClass().getSimpleName() + " - " + service);
service.search("hello world");
}
}
}
项目结构:

测试结果如下:
bash
DbSearchServiceImpl - org.example.spi.DbSearchServiceImpl@4554617c
数据库搜索:hello world
FileSearchServiceImpl - org.example.spi.FileSearchServiceImpl@74a14482
文件搜索:hello world
三、Spring中的SPI
JDK SPI 的痛点
虽然 JDK SPI 属于标准,但在实际的大型框架开发中,它有几个明显的缺陷:
- 全量加载:ServiceLoader 在遍历时,会把配置文件中所有的实现类都实例化一遍。如果你只想用其中某一个,或者其中某个实现类初始化非常耗时,这会造成极大的资源浪费。
- 获取不便:只能通过 Iterator 遍历,无法根据某个参数(比如 key)直接获取对应的实现类。
- 异常处理不友好:加载失败时异常信息不明确
Spring SPI 的目标
Spring 框架的许多关键点都依赖于其 SPI 机制来实现 可插拔:
- Web 容器嵌入: 允许你轻松切换 Tomcat、Jetty、Undertow 等 Web 服务器。
- 数据访问抽象: 允许你使用不同的 ORM 框架(MyBatis、Hibernate、JPA)。
- AOP 代理机制: 可以选择 JDK 动态代理或 CGLIB 代理。
- 自定义配置解析: 允许扩展 XML 或注解的解析逻辑。
SPI文件 核心约定
Spring Boot 2.6.x 及以下(常见)
- 文件:META-INF/spring.factories
- 格式:Java properties,key 为"扩展点"(例如:
org.springframework.boot.autoconfigure.EnableAutoConfiguration),value 为逗号分隔的实现类列表。
示例:
# META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.autoconfig.MyAutoConfiguration,\
com.example.autoconfig.MyOtherAutoConfiguration
- 加载方式:SpringFactoriesLoader 读取 properties,按 key 找出值(逗号分割),再将类名导入/实例化。这意味着,当 Spring Boot 启动时,它会通过 SPI 机制扫描到 EnableAutoConfiguration 对应的所有配置类,并将它们加载到容器中
Spring Boot 2.7.x+
- 文件:META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
- 格式:每行一个类的全限定名(支持注释 #),没有 key → 文件专门用于 AutoConfiguration 列表。
示例:
txt
# META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.example.autoconfig.MyAutoConfiguration
com.example.autoconfig.MyOtherAutoConfiguration
- 加载方式:Spring Boot 在定位自动配置候选时会读取此 imports 文件,并配合 @AutoConfiguration 注解与条件注解来决定是否导入。
四、Spring SPI机制的实现原理
Spring SPI 的核心实现类是 org.springframework.core.io.support.SpringFactoriesLoader。
SpringFactoriesLoader核心方法:loadSpringFactories
Spring 是如何找到所有 jar 包里的配置文件的,通过SpringFactoriesLoader源码可以看到
java
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
// 存储SPI机制加载的类及其映射
static final Map<ClassLoader, Map<String, List<String>>> cache = new ConcurrentReferenceHashMap<>();
// 核心加载方法
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
// 如果外部没有传 classloader,就用当前类(SpringFactoriesLoader)的 classloader
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
String factoryTypeName = factoryType.getName();
// 从缓存中加载所有 spring.factories 再返回 key 对应的值
return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
// 返回结构 Map<扩展点接口完整类名, List<实现类完整类名>>
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
// 优先返回缓存
Map<String, List<String>> result = cache.get(classLoader);
if (result != null) {
return result;
}
result = new HashMap<>();
try {
// 扫描所有 META-INF/spring.factories 文件
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
while (urls.hasMoreElements()) {
// 每个 URL 都代表一个 jar 包中的 META-INF/spring.factories 文件
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
// 解析所有 key 和 value,如果一个key配置了value,则用英文逗号隔开,此处会做分割
String[] factoryImplementationNames =
StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
// 将 key → list 加入结果 Map
for (String factoryImplementationName : factoryImplementationNames) {
result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
.add(factoryImplementationName.trim());
}
}
}
// 替换实现类名称列表为不可修改的集合
result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
// 加缓存,下次直接复用
cache.put(classLoader, result);
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
return result;
}
// 加载并实例化工厂对象
public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
Assert.notNull(factoryType, "'factoryType' must not be null");
ClassLoader classLoaderToUse = classLoader;
// 确定要使用的 ClassLoader:如果传入为空,则使用 SpringFactoriesLoader 自身的 ClassLoader
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
// 调用 loadFactoryNames 获取所有实现类的全限定名列表
List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse);
if (logger.isTraceEnabled()) {
logger.trace("Loaded [" + factoryType.getName() + "] names: " + factoryImplementationNames);
}
// 实例化所有实现类
List<T> result = new ArrayList<>(factoryImplementationNames.size());
for (String factoryImplementationName : factoryImplementationNames) {
// 逐个通过反射实例化
result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));
}
// 对结果进行排序,Spring 使用 AnnotationAwareOrderComparator,支持 @Order 或 Ordered 接口
AnnotationAwareOrderComparator.sort(result);
return result;
}
/**
* 辅助方法:通过反射实例化工厂实现类。
* @param factoryImplementationName 实现类的全限定名
* @param factoryType 工厂接口/抽象类类型
* @param classLoader 用于加载类的 ClassLoader
* @return 实例化后的对象
* @throws IllegalArgumentException 如果类加载失败、无法赋值或实例化失败
*/
private static <T> T instantiateFactory(String factoryImplementationName, Class<T> factoryType, ClassLoader classLoader) {
try {
// 1. 加载实现类 Class 对象
Class<?> factoryImplementationClass = ClassUtils.forName(factoryImplementationName, classLoader);
// 2. 校验:检查实现类是否是工厂类型(接口/抽象类)的子类型
if (!factoryType.isAssignableFrom(factoryImplementationClass)) {
throw new IllegalArgumentException(
"Class [" + factoryImplementationName + "] is not assignable to factory type [" + factoryType.getName() + "]");
}
// 3. 实例化:通过反射获取无参构造函数并创建实例(ReflectionUtils 确保访问性)
return (T) ReflectionUtils.accessibleConstructor(factoryImplementationClass).newInstance();
}
catch (Throwable ex) {
throw new IllegalArgumentException(
"Unable to instantiate factory class [" + factoryImplementationName + "] for factory type [" + factoryType.getName() + "]",
ex);
}
}
可以看到,loadFactoryNames 是对外入口,负责根据指定的 SPI 接口类型,从所有已加载的 spring.factories 中取出对应的实现类名单,而 loadSpringFactories 则是底层引擎,负责扫描类路径下所有 META-INF/spring.factories 文件,将其解析、合并、去重并缓存成一个 Map,通过上面的步骤,Spring 就拿到了实现类的全限定名字符串,最后通过instantiateFactory,反射 ReflectionUtils.accessibleConstructor 来实例化对象。