Spring Boot 2.4 中的配置文件处理

工程 | Phil Webb | 2020 年 8 月 14 日 | ...

Spring Boot 2.4.0.M2 刚刚发布,它在加载 application.propertiesapplication.yml 文件的方式上带来了一些有趣的改变。

如果你的应用程序只使用一个典型的 application.propertiesapplication.yml 文件,那么你可能不会注意到任何差异。但是,如果你的应用程序使用了更复杂的配置(例如特定 profile 的属性),那么你可能想继续阅读,了解我们更改了什么以及原因。

我们为什么要做出这些更改

在最近的 Spring Boot 版本中,我们一直在努力改进对 Kubernetes 的支持。我们在 Spring Boot 2.3 中想添加但未能实现的一项功能是对卷挂载配置的支持。

卷挂载配置是 Kubernetes 的一个流行功能,其中使用 ConfigMap 指令将配置直接呈现在文件系统上。你可以挂载一个包含多个键值组合的完整 YAML 文件,或者使用更简单的目录树格式,其中文件名是键,文件内容是值。

我们希望为这两种方式都提供支持,并且以一种与我们现有的 application.propertiesapplication.yml 支持自然结合的方式实现。为此,我们需要修改那个令人头疼的 ConfigFileApplicationListener 类。

ConfigFileApplicationListener 的问题

几年前,一些有趣的 视频游戏《陷阱冒险 2》(Trap Adventure 2) 的片段开始流传开来。它们很好地类比了软件中可能发生的情况。有时你会发现自己陷入一些代码区域,这些区域非常难以更改。在 Spring Boot 中,ConfigFileApplicationListener 最终成为了这样的“陷阱冒险”之一。

并不是说代码写得不好,或者缺少测试。只是随着我们不断添加功能,我们最终让自己陷入了困境。

我们在这段代码中遇到的两个主要问题与特定 profile 的文档(主要是 YAML 文件)有关。具体来说:

  • 你可以从特定 profile 的文档中启用额外的 profile。

  • 很难知道文档将被添加的顺序。

看下面的例子:

security.user.password: usera

spring.profiles: local security.user.password: userb runlocal: true

spring.profiles: !dev spring.profiles.include: local security.user.password: userc

这里我们有一个 多文档 YAML 文件(一个由三个逻辑文档组成、由 --- 分隔的单个文件)。

如果你使用 --spring.profiles.active=prod 运行,那么 security.user.password 的值是什么?runlocal 属性是否设置了?你确定吗?考虑到在处理时 profile 并未激活,中间的文档是否甚至会被包含在内?

我们经常收到关于这种文件处理逻辑的问题报告,但每当我们尝试修复它们时,就会导致其他地方出问题。我们最终决定唯一的出路是彻底重新思考整个机制。

因此,在 Spring Boot 2.4 中,我们计划对属性文件和 YAML 文件的加载方式进行两项重大更改:

  1. 文档将按照它们的定义顺序加载。

  2. 不能再从特定 profile 的文档中激活 profile。

文档顺序

从 Spring Boot 2.4 开始,加载属性文件和 YAML 文件时将遵循一个简单的规则:文件中声明靠后的属性将覆盖靠前的属性。

这遵循了传统的 .properties 文件已经使用的排序规则。可以想象每一行都在 Map 中放入一个条目。当具有相同键的新值放入 Map 时,任何现有条目都会被替换。

按照这些规则,对于一个多文档 YAML 文件,后面的文档将覆盖前面文档中的值:

test: "value"

test: "overridden-value"

多文档 Properties 文件

