Spring Boot v2.4.4源码解析配置文件加载篇一 #2.4版本与2.3版本差异

Posted u一枚蒟蒻

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring Boot v2.4.4源码解析配置文件加载篇一 #2.4版本与2.3版本差异相关的知识,希望对你有一定的参考价值。

Spring Boot 2.4版本与2.3版本处理配置文件差异

一、概述

Spring Boot v2.4.4源码解析(三)事件机制篇一这篇博客中可以看出,在Spring Boot启动过程中包含很多关键时间节点,EventPublishingRunListener会将这些时间节点封装成事件并通过SimpleApplicationEventMulticaster广播出去。
在这些时间节点中,有一个environmentPrepared,表示环境已经准备好,如果需要对环境做定制化操作的组件,可以监听该事件。Spring Boot正是在该时间节点将配置文件加载到环境EnvironmentpropertySources属性中。

在事件广播器SimpleApplicationEventMulticaster 广播事件方法multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) 内部打断点,断点条件是event instanceof org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent,表示发布ApplicationEnvironmentPreparedEvent事件时进入断点。

然后在Variables中添加getApplicationListeners(event, type)查看有哪些事件监听器监听ApplicationEnvironmentPreparedEvent事件。
在Spring Boot 2.3版本和2.4版本中会发现有如下ApplicationEnvironmentPreparedEvent事件监听器:

二、变更原因

注意,2.3版本中ConfigFileApplicationListener和2.4版本中EnvironmentPostProcessorApplicationListener正是处理配置文件的事件监听器。
为什么Spring Boot 2.4版本要放弃ConfigFileApplicationListener而使用EnvironmentPostProcessorApplicationListener处理配置文件加载呢?

Config file processing in Spring Boot 2.4这篇博客给出了答案。

总结主要原因如下:
  • 支持Kubernetes卷挂载配置;
    卷挂载配置是 Kubernetes 的一个流行功能,其中ConfigMap可以将环境配置信息和容器镜像解耦便于应用配置的修改。
    这块内容可以参考K8s 集群使用 ConfigMap 优雅加载 Spring Boot 配置文件
  • ConfigFileApplicationListener本身存在一些问题;
    Config file processing in Spring Boot 2.4文章中, 作者将ConfigFileApplicationListener 比喻为陷阱冒险(trap adventures),并不是指其代码质量不高,或者测试覆盖不全面,而是指很难添加新功能。
    在处理特定profile配置文件(主要YAML文件)会遇到两个问题:
    • 可以在这些配置文件中激活附加的profiles;
    • 很难弄清楚配置文件的添加顺序;

例如,在YAML文件中可以包含多个逻辑文件,这些逻辑文件之间用---分隔开。考虑如下包含三个逻辑文件的YAML文件:

security.user.password: usera
---
spring.profiles: local
security.user.password: userb
runlocal: true
---
spring.profiles: prod
spring.profiles.include: local
security.user.password: userc

在Spring Boot 2.3中添加VM参数-Dspring.profiles.active=prod并启动,获取security.user.passwordrunlocal配置,会发现其值分别为userbtrue。是不是感觉有点奇怪,在处理中间文件时,该文件的profiles还未被激活,但是最终该文件还是被包含进来了。
所以在Spring Boot 2.4中对加载properties和YAML配置文件方式作出了两个重大改变,旨在简化和合理化加载外部配置的方式:

  • 将按照定义顺序加载文件;
  • 不能在特定profile文件中激活profiles;

三、主要变更点

1. YAML 多逻辑文件顺序变更

对于Spring Boot 2.3及更早版本, YAML中的多个逻辑文件基于profile激活顺序添加,而在2.4版本中,则按照文件声明顺序添加。
例如,对于如下YAML文件:

test: "value"
---
test: "overridden-value"

最终test属性值为overridden-value

2. 增加 Properties 文件多逻辑文件

在 Spring Boot 2.4 中,在Java properties文件中可以使用和YAML类似的多逻辑文件,多个逻辑文件之间用#---隔开。
例如:

test=value
#---
test=overridden-value

3. Jar 包外部配置文件和内部配置文件顺序变更

在Spring Boot 2.3及更早版本中,Jar包外部的application.properties文件不会覆盖Jar包内部的application-<profile>.properties文件。
从Spring Boot 2.4开始,外部文件总是覆盖Jar包内部文件(application-<profile>.properties文件或者其他文件);
具体配置文件加载顺序可以参考Application properties outside of jar should take precedence over profile-specific variants inside the jarSpring Boot Reference Documentation 2.3.6.RELEASESpring Boot Reference Documentation 2.4.1

