Spring boot接口扩展之SPI方式详解

程序设计的所有原则和方法论都是追求一件事——简单——功能简单、依赖简单、修改简单、理解简单。因为只有简单才好用,简单才好维护。因此,不应该以评论艺术品的眼光来评价程序设计是否优秀,程序设计的艺术不在于有多复杂多深沉,而在于能用多简单的方式实现多复杂的业务需求;不在于只有少数人能理解,而在于能让更多人理解。

Spring提供了强大的扩展体系,其中本篇介绍的SPI方式——即在META-INF目录下指定文件注册扩展实现的方式是其中主要的扩展方式之一,该种方式广泛应用在spring boot中用于自动加载的扩展实现。本篇先介绍spring在META-INF目录下约定了哪些可配置的文件,然后介绍了spring boot用于加载spring.factoriesaot.factories文件的SpringFactoriesLoader类,最后总结了spring.factories可配置扩展接口及这些接口的加载执行时机。我们在开发中可以根据这些时机定制自己的扩展实现,比如加载配置中心的动态配置。

1. META-INF目录下与spring相关的文件

文件名描述格式加载类
spring.handlers注册表,与xml命名空间对应处理类properties格式,k=v, k为命名空间描述符通常为http url格式,v为org.springframework.beans.factory.xml.NamespaceHandler实现类org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver使用
org.springframework.core.io.support.PropertiesLoaderUtils#loadAllProperties方法加载
spring.schemas注册表,与xml命名空间对应的xsd定义路径properties格式,k=v, k为命名空间xsd http url路径,v为xsd源码路径
spring.factoriesspring boot启动时的扩展类properties格式,k=v, k为接口名,v为接口实现,可以指定多个实现org.springframework.core.io.support.SpringFactoriesLoader
forDefaultResourceLocation()方法
spring/aot.factoriesspring boot aot编译扩展类properties格式,k=v, k为接口名,v为接口实现,可以指定多个实现org.springframework.beans.factory.aot.AotServices使用
org.springframework.core.io.support.SpringFactoriesLoader
spring/org.springframework.boot.
autoconfigure.AutoConfiguration.imports
spring boot3用于加载@AutoConfiguration和@Configuration注解类一行一个@AutoConfiguration类描述符ConfigurationClassPostProcessor工厂后处理器执行postProcessBeanDefinitionRegistry(...)时触发AutoConfigurationImportSelector使用ImportCandidates#load()方法加载

Spring boot3之后变动说明
springboot3之前配置在META-INF/spring.factories
文件中的org.springframework.boot.autoconfigure.EnableAutoConfigurationkey表示配置,需要迁移到META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件

如springboot3之前META-INF/spring.factories的配置:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  org.example.spring.TestImportAutoConfiguration1,\
  org.example.spring.TestImportAutoConfiguration2

springboot3之后使用META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件配置,内容如下:

org.example.spring.TestImportAutoConfiguration1
org.example.spring.TestImportAutoConfiguration2

2. SpringFactoriesLoader介绍

spring.factoriesaot.factories文件都是由SpringFactoriesLoader对象加载,
这个类提供如下两种静态工厂方法用于创建SpringFactoriesLoader对象。

方法名说明
forDefaultResourceLocation加载spring.factories中配置的类
forResourceLocation用于加载指定文件中的类,文件路径相对于classpath
loadFactoryNames从spring.factories中只加载指定扩展类型下所配置类型描述符

比如:

SpringFactoriesLoader loader = SpringFactoriesLoader.forResourceLocation("META-INF/spring.factories");
loader.

load(ApplicationContextInitializer .class);

SpringFactoriesLoader读取文件内容的方式也比较简单,如下:

