【SpringBoot】对比 SpringBoot 2.4.0 版本前后配置文件机制改动

【SpringBoot】对比 SpringBoot 2.4.0 版本前后配置文件机制改动

  • 前言
  • 2.4.0 版本之前
    • 配置文件
    • profile
  • 2.4.x 版本
    • 配置文件
    • optional
    • import
    • profile
  • 总结

前言

SpringBoot 大行其道,与它的简单易上手息息相关,其中最重要的一个特性就是:配置文件,它会基于我们在指定路径下的配置文件,将对应的属性集整合到 Environment 中,支持通过 @Value 注解或直接从 Environment 读取等

随着 Spring Boot 2.4.x 的发布,其对配置文件的加载机制进行了重做,尽管提供了向下兼容的配置,但老版本的加载机制最终会被移除,因此本文对 2.4.0 版本前后的配置相关进行一个简单的了解与对比

2.4.0 版本之前

2.4.0 之前,SpringBoot 官方是这样定义的:通过 PropertySource 来保证属性的覆盖,属性的顺序如下:

  1. 启用 devtools$HOME/.config/spring-boot 路径下的配置
  2. test 模块下 @TestPropertySource 注解引入的配置文件
  3. test 模块下的 properties 属性
  4. 命令行参数
  5. SPRING_APPLICATION_JSON 属性
  6. ServletConfig 的初始化参数
  7. ServletContext 的初始化参数
  8. JNDI 属性
  9. Java System properties
  10. OS environment variables
  11. 对于 random.* 形式的属性,优先从 RandomValuePropertySource 获取(指优先于后者)
  12. jar 包外的 application-{profile}.properties/yaml 配置文件
  13. jar 包内的 application-{profile}.properties/yaml 配置文件
  14. jar 包外的 application.properties/yaml 配置文件
  15. jar 包内的 application.properties/yaml 配置文件
  16. @PropertySource 注解引入的配置属性
  17. 默认属性 SpringApplication.setDefaultProperties

上述顺序中,越靠前的配置,属性优先级越高,举个例子:命令行启动参数添加 --os.name=test,是会覆盖后续 os.name 属性的,包括系统变量(System.getProperties) 的 os.name,同理系统变量的属性也会覆盖后续配置文件里的同名属性

配置文件

我们可以通过指定 spring.config.name 属性来修改默认的配置文件名,默认即为 application

同时,我们可以指定 spring.config.location 属性来指定配置文件(路径),倘若该配置项以 / 结尾,则对应的 spring.config.name 会被拼接上去,组成完整的配置文件路径(注意!此时配置文件的 profile 会失效,因此不建议这么做)

需要注意的是,该配置项的声明需要在很早期:比如 环境变量,命令行参数等

