SpringBoot
大行其道,与它的简单易上手息息相关,其中最重要的一个特性就是:配置文件,它会基于我们在指定路径下的配置文件,将对应的属性集整合到 Environment
中,支持通过 @Value
注解或直接从 Environment
读取等
随着 Spring Boot 2.4.x
的发布,其对配置文件的加载机制进行了重做,尽管提供了向下兼容的配置,但老版本的加载机制最终会被移除,因此本文对 2.4.0
版本前后的配置相关进行一个简单的了解与对比
在 2.4.0
之前,SpringBoot
官方是这样定义的:通过 PropertySource
来保证属性的覆盖,属性的顺序如下:
devtools
时 $HOME/.config/spring-boot
路径下的配置test
模块下 @TestPropertySource
注解引入的配置文件test
模块下的 properties
属性SPRING_APPLICATION_JSON
属性ServletConfig
的初始化参数ServletContext
的初始化参数JNDI
属性Java System properties
OS environment variables
random.*
形式的属性,优先从 RandomValuePropertySource
获取(指优先于后者)jar
包外的 application-{profile}.properties/yaml
配置文件jar
包内的 application-{profile}.properties/yaml
配置文件jar
包外的 application.properties/yaml
配置文件jar
包内的 application.properties/yaml
配置文件@PropertySource
注解引入的配置属性SpringApplication.setDefaultProperties
上述顺序中,越靠前的配置,属性优先级越高,举个例子:命令行启动参数添加 --os.name=test
,是会覆盖后续 os.name
属性的,包括系统变量(System.getProperties
) 的 os.name
,同理系统变量的属性也会覆盖后续配置文件里的同名属性
我们可以通过指定 spring.config.name
属性来修改默认的配置文件名,默认即为 application
同时,我们可以指定 spring.config.location
属性来指定配置文件(路径),倘若该配置项以 /
结尾,则对应的 spring.config.name
会被拼接上去,组成完整的配置文件路径(注意!此时配置文件的 profile
会失效,因此不建议这么做)
需要注意的是,该配置项的声明需要在很早期:比如 环境变量,命令行参数等
默认的 spring.config.name
为 application
,默认的 spring.config.location
为 classpath:/,classpath:/config/,file:./,file:./config/*/,file:./config/
,而对配置文件的加载实际上是倒序的,因而,配置文件的加载路径顺序为:
jar
当前路径的 config
文件夹下jar
当前路径的 config
子文件夹下,形如 ./config/my/application.properties
jar
当前路径同级classpath
类路径的 config
文件夹下classpath
类路径下如此,便可以解释所谓的 外部配置优先于内部配置
,同样的,如果我们指定 spring.config.location
为 file:./, classpath:/
,则表现出的就会是 内部配置优先于外部配置
(可以自己动手尝试下)
另外,还可以指定 spring.config.additional-location
属性添加配置文件。当然,这部分配置文件也会倒序后被优先加载,比如说指定 spring.config.additional-location=file:./custom-config/, classpath:/custom-config/
,则最终的配置文件加载顺序为:
classpath
的 custom-config
文件夹下jar
当前路径的 custom-config
文件夹下jar
当前路径的 config
文件夹下jar
当前路径的 config
文件夹子文件夹下,形如 ./config/my/application.properties
jar
当前路径同级classpath
类路径的 config
文件夹下classpath
类路径下可以以形如 application-{profile}.properties
的命名指定配置文件,默认 profile
为 default
,可通过 spring.profiles.active
属性激活对应的 profile
从文首给出的配置顺序可以看到,激活的 profile
是优先于 application.properties
,且无关配置文件位置。换句话说,jar
内部 application-{profile}.properties
的属性也会覆盖 jar
包外 application.properties
的对应属性(这是与 2.4.x
版本最大的不同之一,下文重点讨论)
到了 2.4.0
版本,SpringBoot
官方是这样定义的:通过 PropertySource
来保证属性的覆盖,属性的顺序如下(后者的属性值覆盖前者
):
SpringApplication.setDefaultProperties
@PropertySource
注解引入的配置属性Config data
)random.*
的 RandomValuePropertySource
OS environment variables
Java System properties
JNDI
属性ServletContext
的 初始化参数ServletConfig
的 初始化参数SPRING_APPLICATION_JSON
属性test
模块下的 properties
属性test
模块下 @TestPropertySource
注解引入的配置文件devtools
时 $HOME/.config/spring-boot
路径下的配置对比可见:整体上属性的加载顺序没有改变,其变化主要体现在 配置文件
2.4.0
版本后是以这样的顺序加载配置文件的:
classpath
根路径classpath
根路径的 config
路径下config
文件夹下config
子文件夹下后者的属性覆盖前者。如此一来,实际配置属性的优先级为:
config
子文件夹下配置文件的属性config
文件夹下的配置文件的属性classpath
根路径的 config
路径下配置文件的属性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
版本之前
新的机制,对于上述 spring.config.location
spring.config.additional-location
和之后要介绍的 spring.config.import
等属性的路径,添加 optional:
前缀,则当对应文件不存在时应用仍可以正常启动
比如 spring.config.location=optional:file:/my.yaml
,当应用启动加载文件 my.yaml
不存在时,不会抛出异常
新的机制,这也是一个比较重要的机制,在配置文件中通过 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/
同样的,可以以形如 application-{profile}.properties
的命名指定配置文件,默认 profile
为 default
,可通过 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
能回到之前的解析方式,但治标不治本jar
包外 config
子文件夹下的配置文件属性优先级提为最高,我们之前提到,这更符合 外部化配置
的思想,这样我们就可以更好的整合、梳理配置文件配置文件位置
优先于 profile
,此举主要是把内部配置文件和外部配置文件分离开来,比如在 2.4.0
之前,jar
内部的 application-${profile}.yaml
文件属性是可以覆盖外部 application.yaml
的,这显然不是很合理optional:
前缀的支持,包容了目标配置文件不存在的情况spring.config.import
属性,这应该是本次更新的重头戏,经常使用容器化部署的同学应该可以感受到,这是对容器化部署的大力支持:无论是对 挂载 volume
文件的读取支持,亦或是对诸如 ConfigMap
组件的解析支持profile
模式的配置文件不做深入了解,因为我觉得这种配置方式并不是很友好,看起来显得杂乱无章