基于本文回答
0
评论

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 最著名的应用场景。

  1. 入口: @SpringBootApplication 注解包含 @EnableAutoConfiguration
  2. 导入: @EnableAutoConfiguration 通过 @Import(AutoConfigurationImportSelector.class) 导入选择器。
  3. 扫描: AutoConfigurationImportSelector 内部调用 SpringFactoriesLoader.loadFactoryNames()
  4. 查找: 它会在所有 jar 包的 META-INF/spring.factories 中查找 Key 为 org.springframework.boot.autoconfigure.EnableAutoConfiguration 的值。
  5. 筛选: 加载这些配置类,并结合 @Conditional 系列注解(如 @ConditionalOnClass, @ConditionalOnProperty)决定是否真正将 Bean 注册到 Spring 容器中。

spring.factories 示例片段:

plaintext
# 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 是核心工具类,主要方法是 loadFactoriesloadFactoryNames

java
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),每行一个自动配置类名。

plaintext
com.example.MyAutoConfiguration
com.example.AnotherAutoConfiguration

原因: 为了提高兼容性和处理速度,并为 GraalVM Native Image 提供更好的支持。

6. 实战:如何自定义一个 Starter

利用 SPI 机制,我们可以轻松编写自定义 Starter:

  1. 创建配置类: 编写一个带有 @Configuration@Bean 的类。
    java
    @Configuration
    @ConditionalOnClass(MyService.class)
    public class MyAutoConfiguration {
        @Bean
        public MyService myService() {
            return new MyServiceImpl();
        }
    }
  2. 注册 SPI:
    • Spring Boot 2.x:src/main/resources/META-INF/spring.factories 中添加:
      plaintext
      org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
      com.example.MyAutoConfiguration
    • Spring Boot 3.x:src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 中添加:
      plaintext
      com.example.MyAutoConfiguration
  3. 打包使用: 将该项目打成 Jar 包,其他项目引入该依赖后,Spring Boot 启动时就会自动扫描并加载 MyAutoConfiguration

总结

Spring Boot 的 SPI 机制是其模块化插件化的核心。它通过 SpringFactoriesLoader 扫描 META-INF/spring.factories(或新的 .imports 文件),实现了在不修改核心代码的情况下,通过添加 Jar 包即可动态扩展系统功能的能力。

右滑查看面试常问