protected static Map<String, List<String>> loadFactoriesResource(ClassLoader classLoader, String resourceLocation) {
    Map<String, List<String>> result = new LinkedHashMap<>();
    try {
        // 遍历classpath下所有指定文件名的文件
        Enumeration<URL> urls = classLoader.getResources(resourceLocation);
        while (urls.hasMoreElements()) {
            UrlResource resource = new UrlResource(urls.nextElement());
            // 加载为properties
            Properties properties = PropertiesLoaderUtils.loadProperties(resource);
            properties.forEach((name, value) -> {
                // 分解多个类并去除空格,多个类使用英文逗号隔开
                String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String) value);
                List<String> implementations = result.computeIfAbsent(((String) name).trim(),
                    key -> new ArrayList<>(factoryImplementationNames.length));
                Arrays.stream(factoryImplementationNames).map(String::trim).forEach(implementations::add);
            });
        }
        // 去重
        result.replaceAll(SpringFactoriesLoader::toDistinctUnmodifiableList);
    } catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load factories from location [" + resourceLocation + "]", ex);
    }
    // 返回结果
    return Collections.unmodifiableMap(result);
}

3. spring.factories文件中可扩展接口

spring.factories文件存放在jar包的META-INF目录下,用于定义spring boot的扩展接口,spring容器刷新前spring
boot会从该文件中加载扩展类来完成spring容器刷新前的准备。

注意:spring.factories中的扩展类对象不受spring容器管理,不能通过@Autowired等方式注入给bean对象。

下面根据扩展接口的执行顺序列出每个接口的用途和加载执行时机。

3.1 BootstrapRegistryInitializer

接口名:org.springframework.boot.BootstrapRegistryInitializer

用途:用于spring boot启动时创建完BootstrapRegistry对象后,往该注册表对象注册对象。BootstrapRegistry
的默认实现为DefaultBootstrapContext
DefaultBootstrapContext表示springboot的启动上下去,用于存放启动过中的对象,它只用于ApplicationStartingEvent
ApplicationEnvironmentPreparedEvent两个事件中,
ApplicationContextInitializedEvent
事件后就会执行其close方法,可以在此之前往启动上下文中添加监听器监听BootstrapContextClosedEvent事件。

何时加载:DefaultBootstrapContext创建时就会加载并执行

实现类提供构造函数:无参构造函数.

内置实现:无

3.2 SpringApplicationRunListener

接口名: org.springframework.boot.SpringApplicationRunListener

用途: 用于spring boot应用启动过程中发布事件

何时加载: spring boot启动时执行BootstrapRegistryInitializer#initialize()
方法后,就会加载并发布ApplicationStartingEvent事件

实现类构造函数可选参数:默认构造函数、SpringApplication,命令行参数String[] args

内置实现类

实现类说明
org.springframework.boot.context.event.EventPublishingRunListener默认使用

3.3 ApplicationListener

接口名: org.springframework.context.ApplicationListener

用途:用于监听spring boot启动过程中的各种事件。

何时加载:Spring boot启动创建SpringApplication对象时就会加载。

实现类提供构造函数:无参构造函数

另外,虽然spring.factories加载的扩展类对象不受spring容器管理,但在即将发布ApplicationPreparedEvent事件时这些监听器
可以实现ApplicationContextAware接口来获取spring容器对象,见下面EventPublishingRunListener源码,
同时spring boot会把这些监听器都添加到spring容器的事件管理器对象中,从而可以监听程序中所有的ApplicationEvent

public void contextLoaded(ConfigurableApplicationContext context) {
    for (ApplicationListener<?> listener : this.application.getListeners()) {
        if (listener instanceof ApplicationContextAware contextAware) {
            // 把spring容器对象传给监听器
            contextAware.setApplicationContext(context);
        }
        // 把监听器添加到容器的事件管理器
        context.addApplicationListener(listener);
    }
    // 发布事件
    multicastInitialEvent(new ApplicationPreparedEvent(this.application, this.args, context));
}

内置实现类

实现类说明
org.springframework.boot.context.config.DelegatingApplicationListener代理context.listener.classes配置的监听者类,在ApplicationEnvironmentPreparedEvent事件时加载
org.springframework.boot.context.logging.LoggingApplicationListener监听ApplicationStartingEvent创建对应LoggingSystem;监听ApplicationEnvironmentPreparedEvent加载日志配置启用日志功能
org.springframework.boot.env.EnvironmentPostProcessorApplicationListener监听ApplicationEnvironmentPreparedEvent事件执行EnvironmentPostProcessor对象

