bigywater.tech
Spring · 70

spring.config.import 与配置文件加载顺序

配置写在好几个文件里,谁盖住谁?spring.config.location 和 additional-location 差在哪?import 进来的文件比原文件高还是低?profile 专属文件一定最高吗?这篇在 Spring Boot 4.0.8 上逐个场景实测,并读了源码里的默认位置定义。

imported 赢 main
被 spring.config.import 导入的文件覆盖声明它的文件,没覆盖的 key 照旧
第 4 位 / 第 1 位
同一个 dev 专属文件:放在 classpath 里排第 4 位,放在 file:./ 就排第 1 位
3 / 5
configtree 文件内容 "abc\n" 读出长度 3(换行被去掉),"abc\n\n" 长度 5(不去掉)

location 与 additional-location

实测 · 本机执行输出
  不设置(默认位置):
    app.name       = [cwd-config]       ←| 'file:[config/application.properties]'
    only.classpath = [yes]              ←| 'classpath:[application.properties]'
    only.cwd       = [yes]              ←| 'file:[config/application.properties]'
  -Dspring.config.location=file:./custom/(替换默认位置):
    app.name       = [custom]           ←| 'file:[custom/application.properties]'
    only.classpath = null               ←| -
    only.cwd       = null               ←| -
  -Dspring.config.additional-location=file:./custom/(追加在默认位置之上):
    app.name       = [custom]           ←| 'file:[custom/application.properties]'
    only.classpath = [yes]              ←| 'classpath:[application.properties]'
    only.cwd       = [yes]              ←| 'file:[config/application.properties]'
  -Dspring.config.name=other(换文件名,默认位置不变):
    app.name       = [cwd-config-other] ←| 'file:[config/other.properties]'
    only.classpath = null               ←| -
    only.cwd       = null               ←| -
  -Dspring.config.location=file:./custom/other.properties(直接指定文件):
    app.name       = [other]            ←| 'file:[custom/other.properties]'
    only.classpath = null               ←| -
    only.cwd       = null               ←| -
  -Dspring.config.location=file:./custom(目录末尾没有斜杠):
    启动失败(没有 FailureAnalyzer 报告,堆栈里的异常):Caused by: java.lang.IllegalStateException: File extension is not known to any PropertySourceLoader. If the location is meant to reference a directory, it must end in '/' or File.separator
  -Dspring.config.location=file:./nope/(目录不存在,没写 optional:):
    启动失败:Description: Config data location 'file:./nope/' does not exist Action: Check that the value 'file:./nope/' is correct, or prefix it with 'optional:'
  -Dspring.config.location=optional:file:./nope/:
    app.name       = null               ←| -
    only.classpath = null               ←| -
    only.cwd       = null               ←| -
  -Dspring.config.location=classpath:/,file:./custom/(多个位置用逗号,后面的优先):
    app.name       = [custom]           ←| 'file:[custom/application.properties]'
    only.classpath = [yes]              ←| 'classpath:[application.properties]'
    only.cwd       = null               ←| -
  -Dspring.config.location=file:./custom/,classpath:/(顺序反过来):
    app.name       = [classpath]        ←| 'classpath:[application.properties]'
    only.classpath = [yes]              ←| 'classpath:[application.properties]'
    only.cwd       = null               ←| -

location 替换默认位置,additional-location 追加在默认位置之上;目录必须以斜杠结尾,不存在的位置要加 optional:。

spring.config.import

实测 · 本机执行输出
  application.properties 自己写 app.name=main 并 import 一个也写了 app.name 的文件:
    app.name       = [imported]         ←| 'file:[imp.properties]'
    only.imp       = [1]                ←| 'file:[imp.properties]'
    x              = [from-main]        ←| 'file:[application.properties]'
  同一行 import 两个文件(imp.properties,imp2.properties):
    app.name       = [imported2]        ←| 'file:[imp2.properties]'
    only.imp       = [1]                ←| 'file:[imp.properties]'
    x              = null               ←| -
  顺序反过来(imp2.properties,imp.properties):
    app.name       = [imported]         ←| 'file:[imp.properties]'
    only.imp       = [1]                ←| 'file:[imp.properties]'
    x              = null               ←| -
  被导入的文件再 import 别的文件(链式):
    app.name       = [b]                ←| 'file:[b.properties]'
    only.imp       = null               ←| -
    x              = [from-b]           ←| 'file:[b.properties]'
  import 的文件不存在,没写 optional:
    启动失败:Description: Config data resource 'file [missing.properties]' via location 'file:./missing.properties' does not exist Action: Check that the value 'file:./missing.properties' at URL [file:application.properties] - 1:22 is correct, or prefix it with 'optional:'
  import 的文件不存在,写了 optional:
    app.name       = [main]             ←| 'file:[application.properties]'
    only.imp       = null               ←| -
    x              = null               ←| -
  import 的位置写在命令行 --spring.config.import:
    app.name       = [imported]         ←| 'file:[imp.properties]'
    only.imp       = [1]                ←| 'file:[imp.properties]'
    x              = null               ←| -
  命令行又写 --app.name=cmdline,文件里 import 的 app.name 还赢吗:
    app.name       = [cmdline]          ←| commandLineArgs
    only.imp       = [1]                ←| 'file:[imp.properties]'
    x              = null               ←| -
  import 的文件放在 jar 里(classpath:):
    app.name       = [cp-imp]           ←| 'classpath:[imp.properties]'
    only.imp       = null               ←| -
    x              = null               ←| -

