人人都会AI编程

24.3 Spring SPI 机制与 SpringFactoriesLoader

更新时间:2026-07-11

在前面的自动配置原理中,我们反复提到 META-INF/spring.factories 文件以及 SpringFactoriesLoader。它们是 Spring Boot 实现“自动发现扩展组件”的核心机制,本质上是一套Spring 自定义的服务发现(SPI)机制。理解它的设计意图和加载流程,对于读懂自动配置源码、扩展 Spring Boot 功能至关重要。

24.3.1 为什么需要 Spring 自己的 SPI

Java 原生的 SPI(Service Provider Interface)机制允许在 META-INF/services 目录下以接口全限定名作为文件名,列出实现类,从而在运行时通过 ServiceLoader 动态加载。这一机制实现了“接口与实现分离”,但存在几个明显的使用痛点:

  • 延迟加载,无法提前感知失败ServiceLoader 是懒加载的迭代器,只有真正遍历时才实例化实现类,加载失败不会立即暴露。
  • 加载过程不可扩展:无法按条件筛选、排序或提供上下文信息,只能机械地加载所有实现。
  • 缺少与 Spring 容器的整合ServiceLoader 加载的对象不受 Spring IoC 容器管理,无法享受依赖注入、生命周期管理等 Spring 基础设施。

Spring 从 3.2 版本开始引入了 SpringFactoriesLoader,设计了一套更灵活的 SPI 机制,专门服务于框架内部和扩展点的自动发现。它基于 META-INF/spring.factories 文件,以键值对的形式定义接口与实现类的映射,并支持条件装配和排序,完美融入了 Spring 的生态体系。

24.3.2 spring.factories 文件的结构

在 Spring Boot 应用的 Classpath 中,经常可以在各个 Starter 的 JAR 包里发现 META-INF/spring.factories 文件。它的格式非常简单,每行一个实现类名,支持 \ 续行和 # 注释:

# 自动配置类注册
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.config.MyAutoConfiguration,\
com.example.config.SecurityAutoConfiguration

# ApplicationContext 初始化器
org.springframework.context.ApplicationContextInitializer=\
com.example.init.ContextInitializer

# 应用监听器
org.springframework.context.ApplicationListener=\
com.example.listener.AppStartedListener

键(key)通常是 Spring 框架中某个类型的全限定名,值(value)是该类型的一个或多个实现类全限定名。这种格式允许框架在启动时根据某个类型,批量检索并加载所有声明的扩展实现。

Spring Boot 利用这一文件,实现了自动配置类的注册中心:EnableAutoConfiguration 这个特殊键对应的所有自动配置类,会在启动阶段被读取,并按照条件装配规则选择性应用。

24.3.3 SpringFactoriesLoader 的工作流程

SpringFactoriesLoader 提供了两个核心静态方法:

  • loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader):加载指定类型的所有 spring.factories 中声明的实现类,并立即实例化。
  • loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader):仅返回实现类的全限定名字符串列表,不实例化。

其内部实现可以分为以下步骤:

  1. 定位文件:使用当前的 ClassLoader 去 Classpath 所有 JAR 包中寻找 META-INF/spring.factories 资源。ClassLoader.getResources("META-INF/spring.factories") 会返回多个 URL,确保合并所有依赖包中的声明。
  2. 解析键值对:以 Properties 文件格式读取,将每个键映射到一个 List<String>,其中字符串是逗号分隔的类名列表(注意,文件中用逗号或换行分隔,最终均被解析为集合)。
  3. 去重与缓存:解析结果会缓存到 ConcurrentReferenceHashMap 中,避免重复 I/O 操作。若有重复的实现类名,默认不进行去重(调用方自行处理)。
  4. 实例化loadFactories 会遍历得到的类名列表,通过反射 Class.forName 加载类,然后使用无参构造器创建实例。

源码片段(简化)如下:

public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
    List<String> factoryNames = loadFactoryNames(factoryType, classLoader);
    List<T> result = new ArrayList<>(factoryNames.size());
    for (String factoryName : factoryNames) {
        result.add(instantiateFactory(factoryName, factoryType, classLoader));
    }
    AnnotationAwareOrderComparator.sort(result); // 支持排序
    return result;
}