Spring Boot 2.3及更早版本配置文件优先级(从上到下优先级降低):

  • Jar包外部的application-{profile}.properties文件及application-{profile}.yml
  • Jar包内部的application-{profile}.properties文件及application-{profile}.yml
  • Jar包外部的application.propertiesapplication.yml;
  • Jar包内部的application.propertiesapplication.yml;

Spring Boot 2.4配置文件优先级(从上到下优先级降低):

  • Jar包外部的application-{profile}.properties文件及application-{profile}.yml
  • Jar包外部的application.propertiesapplication.yml;
  • Jar包内部的application-{profile}.properties文件及application-{profile}.yml
  • Jar包内部的application.propertiesapplication.yml;

4. spring.profiles 配置项迁移

在Spring Boot 2.3及更早版本中,可以在YAML的多逻辑文件中使用spring.profiles指定该配置文件生效的profile,在2.4版本中,将该配置项迁移到spring.config.activate.on-profile

5. Profile 激活

Spring Boot中可以使用spring.profiles.active配置项激活特定的profiles,例如可以使用如下命令行激活prod

$ java -jar myapp.jar --spring.profiles.active=prod

当然也可以在application.properties或者application.yaml文件中配置该配置项,但是在Spring Boot 2.4中,不能在特定profile文件中设置该配置项值。也就是说,在Spring Boot 2.4中,配置项spring.config.activate.on-profilespring.profiles.active不能同时设置。类似地,配置项spring.config.activate.on-profilespring.profiles.include也不能同时设置。
例如,如下配置将会报错:

spring:
  config:
    activate:
      on-profile: "prod"
  profiles:
    include: "metrics"

为什么会增加这样一个限制呢?
加了这个限制后,同一个文件中on-profile条件只会计算一次,前文这个问题就会解决。

6. Profile 组

由于Spring Boot 2.4不能同时设置spring.config.activate.on-profilespring.profiles.active,也不能同时设置spring.config.activate.on-profilespring.profiles.include,那对于2.3版本使用spring.profiles + spring.profiles.include扩展激活profiles这种情况怎么兼容呢?
例如,如下application.yaml

spring.profiles: "debug"
spring.profiles.include: "debugdb,debugcloud"

如果迁移到Spring Boot 2.4 application.yaml变更为:

spring:
  config:
    activate:
      on-profile: "debug"
  profiles:
    include: "debugdb,debugcloud"

但是不能同时设置spring.config.activate.on-profilespring.profiles.include
为了解决这类问题,Spring Boot 2.4 提出Profile组概念,Profile组可以完成这样一件事:如果profile 'x’处于激活状态,profiles ‘y’ 和 ‘z’ 也处于激活状态。配置项格式为spring.profiles.group.<source>,例如,上面的配置可以这样写:

spring:
  profiles:
    group:
      "debug": "debugdb,debugcloud"

同样,也不能同时设置配置项spring.profile.groupspring.config.activate.on-profile的值。

7. 导入附加配置

配置文件加载成功后,会以OriginTrackedMapPropertySource形式存在EnvironmentPropertySources属性中。可以在Spring Boot启动类中增加如下代码,查看引入了哪些配置文件:

ConfigurableApplicationContext context = SpringApplication.run(DemoApplication.class, args);
MutablePropertySources propertySources = context.getEnvironment().getPropertySources();
propertySources.forEach( s -> {
    if(s instanceof OriginTrackedMapPropertySource){
        System.err.println(s);
    }
});