被导入的文件覆盖声明它的文件,多个 import 后面的赢,命令行属性仍然最高;缺失的文件要加 optional: 才不报错。

无扩展名文件与 configtree

实测 · 本机执行输出
  import 一个没有扩展名的文件,不指定格式:
    启动失败(没有 FailureAnalyzer 报告,堆栈里的异常):Caused by: java.lang.IllegalStateException: File extension is not known to any PropertySourceLoader. If the location is meant to reference a directory, it must end in '/' or File.separator
  加上 [.properties] 指定格式:
    db.password    = [s3cret]           ←| 'file:[secret]'
  加上 [.yaml] 指定格式:
    db.password    = [yaml-secret]      ←| 'file:[secret]'
  configtree:./tree/(目录里每个文件名是 key,文件内容是 value):
    db.password    = [pw-with-newline]  ←| Config tree '<jtmp>/x70_wd/./tree'
    db.user        = [root]             ←| Config tree '<jtmp>/x70_wd/./tree'
    app.name       = [from-tree]        ←| Config tree '<jtmp>/x70_wd/./tree'
  文件内容末尾的换行、空格有没有被保留(只看长度;pw1="abc\n"、pw2="abc\n\n"、pw3=" abc "、pw4="abc"):
    pw1            = [abc] 长度 3         ←| Config tree '<jtmp>/x70_wd/./tree'
    pw2            = [abc\n\n] 长度 5     ←| Config tree '<jtmp>/x70_wd/./tree'
    pw3            = [ abc ] 长度 5       ←| Config tree '<jtmp>/x70_wd/./tree'
    pw4            = [abc] 长度 3         ←| Config tree '<jtmp>/x70_wd/./tree'
  configtree 的目录不存在,没写 optional:
    启动失败:Description: Config data resource 'config tree [<jtmp>/x70_wd/./nope]' via location 'configtree:./nope/' does not exist Action: Check that the value 'configtree:./nope/' at URL [file:application.properties] - 1:22 is correct, or prefix it with 'optional:'

无扩展名要用 [.properties] 或 [.yaml] 指定格式;configtree 把文件名当 key、文件内容当 value,只有单个换行才会被去掉。

多文档与 profile

实测 · 本机执行输出
  一个 application.properties 里用 #--- 分成 4 个文档,profile (不激活):
    app.name       = [base]             ←| 'file:[application.properties]'
    x              = [base]             ←| 'file:[application.properties]'
    y              = [last]             ←| 'file:[application.properties]'
  一个 application.properties 里用 #--- 分成 4 个文档,profile dev:
    app.name       = [dev]              ←| 'file:[application.properties]'
    x              = [base]             ←| 'file:[application.properties]'
    y              = [last]             ←| 'file:[application.properties]'
  一个 application.properties 里用 #--- 分成 4 个文档,profile prod:
    app.name       = [prod]             ←| 'file:[application.properties]'
    x              = [prod]             ←| 'file:[application.properties]'
    y              = [last]             ←| 'file:[application.properties]'
  profile 专属的文档写在前面、普通文档写在后面,激活 dev:
    app.name       = [plain-after]      ←| 'file:[application.properties]'
  同样的写法用 application.yaml(--- 分文档),激活 dev:
    app.name       = [yaml-dev]         ←| 'file:[application.yaml]'
  application.properties 和 application.yaml 同时存在,都写 app.name:
    app.name       = [props]            ←| 'file:[application.properties]'
  第二个文档 on-profile=dev 里写了 spring.profiles.active=prod,不激活 dev:
    app.name       = [base]             ←| 'file:[application.properties]'
    active profiles = [prod]
  同上,--spring.profiles.active=dev:
    app.name       = [base]             ←| 'file:[application.properties]'
    active profiles = [dev]
  application-dev.properties 里写 spring.profiles.active=prod,--spring.profiles.active=dev:
    启动失败(没有 FailureAnalyzer 报告,堆栈里的异常):org.springframework.boot.context.config.InvalidConfigDataPropertyException: Property 'spring.profiles.active' imported from location 'file [application-dev.properties]' is invalid in a profile specific resource [origin: URL [file:application-dev.properties] - 1:24]
  on-profile=dev 的文档里写 spring.config.import,激活 dev:
    app.name       = [imported-by-dev]  ←| 'file:[imp.properties]'
  同上,不激活 dev(import 不会发生):
    app.name       = [base]             ←| 'file:[application.properties]'

