提升技能
VMware 提供培训和认证,助你加速进步。
了解更多Spring Boot 2.4.0.M2 刚刚发布,它在加载 application.properties
和 application.yml
文件的方式上带来了一些有趣的改变。
如果你的应用程序只使用一个典型的 application.properties
或 application.yml
文件,那么你可能不会注意到任何差异。但是,如果你的应用程序使用了更复杂的配置(例如特定 profile 的属性),那么你可能想继续阅读,了解我们更改了什么以及原因。
在最近的 Spring Boot 版本中,我们一直在努力改进对 Kubernetes 的支持。我们在 Spring Boot 2.3 中想添加但未能实现的一项功能是对卷挂载配置的支持。
卷挂载配置是 Kubernetes 的一个流行功能,其中使用 ConfigMap
指令将配置直接呈现在文件系统上。你可以挂载一个包含多个键值组合的完整 YAML 文件,或者使用更简单的目录树格式,其中文件名是键,文件内容是值。
我们希望为这两种方式都提供支持,并且以一种与我们现有的 application.properties
和 application.yml
支持自然结合的方式实现。为此,我们需要修改那个令人头疼的 ConfigFileApplicationListener
类。
几年前,一些有趣的 视频游戏《陷阱冒险 2》(Trap Adventure 2) 的片段开始流传开来。它们很好地类比了软件中可能发生的情况。有时你会发现自己陷入一些代码区域,这些区域非常难以更改。在 Spring Boot 中,ConfigFileApplicationListener
最终成为了这样的“陷阱冒险”之一。
并不是说代码写得不好,或者缺少测试。只是随着我们不断添加功能,我们最终让自己陷入了困境。
我们在这段代码中遇到的两个主要问题与特定 profile 的文档(主要是 YAML 文件)有关。具体来说:
你可以从特定 profile 的文档中启用额外的 profile。
很难知道文档将被添加的顺序。
看下面的例子:
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 文件的加载方式进行两项重大更改:
文档将按照它们的定义顺序加载。
不能再从特定 profile 的文档中激活 profile。
从 Spring Boot 2.4 开始,加载属性文件和 YAML 文件时将遵循一个简单的规则:文件中声明靠后的属性将覆盖靠前的属性。
这遵循了传统的 .properties
文件已经使用的排序规则。可以想象每一行都在 Map
中放入一个条目。当具有相同键的新值放入 Map
时,任何现有条目都会被替换。
按照这些规则,对于一个多文档 YAML 文件,后面的文档将覆盖前面文档中的值:
test: "overridden-value"
在 Spring Boot 2.4 中,我们决定为 Java properties 文件引入类似 YAML 的多文档支持。多文档 properties 文件使用注释符号(#
),后跟熟悉的三连字符(---
)来分隔文档(我们选择使用注释符号是为了不破坏现有的 IDE 工具)。
例如,上面 YAML 片段的 properties 等效形式是:
test=value #--- test=overridden-value
上面的例子有点人为,因为它总是覆盖一个值并没有实际意义。更常见的设置是声明第二个文档仅在特定 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
你仍然可以使用 spring.profiles.active
属性从 application.properties
或 application.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.properties
和 application.yml
文件更容易理解和推理。我们也希望这能让 Spring Boot 本身更容易管理和维护。然而,我们意识到至少存在一个有效的用例,即人们希望将一个 profile 扩展为多个子 profile。为了支持这种情况,我们正在添加一个名为“profile groups”的功能。
Profile groups 是 Spring Boot 2.4 中的一个新功能,它允许你将一个 profile 扩展为多个子 profile。例如,假设你有一组复杂的 @Configuration
类,你可以使用 @Profile
注解有条件地启用它们。你可能有一个带有 @Profile("proddb")
的数据库配置,一个带有 @Profile("prodmq")
的消息配置等等。
使用多个离散的 profile 可能会让你的代码更容易理解,但这对于部署来说并不理想。你不想强迫用户记住他们必须同时激活 proddb
、prodmq
、prodmetrics
等等。相反,你只希望他们能够激活一个单独的 prod
profile。Profile groups 正好可以让你实现这一点。
要定义一个 group,你可以在 application.properties
或 application.yml
文件中使用 spring.profiles.group
属性。例如:
spring.profiles.group.prod=proddb,prodmq,prodmetrics
既然我们已经修复了配置文件处理的基本问题,我们终于可以考虑提供新的功能了。我们在 Spring Boot 2.4 中提供的主要功能是支持导入额外的配置。
在 Spring Boot 的早期版本中,导入 application.properties
和 application.yml
之外的额外属性文件或 yaml 文件相当困难。你可以使用 spring.config.additional-location
属性,但你需要在很早的阶段设置它,并且它能处理的文件类型也相当有限。
使用最新的里程碑版本,你可以直接在 application.properties
或 application.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.application
和 test
属性。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
在 spring.config.import
属性中指定的 location 字符串是完全可插拔的,并且可以通过编写一些自定义类进行扩展。我们期望第三方库将来会开始提供对自定义 location 的支持。例如,你可以想象第三方 jar 包支持诸如 archaius://…
、vault://…
或 zookeeper://…
等 location。
如果你对添加额外的 location 支持感兴趣,请查阅 org.springframework.boot.context.config
包中 ConfigDataLocationResolver
和 ConfigDataLoader
的 javadoc。
如果你正在升级现有的 Spring Boot 应用程序,并且对使用所有这些新功能感到不适应,你可以切换回旧的处理程序。为此,你可以在 application.properties
或 application.yml
文件中将 spring.config.use-legacy-processing
设置为 true
。这将使你的应用程序配置处理与 Spring Boot 2.3 应用程序完全相同。
如果你确实发现需要切换到遗留处理方式,因为我们遗漏了某个特定的用例,请在 GitHub 上提交一个问题,我们将尽力解决。
我们希望新的配置数据处理类有用,并且不会带来太多的升级痛苦。如果你想了解更多信息,可以查阅更新后的参考文档。