默认的 spring.config.nameapplication,默认的 spring.config.locationclasspath:/,classpath:/config/,file:./,file:./config/*/,file:./config/,而对配置文件的加载实际上是倒序的,因而,配置文件的加载路径顺序为:

  1. jar 当前路径的 config 文件夹下
  2. jar 当前路径的 config 子文件夹下,形如 ./config/my/application.properties
  3. jar 当前路径同级
  4. classpath 类路径的 config 文件夹下
  5. classpath 类路径下

如此,便可以解释所谓的 外部配置优先于内部配置,同样的,如果我们指定 spring.config.locationfile:./, classpath:/,则表现出的就会是 内部配置优先于外部配置(可以自己动手尝试下)

另外,还可以指定 spring.config.additional-location 属性添加配置文件。当然,这部分配置文件也会倒序后被优先加载,比如说指定 spring.config.additional-location=file:./custom-config/, classpath:/custom-config/,则最终的配置文件加载顺序为:

  1. classpathcustom-config 文件夹下
  2. jar 当前路径的 custom-config 文件夹下
  3. jar 当前路径的 config 文件夹下
  4. jar 当前路径的 config 文件夹子文件夹下,形如 ./config/my/application.properties
  5. jar 当前路径同级
  6. classpath 类路径的 config 文件夹下
  7. classpath 类路径下

profile

可以以形如 application-{profile}.properties 的命名指定配置文件,默认 profiledefault,可通过 spring.profiles.active 属性激活对应的 profile

从文首给出的配置顺序可以看到,激活的 profile 是优先于 application.properties,且无关配置文件位置。换句话说,jar 内部 application-{profile}.properties 的属性也会覆盖 jar 包外 application.properties 的对应属性(这是与 2.4.x 版本最大的不同之一,下文重点讨论)

2.4.x 版本

到了 2.4.0 版本,SpringBoot 官方是这样定义的:通过 PropertySource 来保证属性的覆盖,属性的顺序如下(后者的属性值覆盖前者):

  1. 默认属性 SpringApplication.setDefaultProperties
  2. @PropertySource 注解引入的配置属性
  3. 配置文件(Config data
  4. 处理 random.*RandomValuePropertySource
  5. OS environment variables
  6. Java System properties
  7. JNDI 属性
  8. ServletContext 的 初始化参数
  9. ServletConfig 的 初始化参数
  10. SPRING_APPLICATION_JSON 属性
  11. 命令行参数
  12. test 模块下的 properties 属性
  13. test 模块下 @TestPropertySource 注解引入的配置文件
  14. 启用 devtools$HOME/.config/spring-boot 路径下的配置

对比可见:整体上属性的加载顺序没有改变,其变化主要体现在 配置文件

配置文件

2.4.0 版本后是以这样的顺序加载配置文件的:

  1. classpath 根路径
  2. classpath 根路径的 config 路径下
  3. 当前路径
  4. 当前路径 config 文件夹下
  5. 当前路径 config 子文件夹下

后者的属性覆盖前者。如此一来,实际配置属性的优先级为:

  1. 当前路径 config 子文件夹下配置文件的属性
  2. 当前路径 config 文件夹下的配置文件的属性
  3. 当前路径配置文件的属性
  4. classpath 根路径的 config 路径下配置文件的属性
  5. classpath 根路径下配置文件的属性

对比发现, 当前路径 config 子文件夹下配置文件的属性优先级调整为最高

其实与之前相比,这才更加符合 外部化配置 的定义,譬如如下路径:
file:/config/redis/redis.yaml
file:/config/mysql/mysql.yaml
将拥有最高的优先级

我们同样也可以使用 spring.config.name spring.config.location spring.config.additional-location 属性来指定配置文件名以及加载路径,其用法与不变,可参考 2.4.0 版本之前

optional

新的机制,对于上述 spring.config.location spring.config.additional-location 和之后要介绍的 spring.config.import 等属性的路径,添加 optional: 前缀,则当对应文件不存在时应用仍可以正常启动

比如 spring.config.location=optional:file:/my.yaml,当应用启动加载文件 my.yaml 不存在时,不会抛出异常

import

新的机制,这也是一个比较重要的机制,在配置文件中通过 spring.config.import 属性可以引入指定的配置文件,比如 spring.config.import=my.yaml

上述示例中,它会将 my.yaml 文件作为临时文件放在当前配置文件之后处理,因此其属性具有更高的优先级,比如:

name: default
spring:
	config:
		import: my.yaml

my.yaml 文件中的属性 name 会覆盖此文件中的值: default

补充如下细节:

  • 无论声明多少次,同一个文件只会被 import 一次
  • import 文件的优先级与其声明的位置无关,即无论如何,它内部的属性优先级都是高于当前文件的
  • 如果 import 多个文件,则之后文件的属性优先级更高

容器化部署时,挂载 volume 的文件通常没有后缀,对于这种文件,我们可以指定其解析格式诸如 spring.config.import: file:/etc/config/my[.yaml]:就是表示读取挂载文件 /etc/config/my 并将其视作 yaml 文件解析

再比如,对于类似 k8s 中的 ConfigMap 组件,也是支持 import 的,格式诸如 spring.config.import: configtree:/etc/config/

profile

同样的,可以以形如 application-{profile}.properties 的命名指定配置文件,默认 profiledefault,可通过 spring.profiles.active 属性激活对应的 profile

2.4.0 版本之前,profile 的优先级是大于 位置 的,如何理解这句话:比如 jar 包内 application-${profile}.yaml 的属性优先级是大于 jar 包外 application.yaml 同名属性的

但在 2.4.x 版本后,位置 的优先级将大于 profile,比如属性优先级依次如下:

  • jar 包外 application-${profile}.yaml 属性
  • jar 包外 application.yaml 属性
  • jar 包内 application-${profile}.yaml 属性
  • jar 包内 application.yaml 属性

对比之前,这样的优先级划分其实才是更合理的,也是容器时代下需要支持的 外部化配置独立于内部的配置

补充如下细节:
2.4.0 版本后支持 profile group 概念,即将 profile 分组,同样可以以组为单位激活,如下示例:

spring:
	profiles:
		group:
			dev:
			- dev1
			- dev2

spring:
	profiles:
		active: dev

上述配置可以通过 spring.profiles.active=dev 激活一组 profile

总结

  • SpringBoot 基于事件驱动来完成配置文件的读取解析相关,其对应的核心事件为 ApplicationEnvironmentPreparedEvent,本次更新也是完全重写了配置文件对应的监听器:在 2.4.0 之前负责监听该事件的 ConfigFileApplicationListener 同时作为 EnvironmentPostProcessor 来负责配置文件解析。而在 2.4.0 之后,ConfigFileApplicationListener 被标记为 @Deprecated,同时EnvironmentPostProcessorApplicationListener 作为新的默认监听器,调用 ConfigDataEnvironmentPostProcessor 来处理配置文件,通过属性 spring.config.use-legacy-processing=true 能回到之前的解析方式,但治标不治本
  • 对比配置文件机制主要改动有:
  • 1)jar 包外 config 子文件夹下的配置文件属性优先级提为最高,我们之前提到,这更符合 外部化配置 的思想,这样我们就可以更好的整合、梳理配置文件
  • 2)配置文件位置 优先于 profile,此举主要是把内部配置文件和外部配置文件分离开来,比如在 2.4.0 之前,jar 内部的 application-${profile}.yaml 文件属性是可以覆盖外部 application.yaml 的,这显然不是很合理
  • 3)optional: 前缀的支持,包容了目标配置文件不存在的情况
  • 4)spring.config.import 属性,这应该是本次更新的重头戏,经常使用容器化部署的同学应该可以感受到,这是对容器化部署的大力支持:无论是对 挂载 volume 文件的读取支持,亦或是对诸如 ConfigMap 组件的解析支持
  • 5)对于单文件多 profile 模式的配置文件不做深入了解,因为我觉得这种配置方式并不是很友好,看起来显得杂乱无章

你可能感兴趣的:(SpringBoot,spring,boot)