重学SpringBoot3-SPI机制

重学SpringBoot3-SPI机制

CoderJia 30 2024-05-06

Spring Boot 的核心特性之一是其强大的自动配置功能,它极大地简化了 Spring 应用程序的配置。这种自动配置部分依赖于 Spring Boot 的服务提供者接口(SPI)机制,它允许开发者以模块化和可插拔的方式扩展和定制框架行为。接下来将详细探讨 Spring Boot 3 中的 SPI 机制,并通过示例展示如何实际使用它来实现自动配置。

什么是 SPI?

SPI(Service Provider Interface)即服务提供者接口,是一种服务发现机制,允许开发者和框架发现和加载可用的服务实现,而不需要在代码中硬编码具体的实现。这种机制使得软件系统能够更加灵活和可扩展。

在 Java 平台上,SPI 通常是通过 java.util.ServiceLoader 类实现的,但 Spring Boot 对这一概念进行了扩展,以支持其自动配置和模块化架构。

Spring Boot 中的 SPI 机制

Spring Boot 利用 spring.factories (注意:从 SpringBoot 2.7 起自动配置不推荐使用 /META-INF/spring.factories 文件,而是在/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports)文件,位于每个项目根目录的 META-INF/ 目录下,来实现其 SPI 机制。这个文件列出了与自动配置相关的接口及其实现类,Spring Boot 启动时会加载这些配置。

spring.factories 文件

这个文件使用键值对的格式列出了多种服务类型及其对应的实现类,常见的服务类型包括:

  • org.springframework.boot.autoconfigure.EnableAutoConfiguration:用于自动配置。
  • org.springframework.context.ApplicationListener:用于应用事件监听器。
  • org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider:用于模板引擎的可用性判断。

以下是 spring.factories 文件的一个典型例子:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.JpaAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration

org.springframework.context.ApplicationListener=\
com.example.MyApplicationListener

Spring Boot 3 则要在 resources/META_INFO/spring/ 目录下新建 org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件,并填入需要自动配置的类的全路径:

com.example.JpaAutoConfiguration

自动配置的实现

在 Spring Boot 中,自动配置类使用 @ConditionalXxx 注解来控制配置类的加载条件。这允许 Spring Boot 根据当前应用的环境,如类路径上的类、环境变量、系统属性等,条件性地应用配置,例如,一个自动配置类可能只在 JPA 实体类存在时才加载:

@Configuration
@ConditionalOnClass(name = "javax.persistence.Entity")
public class JpaAutoConfiguration {
    // 配置 JPA 相关的 beans
}

启动流程中的作用

在 Spring Boot 的启动流程中,当 SpringApplication 类被调用时,它将初始化一个 SpringApplicationContext。在这个过程中,Spring Boot 通过 SpringFactoriesLoader 类来加载 spring.factoriesorg.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中定义的各种组件,包括自动配置类。然后,根据应用的实际环境(比如类路径上的库和定义的 Beans),以及自动配置类上的条件注解,决定是否激活这些自动配置类。

SPI实际应用

让我们通过一个自定义 stater 示例来展示如何创建和使用自定义的自动配置。

步骤 1: 新建模块

自定义的 starter 不需要启动类,需要删掉。

步骤 2: 创建自动配置类

创建一个自动配置类,这个类将提供一个服务,仅在某个特定的类如 SpecificClass 存在于启动类类路径上时才加载 MyService 类型的 bean。

package com.coderjia.spi.config;

import com.coderjia.spi.service.MyService;
import com.coderjia.spi.service.impl.MyServiceImpl;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author CoderJia
 * @create 2024/5/5 下午 03:29
 * @Description
 **/

@Configuration
@ConditionalOnClass(name = "com.coderjia.features.config.SpecificClass")
public class MyAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public MyService MyService() {
        return new MyServiceImpl();
    }
}

MyService 类简单实现:

public class MyServiceImpl implements MyService {
    @Override
    public void sayHello() {
        System.out.println("hello");
    }
}

步骤 3: 注册自动配置类

接下来,SpringBoot 2.7 以下在模块的 META-INF/spring.factories 文件中注册这个自动配置类:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.coderjia.spi.config.MyAutoConfiguration

SpringBoot 2.7 及以上在 /META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中添加自动配置类:

com.coderjia.spi.config.MyAutoConfiguration

这样配置后,只要 SpecificClass 类存在于类路径上,Spring Boot 就会自动配置 MyService

步骤 4: 使用

在其他项目中,只需 pom 文件中引入我们自定义的 starter 依赖,并确保 SpecificClass 类在类路径上,Spring Boot 将自动应用这个配置。

        <dependency>
            <groupId>com.coderjia</groupId>
            <artifactId>spring-boot3-07-spi</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

步骤 5: 测试

首先,如果当前项目中启动类所在路径及其子路径不存在 SpecificClass 类,则从 IOC 容器中取不到 MyService

不存在SpecificClass类

当在当前项目中创建了 SpecificClass 类,则可以正常拿到 MyService bean:

存在SpecificClass类

最佳实践与注意事项

在利用 Spring Boot 的 SPI 机制开发自定义 starter 或自动配置时,有几个最佳实践和注意事项可以帮助确保项目的成功和维护性:

1. 明确条件注解

使用条件注解(如 @ConditionalOnClass@ConditionalOnBean)时,应尽量明确条件,确保你的自动配置仅在满足特定条件时才应用。这避免了配置冲突和非预期的行为,特别是在复杂的项目中。

2. 避免类路径问题

在设计自动配置时,注意类路径上可能出现的冲突。例如,如果自动配置依赖于某个库,确保这个库不会与项目中已有的库版本冲突。

3. 使用 @ConditionalOnMissingBean

在定义提供服务的 Bean 时,使用 @ConditionalOnMissingBean 注解可以确保在应用中已存在相同类型的 Bean 时,自动配置不会再创建新的 Bean,从而避免重复配置。

4. 文档和示例

为你的自定义 starter 或自动配置提供详细的文档和使用示例。这对于其他开发者来说是非常有价值的,特别是在解决依赖和配置问题时。

5. 分离自动配置和业务逻辑

将自动配置代码和业务逻辑代码分开,自动配置模块不应包含业务逻辑,它只应负责自动装配和条件判断。这样做可以提高模块的清晰度和重用性。

总结

Spring Boot 的 SPI 机制不仅强化了框架的自动配置能力,还提供了一种灵活且强大的方式来扩展和定制应用程序的行为。通过合理利用这一机制,开发者可以显著简化 Spring 应用的配置过程,加速开发和部署周期。