3.4 LoggingSystemFactory

接口名:org.springframework.boot.logging.LoggingSystemFactory

用途:提供用于创建LoggingSystem对象的工厂方法,LoggingSystem提供了日志系统初始化抽象,通过它可以加载我们的日志配置。

何时加载: LoggingApplicationListener监听spring boot应用启动触发的ApplicationStartingEvent事件时加载这些工厂类
,扫描classpath中存在的日志框架选择对应的工厂方法类创建LoggingSystem对象。

实现类提供构造函数:无参构造函数

日志配置何时生效:LoggingApplicationListener监听spring boot应用启动触发的ApplicationEnvironmentPreparedEvent
事件后初始化日志配置,即可使用日志系统(如logback)输出日志。

内置实现类

实现类说明加载条件说明
org.springframework.boot.logging.logback.LogbackLoggingSystem.Factory加载Logback日志如果classpath有ch.qos.logback.classic.LoggerContext则加载LogbackLoggingSystem
org.springframework.boot.logging.log4j2.Log4J2LoggingSystem.Factory加载og4J2日志如果classpath有org.apache.logging.log4j.core.impl.Log4jContextFactory则加载
org.springframework.boot.logging.java.JavaLoggingSystem.Factory加载java日志如果classpath有java.util.logging.LogManager

3.5 EnvironmentPostProcessor

接口名: org.springframework.boot.env.EnvironmentPostProcessor

用途:spring boot触发ApplicationEnvironmentPreparedEvent事件时执行,可用于做些环境准备

何时加载:spring boot触发ApplicationEnvironmentPreparedEvent事件时由EnvironmentPostProcessorApplicationListener加载

实现类构造函数可选参数:默认构造函数、DeferredLogFactoryConfigurableBootstrapContextBootstrapContextBootstrapRegistry

内置实现类

实现类说明
org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor用于加载配置文件
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor解析spring.application.json的json字符串配置
org.springframework.boot.reactor.ReactorEnvironmentPostProcessorspring.reactor.debug-agent.enabled=true时执行ReactorDebugAgent#init方法

3.6 ConfigDataLocationResolver

接口名:org.springframework.boot.context.config.ConfigDataLocationResolver

用途: 策略类,用于把指定文件解析为ConfigDataResource对象以便ConfigDataLoader对象使用,ConfigDataResource
内置的有StandardConfigDataResourceConfigTreeConfigDataResource两具体实现

何时加载:spring boot启动时发布ApplicationEnvironmentPreparedEvent事件时,由ConfigDataEnvironmentPostProcessor执行时

实现类构造函数可选参数:默认构造函数、DeferredLogFactoryConfigurableBootstrapContextBootstrapContextBootstrapRegistry
BinderResourceLoader

内置实现类

实现类说明
org.springframework.boot.context.config.ConfigTreeConfigDataLocationResolver把目录下构造为ConfigTreeConfigDataResource对象
org.springframework.boot.context.config.StandardConfigDataLocationResolver把配置文件构造为StandardConfigDataResource对象

3.7 PropertySourceLoader

接口名:org.springframework.boot.env.PropertySourceLoader

用途: 策略类,用于StandardConfigDataLocationResolver对象把配置加载为PropertySource,不同配置来源实现不同的加载策略。
PropertySource用于持有应用不同级别的配置信息,比如ServletContext、ServletConfig、System Properties、命令行配置、以及自定义的动态数据源。

何时加载:StandardConfigDataLocationResolver创建时加载

实现类提供构造函数:无参构造函数

内置实现类

实现类说明
org.springframework.boot.env.PropertiesPropertySourceLoader用于记载properties、xml扩展名的文件内容
org.springframework.boot.env.YamlPropertySourceLoader用于加载yaml、yml扩展名的文件的内容

