Spring Boot 的 SPI 机制?
Spring Boot 的 SPI (Service Provider Interface) 机制是其核心特性之一,也是实现 “约定大于配置” (Convention over Configuration) 和 自动装配 (Auto-configuration) 的基石。
虽然 Java 本身提供了标准的 SPI 机制(java.util.ServiceLoader),但 Spring Boot 为了满足更复杂的应用场景(如类加载顺序、条件加载等),实现了一套自己的 SPI 机制。
以下是关于 Spring Boot SPI 机制的详细解析:
1. 核心概念与文件
Spring Boot 的 SPI 机制主要依赖于一个特定的文件和加载器:
- 配置文件:
META-INF/spring.factories - 加载工具类:
SpringFactoriesLoader
怎么工作的?
当 Spring Boot 应用启动时,它会扫描所有 Jar 包(包括依赖的第三方库)类路径下的 META-INF/spring.factories 文件。这个文件是一个标准的 Properties 格式文件,包含了一组 Key-Value 对:
- Key (接口/注解全限定名): 通常是 Spring 的接口或注解,例如
org.springframework.boot.autoconfigure.EnableAutoConfiguration。 - Value (实现类全限定名列表): 逗号分隔的实现类名列表。
2. Spring Boot SPI vs Java SPI
| 特性 | Java SPI (ServiceLoader) |
Spring Boot SPI (SpringFactoriesLoader) |
|---|---|---|
| 配置文件位置 | META-INF/services/接口全限定名 |
META-INF/spring.factories |
| 文件内容 | 纯文本,每行一个实现类名 | Properties 格式,Key=ValueList |
| 加载方式 | 懒加载 (Iterator),遍历时实例化 | 一次性读取所有配置,实例化并缓存 |
| 扩展性 | 较弱,无法通过注解控制顺序 | 强,支持 @Order、@Priority 排序 |
| 灵活性 | 只能获取实现类 | 可以获取配置类、监听器、过滤器等多种类型 |
3. 核心应用场景:自动装配
这是 Spring Boot SPI 最著名的应用场景。
- 入口:
@SpringBootApplication注解包含@EnableAutoConfiguration。 - 导入:
@EnableAutoConfiguration通过@Import(AutoConfigurationImportSelector.class)导入选择器。 - 扫描:
AutoConfigurationImportSelector内部调用SpringFactoriesLoader.loadFactoryNames()。 - 查找: 它会在所有 jar 包的
META-INF/spring.factories中查找 Key 为org.springframework.boot.autoconfigure.EnableAutoConfiguration的值。 - 筛选: 加载这些配置类,并结合
@Conditional系列注解(如@ConditionalOnClass,@ConditionalOnProperty)决定是否真正将 Bean 注册到 Spring 容器中。
spring.factories 示例片段:
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration
4. 源码简析 (SpringFactoriesLoader)
SpringFactoriesLoader 是核心工具类,主要方法是 loadFactories 和 loadFactoryNames。
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
// 1. 加载所有 spring.factories 文件中的配置
// 2. 获取指定 factoryType (Key) 对应的实现类列表 (Value)
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
// 缓存机制,避免重复 IO
Map<String, List<String>> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
// 读取所有 jar 包下的 META-INF/spring.factories
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
// 解析 Properties 并合并
// ... (省略解析代码)
return result;
} catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
5. Spring Boot 2.7+ 和 3.x 的变化
注意:这是一个重要的更新点。
从 Spring Boot 2.7 开始,官方引入了一个新的文件位置用于自动配置,并在 Spring Boot 3.0 中移除了对 spring.factories 中自动配置键的支持(其他类型的 SPI 依然使用 spring.factories)。
- 旧方式:
META-INF/spring.factories - 新方式:
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
新文件格式:
不再是 Key-Value 格式,而是纯文本列表(类似 Java SPI),每行一个自动配置类名。
com.example.MyAutoConfiguration
com.example.AnotherAutoConfiguration
原因: 为了提高兼容性和处理速度,并为 GraalVM Native Image 提供更好的支持。
6. 实战:如何自定义一个 Starter
利用 SPI 机制,我们可以轻松编写自定义 Starter:
- 创建配置类: 编写一个带有
@Configuration和@Bean的类。java@Configuration @ConditionalOnClass(MyService.class) public class MyAutoConfiguration { @Bean public MyService myService() { return new MyServiceImpl(); } } - 注册 SPI:
- Spring Boot 2.x: 在
src/main/resources/META-INF/spring.factories中添加:plaintextorg.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.example.MyAutoConfiguration - Spring Boot 3.x: 在
src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports中添加:plaintextcom.example.MyAutoConfiguration
- Spring Boot 2.x: 在
- 打包使用: 将该项目打成 Jar 包,其他项目引入该依赖后,Spring Boot 启动时就会自动扫描并加载
MyAutoConfiguration。
总结
Spring Boot 的 SPI 机制是其模块化和插件化的核心。它通过 SpringFactoriesLoader 扫描 META-INF/spring.factories(或新的 .imports 文件),实现了在不修改核心代码的情况下,通过添加 Jar 包即可动态扩展系统功能的能力。