Spring Boot 2.4 配置文件加载流程变更

# 简介

原文发布于: https://spring.io/blog/2020/08/14/config-file-processing-in-spring-boot-2-4 (opens new window)

aliyun 2020 promotion (opens new window)

Spring Boot 2.4.0.M2 刚刚发布 (opens new window), 它对 application.propertiesapplication.yml 文件的加载方式进行了一些有趣的更改.

如果您的应用程序使用仅使用单个 application.propertiesapplication.yml 文件, 那么您可能不会注意到任何区别.

但是, 如果您的应用程序使用更复杂的设置 (例如, 针对特定配置文件的属性), 则可能需要继续阅读以了解我们所做的更改以及更改的原因.

# 为什么我们要进行这些更改

借助最新版本的 Spring Boot, 我们一直在努力改善对 Kubernetes 的支持. 我们想在 Spring Boot 2.3 中实现但没能做到的一件事是对卷挂载 (volume mount) 配置的支持.

卷挂载配置是 Kubernetes 的一项常用功能, 其中的 ConfigMap 指令用于直接在文件系统上显示配置. 您可以载入包含多个键值对的完整 YAML 文件, 也可以使用更简单的目录树格式, 其中文件名是键, 文件的内容是值.

我们想为两者都提供支持, 并且以一种可以与现有的 application.propertiesapplication.yml 一起使用的方式进行实现. 为此, 我们需要修改令人​​恐惧的ConfigFileApplicationListener 类.

# ConfigFileApplicationListener 的问题

几年前, 视频游戏 Trap Adventure 2 (opens new window) 中的 一些集锦 (Youtube) (opens new window) 开始出现. 它们与软件中可能发生的事情非常相似: 有时, 您会发现自己被难以更改的代码包围. 在 Spring Boot 中, ConfigFileApplicationListener 最终成为这些了 “陷阱” 之一.

(译者注: Trap Adventure 2 是一种类似于 I wanna (opens new window) 的跳刺游戏)

它并不是代码编写错误或缺少测试. 只是在我们添加功能时陷入了困境.

现有的代码有两个主要问题与配置文件有关 (主要在 YAML 中). 即:

  1. 您可以从一个配置文件中启用其他配置文件.
  2. 很难知道配置确切的添加顺序.

请看以下示例:

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 属性被设置了么? 你确定? 如果配置文件在被处理时未被激活, 中间部分的逻辑文档也会被包含在内么?

我们经常会遇到此类文件处理逻辑的问题, 但是每当我们尝试修复它时, 总会遇到种种不兼容的情况.

最终我们得出继续前进的唯一方法就是重新设计整个配置文件的加载过程.

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

  1. 文档将按照定义的顺序加载.
  2. 不能再从配置文件的文档中激活其他配置文件.

# 文档顺序

Spring Boot 2.4 开始, 一个简单的规则将被应用在加载 PropertiesYAML 文件的顺序上: 靠近文件靠底部的属性将覆盖靠顶部的属性.

这与过去 Properties 文件使用的排序规则相同. 你可以试想一下将每行当作一个条目 (entry) 放入 Map 中. 当有相同键的新值放入时, 现有的条目将被替换掉.

对于给定的多文档 YAML 文件也同样遵循这些规则, 靠近文件底部的文档将覆盖靠顶部的文档:

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

# 多文档 Properties 文件