在Spring Boot 2.3版本中,配置文件默认搜索顺序为(从上到下优先级降低):

  • file:./config/
  • file:./config/*/
  • file:./
  • classpath:/config/
  • classpath:/

在Jar包内部resources目录,Jar包内部resources/config目录,Jar包当前目录,Jar包当前/config目录,Jar包当前/config/fsx目录下面分别放置application.yml,然后以java -jar target/demo-0.0.1-SNAPSHOT.jar命令行运行该Jar包,会发现如下输出:

OriginTrackedMapPropertySource {name='applicationConfig: [file:./config/application.yml]'}
OriginTrackedMapPropertySource {name='applicationConfig: [config/fsx/application.yml]'}
OriginTrackedMapPropertySource {name='applicationConfig: [file:./application.yml]'}
OriginTrackedMapPropertySource {name='applicationConfig: [classpath:/config/application.yml]'}
OriginTrackedMapPropertySource {name='applicationConfig: [classpath:/application.yml]'}

搜索配置文件名称默认为application,可以通过配置项spring.config.name配置,也可以使用配置项spring.config.location明确指定配置文件搜索顺序(多个文件或者目录质检用逗号隔开)。
例如,以命令行java -jar target/demo-0.0.1-SNAPSHOT.jar --spring.config.location=classpath:/,file:./ 启动时,输出如下:

OriginTrackedMapPropertySource {name='applicationConfig: [file:./application.yml]'}
OriginTrackedMapPropertySource {name='applicationConfig: [classpath:/application.yml]'}

可以通过spring.config.additional-location在默认的搜索顺序上增加搜索路径,该配置项的值优先默认的搜索顺序。
在Jar包路径/custom-config目录下添加application.yml文件,并以命令行java -jar target/demo-0.0.1-SNAPSHOT.jar --spring.config.additional-location=file:./custom-config启动时,输出如下:

OriginTrackedMapPropertySource {name='applicationConfig: [file:custom-config/application.yml]'}
OriginTrackedMapPropertySource {name='applicationConfig: [file:./config/application.yml]'}
OriginTrackedMapPropertySource {name='applicationConfig: [config/fsx/application.yml]'}
OriginTrackedMapPropertySource {name='applicationConfig: [file:./application.yml]'}
OriginTrackedMapPropertySource {name='applicationConfig: [classpath:/config/application.yml]'}
OriginTrackedMapPropertySource {name='applicationConfig: [classpath:/application.yml]'}

此时,配置文件默认搜索顺序为(从上到下优先级降低):

  • file:./custom-config/
  • classpath:custom-config/
  • file:./config/
  • file:./config/*/
  • file:./
  • classpath:/config/
  • classpath:/

但有一个问题,这几个配置项必须配置为环境属性,例如操作系统环境变量,系统变量,命令行参数,不能中application.properties或者 application.yml中指定,且文件名仅限于.properties, .yaml, 或者.yml
在Spring Boot 2.4 中新增了配置项spring.config.import,可以使用该配置项导入附加配置, 且该配置项可以在application.properties或者 application.yml中设置,导入文件添加顺序在定义该配置项文件之后。

8. 卷挂载配置树

如果使用spring.config.import导入附加配置时,该配置项的值没有前缀,就认为导入常规文件或者文件夹,如果以configtree作为前缀,则告诉Spring Boot该路径为Kubernetes风格的卷加载配置树。
例如,在application.properties中定义如下配置项:

spring.config.import=configtree:/etc/config

如果有如下挂载内容:

etc/
 +- config/
     +- my/
     |  +- application
     +- test

那么,最终会在Environment中增加两个属性:my.applicationtest,其值分别为/etc/config/my/application/etc/config/test

9. 云平台激活

如果只想卷挂载配置树在指定的云平台下才激活,可以使用spring.config.activate.on-cloud-platform配置。该配置和spring.config.activate.on-profile相似,只不过使用的是云平台的值,而非profile名称。

例如,上面配置只在Kubernetes下部署才生效,可以如下配置

spring.config.activate.on-cloud-platform=kubernetes
spring.config.import=configtree:/etc/config

10. 第三方配置源

配置项spring.config.import的值完全是可拔插的,我们可以自定义扩展。可以想象第三方Jar来支持诸如archaius://...vault://...zookeeper://...之类的配置路径。
这部分源码可以参考org.springframework.boot.context.config包下的ConfigDataLocationResolverConfigDataLoader类。

11. 仍然使用Spring Boot 2.3配置方式

如果Spring Boot 2.4这种变更让你很不爽,想在Spring Boot 2.4 下使用原有配置文件加载方式, 可以在application.properties或者 application.yml文件中将配置项spring.config.use-legacy-processing置为true,那么Spring Boot将沿用2.3版本方式处理配置文件。
Spring Boot版本迁移时,一些配置项的变更可能对我们造成困惑,我们可以在pom文件中引入spring-boot-properties-migrator模块,引入后在项目启动时,当你的配置文件中存在被识别的已经移除的属性时,将会日志打印提示。

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-properties-migrator</artifactId>
	<scope>runtime</scope>
</dependency>

版本迁移完成后,再将该模块从pom文件中移除。

参考

以上是关于Spring Boot v2.4.4源码解析配置文件加载篇一 #2.4版本与2.3版本差异的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot实战源码解析Spring Boot自动配置原理

Spring Boot实战源码解析Spring Boot自动配置原理

SpringBoot 源码解析 ----- Spring Boot的核心能力 - 自动配置源码解析

Spring Boot自动配置源码解析(基于Spring Boot 2.0.2.RELEASE)

Spring Boot Bean 解析

源码解析自动配置的这些细节都不知道,别说你会 springboot