同一个文件里文档顺序决定覆盖关系;profile 专属文件里不能写 spring.profiles.active;on-profile 文档里的 import 只在该 profile 激活时才发生。

最终顺序

实测 · 本机执行输出
  不激活 profile,Environment 里的配置来源(第 1 个优先级最高):
    app.name       = [sub2]             ←| 'file:[config/sub2/application.properties]'
     1. 'file:[config/sub2/application.properties]'
     2. 'file:[config/sub1/application.properties]'
     3. 'file:[config/application.properties]'
     4. 'file:[imp.properties]'
     5. 'file:[application.properties]'
     6. 'classpath:[config/application.properties]'
     7. 'classpath:[application.properties]'
  激活 dev(多了 application-dev.properties):
    app.name       = [cwd-dev]          ←| 'file:[application-dev.properties]'
     1. 'file:[application-dev.properties]'
     2. 'file:[config/sub2/application.properties]'
     3. 'file:[config/sub1/application.properties]'
     4. 'file:[config/application.properties]'
     5. 'file:[imp.properties]'
     6. 'file:[application.properties]'
     7. 'classpath:[config/application.properties]'
     8. 'classpath:[application.properties]'
  只在 application.properties 里 import,同时有 application-dev.properties,激活 dev:
    app.name       = [cwd-dev]          ←| 'file:[application-dev.properties]'
  -- 同样的 profile 专属文件(dev),只放在下面这一个位置;其余位置只有不带 profile 的 application.properties(classpath:/、classpath:/config/、file:./、file:./config/、file:./config/sub1/ 各一个)
  dev 文件只放在 classpath:/:
    app.name       = [sub1]             ←| 'file:[config/sub1/application.properties]'
    dev 文件排第 4 位(共 6 个),比它优先的:file:config/sub1/application.properties、file:config/application.properties、file:application.properties;比它低的:classpath:config/application.properties、classpath:application.properties
  dev 文件只放在 classpath:/config/:
    app.name       = [sub1]             ←| 'file:[config/sub1/application.properties]'
    dev 文件排第 4 位(共 6 个),比它优先的:file:config/sub1/application.properties、file:config/application.properties、file:application.properties;比它低的:classpath:config/application.properties、classpath:application.properties
  dev 文件只放在 file:./:
    app.name       = [DEV]              ←| 'file:[application-dev.properties]'
    dev 文件排第 1 位(共 6 个),比它优先的:(没有);比它低的:file:config/sub1/application.properties、file:config/application.properties、file:application.properties、classpath:config/application.properties、classpath:application.properties
  dev 文件只放在 file:./config/:
    app.name       = [DEV]              ←| 'file:[config/application-dev.properties]'
    dev 文件排第 1 位(共 6 个),比它优先的:(没有);比它低的:file:config/sub1/application.properties、file:config/application.properties、file:application.properties、classpath:config/application.properties、classpath:application.properties
  dev 文件只放在 file:./config/sub1/:
    app.name       = [DEV]              ←| 'file:[config/sub1/application-dev.properties]'
    dev 文件排第 1 位(共 6 个),比它优先的:(没有);比它低的:file:config/sub1/application.properties、file:config/application.properties、file:application.properties、classpath:config/application.properties、classpath:application.properties

默认位置分 classpath 和 file 两组,组内 profile 专属文件整体更高,file 整组高于 classpath 整组。

分步演示

配置加载顺序
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK 21.0.12(Homebrew) / Spring Boot 4.0.8(Spring Framework 7.0.9、Tomcat 11.0.24) / Spring Cloud 2025.1.3;用 javac 编译、java 直接运行,依赖由 Maven 从 Maven Central 解析;数字每次运行会有小幅抖动。用 Spring Boot 4.0.8(spring-boot-starter,非 Web 模式)的单个程序,每个场景一个独立 JVM;工作目录是临时目录,classpath 位置用一个放在 classpath 最前面的目录代替(不是真正的 jar);来源名取自 Environment 的 PropertySources;默认位置分两组、configtree 的换行规则,另外读了 spring-boot-4.0.8-sources.jar 里的 ConfigDataEnvironment 与 ConfigTreePropertySource 源码
    • 没有验证:真正的可执行 jar 里的 BOOT-INF/classes;Kubernetes 实际挂载的 ConfigMap / Secret(挂载目录里是符号链接,这里只用普通目录);spring.config.import 的其他前缀(optional:configserver:、vault: 等属于 Spring Cloud,见 Spring Cloud 部分);config/*/ 之下更深的目录和更多子目录的排序规则(只测了 sub1、sub2 两个);spring.profiles.include 与 profile 组(见第 72 篇);on-profile 文档里写了 spring.profiles.active 时 active profiles 为什么变成 [prod],我只观察到结果,没有追到原因;Windows 路径
    京ICP备2026067502号-1京公网安备11010502063487号