借助 Spring Boot 2.4, 我们决定将类似 YAML 的多文档支持引入到 JavaProperties 文件中. 多文档 Properties 文件将使用注释 (#) 后跟着三个破折号来分隔文档 (使用注释是为了使现有的 IDE 能正常工作) .

例如, 上面的 YAML 代码段的等效 Properties 文件内容为:

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

# 特定配置文件的专有属性

上面的示例有点刻意为之, 因为始终覆盖一个属性实际上并没有任何意义. 更为常见的情况是声明一个仅在特定配置下处于激活状态的文档.

Spring Boot 2.3 中, 您可以使用 spring.profiles.active 属性来执行此操作. 到了 Spring Boot 2.4, 我们决定将这个属性更改为 spring.config.activate.on-profile.

例如, 如果我们只想 test 属性在 dev 配置文件处于激活状态时被覆盖, 则可以使用以下命令:

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

# 配置文件激活过程

您仍然可以在 application.propertiesapplication.yaml 文件的中使用 spring.profiles.active 属性来激活或包括其他配置文件.

例如, 以下内容完全符合规范:

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 #此处将导致应用启动失败
test=overridden-value

我们希望这一新限制最终将使您的 application.propertiesapplication.yml 文件更易于推理和理解. 我们还希望它可以使 Spring Boot 本身更易于管理和维护. 目前, 我们知道至少有一个有效的使用场景, 就是人们希望将一个配置文件扩展为多个子配置文件. 为了支持这一点, 我们添加了一个称为 "配置文件组(profile group)" 的功能.

# 配置文件组 (profile group)

配置文件组是 Spring Boot 2.4 中的一项新功能, 可让您将单个配置文件扩展为多个子配置文件.

例如, 假设您有一组复杂的 @Configuration 类, 可以使用 @Profile 注释有条件地启用它们. 您可能有使用 @Profile("proddb") 进行激活的数据库配置, 使用 @Profile("prodmq") 进行激活的消息配置等等.

使用多个离散的配置文件可能使您的代码更易于推理, 但是对于部署而言, 它并不是理想的选择. 你不希望强迫用户记住他们必须同时激活 proddb, prodmq, prodmetrics等配置. 相反, 您希望他们只要激活一个 prod 配置文件就够了. 配置文件组正是为了这一需求而设计的.

要定义组, 您可以在 application.propertiesapplication.yml 中定义 spring.profiles.group 属性.

例如:

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

# 导入其他配置

现在, 我们已经解决了配置文件处理的基本问题, 我们终于可以考虑要提供的新功能. 我们将在 Spring Boot 2.4 中提供的主要功能是对导入其他配置的支持.

对于早期版本的 Spring Boot, 想要在 application.propertiesapplication.yml 生效之前导入额外的 PropertiesYAML 文件是十分困难的. 虽然您可以使用 spring.config.additional-location 属性来达成这个目的, 但是您需要尽早地设置它, 并且它可以处理的文件类型也非常有限.

在最新的里程碑版本中, 您可以直接在 application.propertiesapplication.yml 文件中使用新属性 spring.config.import.

例如, 您可能想导入一个不受版本控制的 developer.properties 配置文件, 以便您团队中的任何开发人员都可以快速更改各种配置项:

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

您甚至可以将 spring.config.import 声明与 spring.config.activate.on-profile 属性结合起来.

例如, 仅在 prod 配置文件处于激活状态时才加载 prod.properties 文件:

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

导入可以被视为在声明它们的文档最底部插入的其他文档. 它们遵循与常规多文档文件相同的自上而下的加载顺序: 无论声明了多少次, 文件仅被导入一次.

# 卷挂载的配置树

导入使用类似 URL 的语法作为其值的定义. 如果您指定的位置 (location) 没有前缀, 那么它将被视为常规文件或文件夹. 但是, 如果使用 configtree: 前缀, 那么 Spring Boot 将希望在该位置找到一个符合 Kubernetes 规范的卷挂载的配置树.

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

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

如果您具有以下被挂载的内容:

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

如果您的 Spring Environmentmy.applicationtest 属性结尾, 则 my.application 的值将是 /etc/config/my/application 文件中的内容, test 的值将是 /etc/config/test 文件中的内容.

# 云平台激活过程

如果只希望在特定的云平台上激活卷挂载的配置树 (或与此有关的任何属性), 则可以使用 spring.config.activate.on-cloud-platform 属性. 这与 spring.config.activate.on-profile 属性相似, 但使用 CloudPlatform 值而不是配置文件名称.

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

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

# 支持额外的位置

spring.config.import 属性中指定的位置字符串是完全可插入的, 并且可以通过编写一些自定义类来扩展. 我们希望第三方库将来可能会为自定义位置提供支持.

例如, 第三方 jar 文件可能会支持来自诸如 archaius://... , vault://...zookeeper://... 的配置文档.

如果您有兴趣添加额外位置的支持, 请查看 org.springframework.boot.context.config 包中 ConfigDataLocationResolver 类和 ConfigDataLoader 类的文档.

# 继续使用旧版处理方式

如果您要升级现有的 Spring Boot 应用程序, 而对所有这些新功能都不满意, 您可以随时切换回旧的处理方式.

为了做到这一点, 你可以在 application.propertiesapplication.yml 文件中设置 spring.config.use-legacy-processing=true. 这样的话您应该能获得与 Spring Boot 2.3 应用程序相同的应用程序配置处理方式.

如果您发现我们错过了特定的使用场景, 则不得不切换到旧版处理方式, 请在 GitHub 上提出一个问题反馈 (opens new window), 我们将尝试解决该问题.

# 总结

我们希望新的配置数据处理类有用, 并且不会引起太多的升级麻烦. 如果您想了解更多有关它们的信息, 可以查阅最新的参考文档 (opens new window).

# 推广

欢迎加入 Spring Cloud 交流群: 617143034 (opens new window)

欢迎大家点击下方的图片领取限量 阿里云优惠券 (opens new window), 新购续费更优惠: 限量阿里云优惠券 (opens new window)