在 Spring Boot 2.4 中,我们决定为 Java properties 文件引入类似 YAML 的多文档支持。多文档 properties 文件使用注释符号(#),后跟熟悉的三连字符(---)来分隔文档(我们选择使用注释符号是为了不破坏现有的 IDE 工具)。

例如,上面 YAML 片段的 properties 等效形式是:

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

特定 Profile 的文档

上面的例子有点人为,因为它总是覆盖一个值并没有实际意义。更常见的设置是声明第二个文档仅在特定 profile 激活时才生效。

在 Spring Boot 2.3 中,你会使用 spring.profiles 键来实现这一点。在 Spring Boot 2.4 中,我们决定将属性更改为 spring.config.activate.on-profile

例如,如果只想在 dev profile 激活时覆盖 test,可以使用以下方式:

test=value #--- spring.config.activate.on-profile=dev test=overridden-value

Profile 激活

你仍然可以使用 spring.profiles.active 属性从 application.propertiesapplication.yaml 文件中激活或包含 profile。

例如,以下方式是完全有效的:

test=value spring.profiles.active=local #--- spring.config.activate.on-profile=dev test=overridden value

你不再被允许做的一件事是将该属性与 spring.config.activate.on-profile 结合使用。例如,以下文件现在会抛出异常:

test=value #--- spring.config.activate.on-profile=dev spring.profiles.active=local # will fail test=overridden value

我们希望这个新的限制最终能让你的 application.propertiesapplication.yml 文件更容易理解和推理。我们也希望这能让 Spring Boot 本身更容易管理和维护。然而,我们意识到至少存在一个有效的用例,即人们希望将一个 profile 扩展为多个子 profile。为了支持这种情况,我们正在添加一个名为“profile groups”的功能。

Profile Groups

Profile groups 是 Spring Boot 2.4 中的一个新功能,它允许你将一个 profile 扩展为多个子 profile。例如,假设你有一组复杂的 @Configuration 类,你可以使用 @Profile 注解有条件地启用它们。你可能有一个带有 @Profile("proddb") 的数据库配置,一个带有 @Profile("prodmq") 的消息配置等等。

使用多个离散的 profile 可能会让你的代码更容易理解,但这对于部署来说并不理想。你不想强迫用户记住他们必须同时激活 proddbprodmqprodmetrics 等等。相反,你只希望他们能够激活一个单独的 prod profile。Profile groups 正好可以让你实现这一点。

要定义一个 group,你可以在 application.propertiesapplication.yml 文件中使用 spring.profiles.group 属性。例如:

spring.profiles.group.prod=proddb,prodmq,prodmetrics

导入额外的配置

既然我们已经修复了配置文件处理的基本问题,我们终于可以考虑提供新的功能了。我们在 Spring Boot 2.4 中提供的主要功能是支持导入额外的配置。

在 Spring Boot 的早期版本中,导入 application.propertiesapplication.yml 之外的额外属性文件或 yaml 文件相当困难。你可以使用 spring.config.additional-location 属性,但你需要在很早的阶段设置它,并且它能处理的文件类型也相当有限。

使用最新的里程碑版本,你可以直接在 application.propertiesapplication.yml 文件中使用新的 spring.config.import 属性。例如,你可能想导入一个“被 git 忽略”的 developer.properties 文件,以便团队中的任何开发人员都可以快速更改仅供他们使用的属性:

application.name=myapp spring.config.import=developer.properties

你甚至可以将 spring.config.import 声明与 spring.config.activate.on-profile 属性结合使用。例如,这里我们只在 prod profile 激活时加载 prod.properties 文件:

spring.config.activate.on-profile=prod spring.config.import=prod.properties

导入可以被视为插入在声明它们的文档正下方的新增文档。它们遵循与常规多文档文件相同的自上而下的排序:一个导入只会被导入一次,无论它被声明多少次。

卷挂载配置树

导入定义使用类似 URL 的语法作为其值。如果你的位置没有前缀,它将被视为常规文件或文件夹。但是,如果你使用 configtree: 前缀,则表示你期望在该位置存在一个 Kubernetes 风格的卷挂载配置树。

例如,你可以在 application.properties 中声明以下内容:

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

如果你有以下挂载内容:

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

你最终会在 Spring Environment 中得到 my.applicationtest 属性。my.application 的值将是 /etc/config/my/application 的内容,而 test 的值将是 /etc/config/test 的内容。

云平台激活

如果你只想让卷挂载的配置树(或者任何属性)在特定的云平台上激活,你可以使用 spring.config.activate.on-cloud-platform 属性。它的工作方式类似于 spring.config.activate.on-profile 属性,但使用的是 CloudPlatform 枚举值,而不是 profile 名称。

如果我们只想在部署到 Kubernetes 时启用上面的 configtree 示例,我们可以执行以下操作:

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

支持额外的 Location

spring.config.import 属性中指定的 location 字符串是完全可插拔的,并且可以通过编写一些自定义类进行扩展。我们期望第三方库将来会开始提供对自定义 location 的支持。例如,你可以想象第三方 jar 包支持诸如 archaius://…​vault://…​zookeeper://…​ 等 location。

如果你对添加额外的 location 支持感兴趣,请查阅 org.springframework.boot.context.config 包中 ConfigDataLocationResolverConfigDataLoader 的 javadoc。

使用遗留处理方式

如果你正在升级现有的 Spring Boot 应用程序,并且对使用所有这些新功能感到不适应,你可以切换回旧的处理程序。为此,你可以在 application.propertiesapplication.yml 文件中将 spring.config.use-legacy-processing 设置为 true。这将使你的应用程序配置处理与 Spring Boot 2.3 应用程序完全相同。

如果你确实发现需要切换到遗留处理方式,因为我们遗漏了某个特定的用例,请在 GitHub 上提交一个问题,我们将尽力解决。

总结

我们希望新的配置数据处理类有用,并且不会带来太多的升级痛苦。如果你想了解更多信息,可以查阅更新后的参考文档

订阅 Spring 邮件列表

通过 Spring 邮件列表保持联系

订阅

提升技能

VMware 提供培训和认证,助你加速进步。

了解更多

获取支持

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件,只需一个简单的订阅即可获得。

了解更多

即将到来的活动

查看 Spring 社区中所有即将到来的活动。

查看全部