程序设计的所有原则和方法论都是追求一件事——简单——功能简单、依赖简单、修改简单、理解简单。因为只有简单才好用,简单才好维护。因此,不应该以评论艺术品的眼光来评价程序设计是否优秀,程序设计的艺术不在于有多复杂多深沉,而在于能用多简单的方式实现多复杂的业务需求;不在于只有少数人能理解,而在于能让更多人理解。
Spring提供了强大的扩展体系,其中本篇介绍的SPI方式——即在META-INF目录下指定文件注册扩展实现的方式是其中主要的扩展方式之一,该种方式广泛应用在spring boot中用于自动加载的扩展实现。本篇先介绍spring在META-INF目录下约定了哪些可配置的文件,然后介绍了spring boot用于加载spring.factories
和aot.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.factories | spring boot启动时的扩展类 | properties格式,k=v, k为接口名,v为接口实现,可以指定多个实现 | org.springframework.core.io.support.SpringFactoriesLoader 的 forDefaultResourceLocation() 方法 |
spring/aot.factories | spring 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.EnableAutoConfiguration
key表示配置,需要迁移到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.factories
和aot.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
加载
实现类构造函数可选参数:默认构造函数、DeferredLogFactory
,ConfigurableBootstrapContext
、BootstrapContext
、BootstrapRegistry
内置实现类
实现类 | 说明 |
---|---|
org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor | 用于加载配置文件 |
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor | 解析spring.application.json 的json字符串配置 |
org.springframework.boot.reactor.ReactorEnvironmentPostProcessor | spring.reactor.debug-agent.enabled=true 时执行ReactorDebugAgent#init 方法 |
3.6 ConfigDataLocationResolver
接口名:org.springframework.boot.context.config.ConfigDataLocationResolver
用途: 策略类,用于把指定文件解析为ConfigDataResource
对象以便ConfigDataLoader
对象使用,ConfigDataResource
内置的有StandardConfigDataResource
和ConfigTreeConfigDataResource
两具体实现
何时加载:spring boot启动时发布ApplicationEnvironmentPreparedEvent
事件时,由ConfigDataEnvironmentPostProcessor
执行时
实现类构造函数可选参数:默认构造函数、DeferredLogFactory
、ConfigurableBootstrapContext
、BootstrapContext
、BootstrapRegistry
、Binder
、ResourceLoader
内置实现类
实现类 | 说明 |
---|---|
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
执行时
实现类构造函数可选参数:默认构造函数、DeferredLogFactory
、ConfigurableBootstrapContext
、BootstrapContext
、BootstrapRegistry
实现类提供构造函数:无参构造函数
内置实现类
实现类 | 说明 |
---|---|
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
对象创建完成后,用于对容器做进一步的初始化操作,比如
- 往容器添加
BeanFactoryPostProcessor
工厂后处理器。 - 往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;
}