3.8 ConfigDataLoader

接口名:org.springframework.boot.context.config.ConfigDataLoader

用途:策略类,加载配置文件,用于加载ConfigDataResource对象中代表的内容封装到为ConfigData
对象,这个对象用于创建ConfigDataEnvironmentContributor

何时加载: spring boot启动时发布ApplicationEnvironmentPreparedEvent事件时,由ConfigDataEnvironmentPostProcessor执行时

实现类构造函数可选参数:默认构造函数、DeferredLogFactoryConfigurableBootstrapContextBootstrapContextBootstrapRegistry

实现类提供构造函数:无参构造函数

内置实现类

实现类说明
org.springframework.boot.context.config.ConfigTreeConfigDataLoader加载ConfigTreeConfigDataResource指定目录下的文件内容为PropertySource并封装为ConfigData,其中key为相对于指定目录的文件path,value为文件内容
org.springframework.boot.context.config.StandardConfigDataLoader加载StandardConfigDataResource代表的内容为PropertySource并封装为ConfigData,比如是yml配置则使用YamlPropertySourceLoader加载

3.9 ApplicationContextFactory

接口名:org.springframework.boot.ApplicationContextFactory

用途: 抽象工厂,用于创建ConfigurableEnvironment环境配置对象和ApplicationContext容器对象

何时加载: 在创建ConfigurableEnvironment前就会根据应用类型WebApplicationType
选择可以支持的ApplicationContextFactory实现类,通过该抽象工厂创建ConfigurableEnvironment对象

实现类提供构造函数:无参构造函数

内置实现类

实现类说明
org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContextFactory如果是native或开启了AOT则创建ReactiveWebServerApplicationContext,否则创建AnnotationConfigReactiveWebServerApplicationContext
org.springframework.boot.web.servlet.context.ServletWebServerApplicationContextFactory如果是native或开启了AOT则创建ServletWebServerApplicationContext,否则创建AnnotationConfigServletWebServerApplicationContext
org.springframework.boot.DefaultApplicationContextFactory上面两个不可用时,使用该工厂方法创建,如果是native或开启了AOT则创建GenericApplicationContext,否则创建AnnotationConfigApplicationContext

3.10 ApplicationContextInitializer

接口名: org.springframework.context.ApplicationContextInitializer

用途:spring容器ApplicationContext对象创建完成后,用于对容器做进一步的初始化操作,比如

  1. 往容器添加BeanFactoryPostProcessor工厂后处理器。
  2. 往spring容器的BeanFactory中注入单例对象。

何时加载:Spring boot启动创建SpringApplication对象时就会加载

实现类提供构造函数:无参构造函数

何时执行:创建ApplicationContext对象后,发布ApplicationContextInitializedEvent时间前执行。

内置实现类

实现类说明
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer添加ConfigurationWarningsPostProcessor工厂后处理器用于检查@ComponentScan的包是否正确
org.springframework.boot.context.config.DelegatingApplicationContextInitializer代理context.initializer.classes配置指定的初始化器对象

3.11 EnableAutoConfiguration

接口名:org.springframework.boot.autoconfigure.EnableAutoConfiguration

用途:@Configuration注解的类不需要通过spring的包扫描器扫描到,只需要通过在spring.factories文件中注册即可,

说明:spring3之前使用这种方式,sprinboot3后使用文件
spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

何时加载:ConfigurationClassPostProcessor后处理执行postProcessBeanDefinitionRegistry方法时加载。

内置实现类
EnableAutoConfiguration只是一个注解类,它没有具体实现,它在spring.factories只属于一个标识key,
真正加载时使用这个key来获取所有配置的类。见spring3之前AutoConfigurationImportSelector类的源码,如下。

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
        getBeanClassLoader());
    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
        + "are using a custom packaging, make sure that file is correct.");
    return configurations;
}

protected Class<?> getSpringFactoriesLoaderFactoryClass() {
    return EnableAutoConfiguration.class;
}
  • 4
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值