这里有一个容易被忽略的细节:loadFactories 在返回结果前,会使用 AnnotationAwareOrderComparator 对实例进行排序。这意味着实现类可以通过 @Order 注解或实现 Ordered 接口来控制加载后的执行顺序,这一特性在处理多个扩展实现时非常关键。

24.3.4 自动配置中的关键应用

Spring Boot 自动配置的入口 AutoConfigurationImportSelector 正是通过 SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, classLoader) 获取所有候选的自动配置类名。之后结合 @Conditional 条件注解过滤出实际需要应用的配置类,最终将这些类注册为 Bean 定义。

这意味着,任何一个第三方 Starter,只要在其 META-INF/spring.factories 中声明了 EnableAutoConfiguration 的实现类,就能自动参与到 Spring Boot 的自动配置流程中。例如 MyBatis 的 mybatis-spring-boot-autoconfigure JAR 就包含:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

这种低侵入的注册方式,是 Spring Boot 生态繁荣的重要支撑——框架无需写死任何第三方组件的全限定名,全部通过 SPI 动态发现。

24.3.5 其他扩展点的应用实例

除了自动配置,Spring Boot 还在多个内部流程中使用了 SpringFactoriesLoader

  • ApplicationContextInitializer:允许在 Spring 容器刷新前介入,定制应用上下文,例如激活某些 Profile 或添加属性源。声明在 spring.factories 中会被自动加载。
  • ApplicationListener:Spring Boot 在启动过程中会自动注册声明在 spring.factories 中的 ApplicationListener,从而在生命周期的各个阶段接收事件。
  • SpringApplicationRunListener:监听 Spring Boot 应用启动生命周期(开始启动、环境准备、上下文准备等),同样通过 spring.factories 加载,并在 SpringApplication.run() 的过程中回调。
  • FailureAnalyzer:应用启动失败时,分析异常并提供可读的诊断信息。所有自定义的 FailureAnalyzer 实现都可以通过 SPI 注册。
  • TemplateAvailabilityProvider:用于判断某类模板引擎是否可用,方便视图层自动配置。

这些扩展点共同构建了 Spring Boot 高度可插拔的架构,外部开发者不必修改框架源码,只需在 spring.factories 中添加一行声明,即可参与到核心流程中。

24.3.6 如何自定义一个 Spring SPI 扩展

假设我们要在 Spring Boot 启动时记录所有 Bean 的定义信息,可以创建一个自定义的 ApplicationListener 并注册:

第一步,实现监听器

public class BeanDefinitionLoggingListener implements ApplicationListener<ContextRefreshedEvent> {
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        ApplicationContext ctx = event.getApplicationContext();
        String[] beanNames = ctx.getBeanDefinitionNames();
        System.out.println("容器中共有 Bean 定义:" + beanNames.length);
    }
}

第二步,在 src/main/resources/META-INF/spring.factories 中注册

org.springframework.context.ApplicationListener=\
com.example.listener.BeanDefinitionLoggingListener

完成这两步后,任何依赖了该 JAR(或该项目自身)的 Spring Boot 应用,在启动时都会自动加载这个监听器,并输出 Bean 定义的数量。无需添加任何注解,也无需在启动类中手动注册。

24.3.7 与 Java SPI 的对比总结

| 特性 | Java SPI (ServiceLoader) | Spring SPI (SpringFactoriesLoader) |
|------|---------------------------|--------------------------------------|
| 配置文件位置 | META-INF/services/接口名 | META-INF/spring.factories |
| 配置格式 | 每行一个实现类名 | key = value1,value2,... 支持多键多值 |
| 加载时机 | 懒加载,遍历时实例化 | 主动加载,可立即实例化或仅获取类名 |
| 排序支持 | 无内置顺序 | 支持 @Order / Ordered 排序 |
| 与 Spring 集成 | 不受容器管理 | 可直接与 ApplicationContext 交互 |
| 主要用途 | JDBC 驱动、日志、脚本引擎等 | 自动配置、启动监听器、初始化器 |

在实际的 Spring Boot 项目中,两者并非互斥。Java SPI 依然被广泛使用(如 JDBC 驱动加载),而 Spring SPI 则专门服务于 Spring 生态内部的扩展体系。理解 SpringFactoriesLoader 的工作原理,是进行框架深度定制和编写高质量 Starter 的基础。

下一节,我们将利用这一机制,亲手编写一个可复用的 Spring Boot Starter,将自定义功能封装为自动化配置模块。