外部化配置

Spring Boot 允许你将配置外部化,以便在不同环境中使用相同的应用程序代码。你可以使用多种外部配置源,包括 Java properties 文件、YAML 文件、环境变量和命令行参数。

属性值可以通过使用 @Value 注解直接注入到你的 Bean 中,通过 Spring 的 Environment 抽象访问,或者通过 @ConfigurationProperties 绑定到结构化对象

Spring Boot 使用了一种非常特定的 PropertySource 顺序,旨在允许合理地覆盖值。后加载的属性源可以覆盖先加载的属性源中定义的值。属性源按以下顺序考虑:

  1. 默认属性(通过设置 SpringApplication.setDefaultProperties(Map) 指定)。

  2. 你的 @Configuration 类上的 @PropertySource 注解。请注意,此类属性源直到应用程序上下文刷新时才会添加到 Environment 中。这对于配置某些属性(例如 logging.*spring.main.*,它们在刷新开始之前就被读取)来说太晚了。

  3. 配置数据(例如 application.properties 文件)。

  4. 一个 RandomValuePropertySource,其属性仅包含在 random.* 中。

  5. 操作系统环境变量。

  6. Java 系统属性(System.getProperties())。

  7. 来自 java:comp/env 的 JNDI 属性。

  8. ServletContext 初始化参数。

  9. ServletConfig 初始化参数。

  10. 来自 SPRING_APPLICATION_JSON 的属性(嵌入在环境变量或系统属性中的内联 JSON)。

  11. 命令行参数。

  12. 测试中的 properties 属性。在 @SpringBootTest用于测试应用程序特定部分的测试注解上可用。

  13. 你的测试中的 @DynamicPropertySource 注解。

  14. 你的测试中的 @TestPropertySource 注解。

  15. 当 devtools 处于活动状态时,位于 $HOME/.config/spring-boot 目录中的Devtools 全局设置属性

配置数据文件按以下顺序考虑:

  1. 打包在你的 jar 内部的应用程序属性application.properties 及其 YAML 变体)。

  2. 打包在你的 jar 内部的特定 Profile 的应用程序属性application-{profile}.properties 及其 YAML 变体)。

  3. 打包在你的 jar 外部的应用程序属性application.properties 及其 YAML 变体)。

  4. 打包在你的 jar 外部的特定 Profile 的应用程序属性application-{profile}.properties 及其 YAML 变体)。

建议你的整个应用程序只使用一种格式。如果同一位置同时存在 .properties 和 YAML 格式的配置文件,则 .properties 优先。
如果使用环境变量而非系统属性,大多数操作系统不允许使用句点分隔的键名,但你可以使用下划线代替(例如,使用 SPRING_CONFIG_NAME 而非 spring.config.name)。详情请参阅从环境变量绑定
如果你的应用程序运行在 Servlet 容器或应用服务器中,则可以使用 JNDI 属性(在 java:comp/env 中)或 Servlet 上下文初始化参数,代替或补充环境变量或系统属性。

举一个具体的例子,假设你开发了一个使用 name 属性的 @Component,如下例所示:

  • Java

  • Kotlin

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class MyBean {

	@Value("${name}")
	private String name;

	// ...

}
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Component

@Component
class MyBean {

	@Value("\${name}")
	private val name: String? = null

	// ...

}

在你的应用程序类路径(例如,在你的 jar 内部)中,你可以有一个 application.properties 文件,为 name 提供一个合理的默认属性值。在新的环境中运行时,可以在 jar 外部提供一个 application.properties 文件来覆盖 name 的值。对于一次性测试,你可以使用特定的命令行开关启动(例如,java -jar app.jar --name="Spring")。

envconfigprops 端点有助于确定某个属性为何具有特定值。你可以使用这两个端点诊断意外的属性值。详情请参阅生产就绪特性部分。

访问命令行属性

默认情况下,SpringApplication 会将任何命令行选项参数(即以 -- 开头的参数,例如 --server.port=9000)转换为一个 property 并将其添加到 Spring Environment 中。如前所述,命令行属性始终优先于基于文件的属性源。

如果你不想将命令行属性添加到 Environment 中,可以通过使用 SpringApplication.setAddCommandLineProperties(false) 来禁用它们。

JSON 应用程序属性

环境变量和系统属性通常有一些限制,导致某些属性名称无法使用。为了解决这个问题,Spring Boot 允许你将一组属性编码成一个 JSON 结构。

当你的应用程序启动时,任何 spring.application.jsonSPRING_APPLICATION_JSON 属性都将被解析并添加到 Environment 中。

例如,在 UN*X shell 中,可以在命令行上以环境变量的形式提供 SPRING_APPLICATION_JSON 属性:

$ SPRING_APPLICATION_JSON='{"my":{"name":"test"}}' java -jar myapp.jar

在上面的例子中,你在 Spring Environment 中得到了 my.name=test

相同的 JSON 也可以作为系统属性提供:

$ java -Dspring.application.json='{"my":{"name":"test"}}' -jar myapp.jar

或者你可以使用命令行参数提供 JSON:

$ java -jar myapp.jar --spring.application.json='{"my":{"name":"test"}}'

如果你部署到传统的应用服务器,你还可以使用名为 java:comp/env/spring.application.json 的 JNDI 变量。

虽然 JSON 中的 null 值会被添加到结果属性源中,但 PropertySourcesPropertyResolvernull 属性视为缺失值。这意味着 JSON 不能使用 null 值覆盖来自较低优先级属性源的属性。

外部应用程序属性

当你的应用程序启动时,Spring Boot 会自动从以下位置查找并加载 application.propertiesapplication.yaml 文件:

  1. 从类路径

    1. 类路径根目录

    2. 类路径下的 /config

  2. 从当前目录

    1. 当前目录

    2. 当前目录下的 config/ 子目录

    3. config/ 子目录的直接子目录

列表按优先级排序(较低项的值会覆盖较早项的值)。从加载的文件中获取的文档作为 PropertySource 实例添加到 Spring Environment 中。

如果你不喜欢使用 application 作为配置文件名,可以通过指定 spring.config.name 环境变量属性来切换到另一个文件名。例如,要查找 myproject.propertiesmyproject.yaml 文件,你可以按如下方式运行你的应用程序:

$ java -jar myproject.jar --spring.config.name=myproject

你还可以通过使用 spring.config.location 环境变量属性来指定显式位置。此属性接受一个逗号分隔的一个或多个位置列表,用于检查。

以下示例展示了如何指定两个不同的文件:

$ java -jar myproject.jar --spring.config.location=\
	optional:classpath:/default.properties,\
	optional:classpath:/override.properties
如果位置是可选的,并且你不介意它们不存在,请使用前缀 optional:
spring.config.namespring.config.locationspring.config.additional-location 在很早的时候就会被使用,以确定需要加载哪些文件。它们必须定义为环境变量属性(通常是操作系统环境变量、系统属性或命令行参数)。

如果 spring.config.location 包含目录(而不是文件),则它们应以 / 结尾。运行时,在加载之前,会为其附加从 spring.config.name 生成的名称。在 spring.config.location 中指定的文件会直接导入。

目录和文件位置值都会被扩展,以检查特定 profile 的文件。例如,如果你的 spring.config.locationclasspath:myconfig.properties,你也会发现相应的 classpath:myconfig-<profile>.properties 文件被加载。

在大多数情况下,你添加的每个 spring.config.location 项都会引用单个文件或目录。位置按照定义的顺序处理,后面的位置可以覆盖前面位置的值。

如果你有一个复杂的位置设置,并且使用了特定 profile 的配置文件,你可能需要提供进一步的提示,以便 Spring Boot 知道它们应该如何分组。位置组是同一级别考虑的位置集合。例如,你可能希望先将所有类路径位置分组,然后将所有外部位置分组。位置组内的项应使用 ; 分隔。详情请参阅特定 Profile 文件部分中的示例。

使用 spring.config.location 配置的位置会替换默认位置。例如,如果 spring.config.location 配置的值为 optional:classpath:/custom-config/,optional:file:./custom-config/,则考虑的完整位置集是:

  1. optional:classpath:custom-config/

  2. optional:file:./custom-config/

如果你倾向于添加额外的路径而不是替换默认路径,可以使用 spring.config.additional-location。从额外位置加载的属性可以覆盖默认位置中的属性。例如,如果 spring.config.additional-location 配置的值为 optional:classpath:/custom-config/,optional:file:./custom-config/,则考虑的完整位置集是:

  1. optional:classpath:/;optional:classpath:/config/

  2. optional:file:./;optional:file:./config/;optional:file:./config/*/

  3. optional:classpath:custom-config/

  4. optional:file:./custom-config/

这种搜索顺序允许你在一个配置文件中指定默认值,然后在另一个文件中选择性地覆盖这些值。你可以在默认位置之一的 application.properties(或你使用 spring.config.name 选择的任何其他基本名称)中为应用程序提供默认值。然后,这些默认值可以在运行时通过位于自定义位置之一的不同文件进行覆盖。

可选位置

默认情况下,当指定的配置数据位置不存在时,Spring Boot 会抛出 ConfigDataLocationNotFoundException,并且你的应用程序将不会启动。

如果你想指定一个位置,但不介意它不总是存在,可以使用 optional: 前缀。你可以在 spring.config.locationspring.config.additional-location 属性以及spring.config.import 声明中使用此前缀。

例如,spring.config.import 的值设置为 optional:file:./myconfig.properties,即使 myconfig.properties 文件缺失,你的应用程序也能启动。

如果你想忽略所有 ConfigDataLocationNotFoundException 错误并始终继续启动应用程序,可以使用 spring.config.on-not-found 属性。使用 SpringApplication.setDefaultProperties(…​) 或系统/环境变量将其值设置为 ignore

通配符位置

如果配置文件位置在最后一个路径段包含 * 字符,则将其视为通配符位置。加载配置时会扩展通配符,以便也检查直接子目录。在 Kubernetes 等环境中,当存在多个配置属性源时,通配符位置特别有用。

例如,如果你有一些 Redis 配置和一些 MySQL 配置,你可能希望将这两部分配置分开,同时要求它们都存在于 application.properties 文件中。这可能导致两个单独的 application.properties 文件挂载在不同的位置,例如 /config/redis/application.properties/config/mysql/application.properties。在这种情况下,设置通配符位置为 config/*/,将导致这两个文件都被处理。

默认情况下,Spring Boot 在默认搜索位置中包含了 config/*/。这意味着将搜索你的 jar 外部 /config 目录的所有子目录。

你可以在 spring.config.locationspring.config.additional-location 属性中自己使用通配符位置。

通配符位置必须只包含一个 *,并且对于作为目录的搜索位置必须以 */ 结尾,对于作为文件的搜索位置必须以 */<filename> 结尾。带有通配符的位置会根据文件名的绝对路径按字母顺序排序。
通配符位置仅适用于外部目录。你不能在 classpath: 位置中使用通配符。

特定 Profile 文件

除了 application 属性文件外,Spring Boot 还会尝试使用命名约定 application-{profile} 加载特定 profile 的文件。例如,如果你的应用程序激活了名为 prod 的 profile 并使用 YAML 文件,则会同时考虑 application.yamlapplication-prod.yaml

特定 profile 的属性会从与标准 application.properties 相同的位置加载,特定 profile 的文件总是会覆盖非特定文件。如果指定了多个 profile,则应用后一个优先的策略。例如,如果 spring.profiles.active 属性指定了 prod,live 这两个 profile,则 application-prod.properties 中的值可以被 application-live.properties 中的值覆盖。

后一个优先的策略适用于位置组级别。spring.config.location 的值为 classpath:/cfg/,classpath:/ext/ 与值为 classpath:/cfg/;classpath:/ext/ 的覆盖规则不同。

例如,沿用上面 prod,live 的例子,我们可能有以下文件:

/cfg
  application-live.properties
/ext
  application-live.properties
  application-prod.properties

当我们设置 spring.config.locationclasspath:/cfg/,classpath:/ext/ 时,我们会先处理所有 /cfg 下的文件,再处理所有 /ext 下的文件:

  1. /cfg/application-live.properties

  2. /ext/application-prod.properties

  3. /ext/application-live.properties

当我们改为使用 classpath:/cfg/;classpath:/ext/(使用 ; 分隔符)时,我们会将 /cfg/ext 视为同一级别处理:

  1. /ext/application-prod.properties

  2. /cfg/application-live.properties

  3. /ext/application-live.properties

Environment 有一组默认 profile(默认情况下是 [default]),如果在没有设置活动 profile 的情况下使用它们。换句话说,如果没有显式激活任何 profile,则会考虑来自 application-default 的属性。

属性文件只会加载一次。如果你已经直接导入了特定 profile 的属性文件,则不会再次导入。

导入额外数据

应用程序属性可以使用 spring.config.import 属性从其他位置导入进一步的配置数据。导入会根据发现的顺序进行处理,并被视为紧接在声明导入的文档下方插入的额外文档。

例如,你的类路径下的 application.properties 文件中可能包含以下内容:

  • Properties

  • YAML

spring.application.name=myapp
spring.config.import=optional:file:./dev.properties
spring:
  application:
    name: "myapp"
  config:
    import: "optional:file:./dev.properties"

这将触发导入当前目录下的 dev.properties 文件(如果该文件存在)。从导入的 dev.properties 文件中获取的值将优先于触发导入的文件。在上面的例子中,dev.properties 可以将 spring.application.name 重新定义为不同的值。

一个导入只会导入一次,无论它被声明多少次。导入在 properties/yaml 文件中的单个文档内定义的顺序无关紧要。例如,下面的两个例子会产生相同的结果:

  • Properties

  • YAML

spring.config.import=my.properties
my.property=value
spring:
  config:
    import: "my.properties"
my:
  property: "value"
  • Properties

  • YAML

my.property=value
spring.config.import=my.properties
my:
  property: "value"
spring:
  config:
    import: "my.properties"

在上面的两个例子中,来自 my.properties 文件的值将优先于触发其导入的文件。

可以在单个 spring.config.import 键下指定多个位置。位置将按照定义的顺序处理,后导入的将优先。

在适当的时候,也会考虑特定 Profile 的变体进行导入。上面的例子会导入 my.properties 以及任何 my-<profile>.properties 变体。

Spring Boot 包含了可插拔的 API,允许支持各种不同的位置地址。默认情况下,你可以导入 Java Properties、YAML 和配置树

第三方 jar 包可以为额外的技术提供支持(文件不需要是本地的)。例如,你可以想象配置数据来自外部存储,如 Consul、Apache ZooKeeper 或 Netflix Archaius。

如果你想支持自己的位置,请参阅 org.springframework.boot.context.config 包中的 ConfigDataLocationResolverConfigDataLoader 类。

导入无扩展名文件

某些云平台无法为卷挂载的文件添加文件扩展名。要导入这些无扩展名的文件,您需要给 Spring Boot 一个提示,以便它知道如何加载它们。您可以通过在方括号中放入扩展名提示来做到这一点。

例如,假设您有一个 /etc/config/myconfig 文件,并希望将其作为 YAML 导入。您可以从您的 application.properties 中使用以下方式导入它

  • Properties

  • YAML

spring.config.import=file:/etc/config/myconfig[.yaml]
spring:
  config:
    import: "file:/etc/config/myconfig[.yaml]"

使用配置树

在云平台(如 Kubernetes)上运行应用程序时,您经常需要读取平台提供的配置值。通常使用环境变量来实现此目的,但这可能存在缺点,特别是当值需要保密时。

作为环境变量的替代方案,现在许多云平台允许您将配置映射到挂载的数据卷中。例如,Kubernetes 可以卷挂载 ConfigMapsSecrets

有两种常用的卷挂载模式

  1. 单个文件包含完整的属性集(通常写为 YAML)。

  2. 多个文件写入目录树,文件名成为“键”,文件内容成为“值”。

对于第一种情况,您可以使用 spring.config.import 直接导入 YAML 或 Properties 文件,如上文所述。对于第二种情况,您需要使用 configtree: 前缀,以便 Spring Boot 知道需要将所有文件公开为属性。

举例来说,假设 Kubernetes 挂载了以下卷

etc/
  config/
    myapp/
      username
      password

username 文件的内容将是一个配置值,而 password 的内容将是一个秘密。

要导入这些属性,您可以将以下内容添加到您的 application.propertiesapplication.yaml 文件中

  • Properties

  • YAML

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

然后,您可以像通常一样从 Environment 中访问或注入 myapp.usernamemyapp.password 属性。

配置树下的文件夹和文件名称构成属性名称。在上面的例子中,要以 usernamepassword 访问属性,您可以将 spring.config.import 设置为 optional:configtree:/etc/config/myapp
带点符号的文件名也能正确映射。例如,在上面的例子中,/etc/config 目录下的一个名为 myapp.username 的文件将在 Environment 中产生一个 myapp.username 属性。
配置树值可以根据期望的内容绑定到字符串 Stringbyte[] 类型。

如果您有多个配置树要从同一个父文件夹导入,您可以使用通配符快捷方式。任何以 /*/ 结尾的 configtree: 位置都将把所有直接子文件夹作为配置树导入。与非通配符导入一样,每个配置树下的文件夹和文件名称构成属性名称。

例如,给定以下卷

etc/
  config/
    dbconfig/
      db/
        username
        password
    mqconfig/
      mq/
        username
        password

您可以使用 configtree:/etc/config/*/ 作为导入位置

  • Properties

  • YAML

spring.config.import=optional:configtree:/etc/config/*/
spring:
  config:
    import: "optional:configtree:/etc/config/*/"

这将添加 db.username, db.password, mq.usernamemq.password 属性。

使用通配符加载的目录按字母顺序排序。如果您需要不同的顺序,则应将每个位置单独列为一个导入项

配置树也可用于 Docker secrets。当 Docker swarm 服务被授予访问 secret 的权限时,该 secret 将被挂载到容器中。例如,如果一个名为 db.password 的 secret 挂载在 /run/secrets/ 位置,您可以使用以下方式将 db.password 提供给 Spring Environment

  • Properties

  • YAML

spring.config.import=optional:configtree:/run/secrets/
spring:
  config:
    import: "optional:configtree:/run/secrets/"

属性占位符

application.propertiesapplication.yaml 中的值在使用时会通过现有的 Environment 进行过滤,因此您可以引用先前定义的值(例如,来自系统属性或环境变量)。标准 ${name} 属性占位符语法可以在值的任何地方使用。属性占位符还可以使用 : 来分隔默认值和属性名称,从而指定默认值,例如 ${name:default}

下面示例展示了使用和不使用默认值的占位符

  • Properties

  • YAML

app.name=MyApp
app.description=${app.name} is a Spring Boot application written by ${username:Unknown}
app:
  name: "MyApp"
  description: "${app.name} is a Spring Boot application written by ${username:Unknown}"

假设 username 属性没有在其他地方设置,app.description 的值将是 MyApp is a Spring Boot application written by Unknown

在占位符中引用属性名称时,应始终使用它们的规范形式(仅使用小写字母的 kebab-case)。这将允许 Spring Boot 使用与宽松绑定 @ConfigurationProperties 时相同的逻辑。

例如,${demo.item-price} 将从 application.properties 文件中获取 demo.item-pricedemo.itemPrice 形式,以及系统环境中的 DEMO_ITEMPRICE。如果您使用 ${demo.itemPrice},则不会考虑 demo.item-priceDEMO_ITEMPRICE

您还可以使用此技术创建现有 Spring Boot 属性的“短”变体。有关详细信息,请参阅“How-to Guides”中的使用“短”命令行参数一节。

使用多文档文件

Spring Boot 允许您将单个物理文件拆分为多个逻辑文档,每个文档独立添加。文档按顺序从上到下处理。后面的文档可以覆盖前面定义的属性。

对于 application.yaml 文件,使用标准的 YAML 多文档语法。三个连续的连字符表示一个文档的结束和下一个文档的开始。

例如,以下文件包含两个逻辑文档

spring:
  application:
    name: "MyApp"
---
spring:
  application:
    name: "MyCloudApp"
  config:
    activate:
      on-cloud-platform: "kubernetes"

对于 application.properties 文件,使用特殊的 #---!--- 注释来标记文档分割

spring.application.name=MyApp
#---
spring.application.name=MyCloudApp
spring.config.activate.on-cloud-platform=kubernetes
属性文件分隔符不能有前导空白,并且必须精确包含三个连字符。分隔符正上方和正下方的行不能使用相同的注释前缀。
多文档属性文件通常与激活属性(如 spring.config.activate.on-profile)结合使用。有关详细信息,请参阅下一节
不能使用 @PropertySource@TestPropertySource 注解加载多文档属性文件。

激活属性

有时,在满足特定条件时才激活给定属性集会很有用。例如,您可能有一些属性仅在特定 profile 处于活动状态时才相关。

您可以使用 spring.config.activate.* 有条件地激活属性文档。

可用的激活属性如下

表 1. 激活属性
属性 注意

on-profile

文档要处于活动状态必须匹配的 profile 表达式,或者必须匹配至少一个文档才能处于活动状态的 profile 表达式列表。

on-cloud-platform

必须检测到的 CloudPlatform,以便文档处于活动状态。

例如,以下配置指定第二个文档仅在 Kubernetes 上运行时处于活动状态,并且仅当“prod”或“staging” profile 处于活动状态时

  • Properties

  • YAML

myprop=always-set
#---
spring.config.activate.on-cloud-platform=kubernetes
spring.config.activate.on-profile=prod | staging
myotherprop=sometimes-set
myprop:
  "always-set"
---
spring:
  config:
    activate:
      on-cloud-platform: "kubernetes"
      on-profile: "prod | staging"
myotherprop: "sometimes-set"

加密属性

Spring Boot 不提供任何内置的属性值加密支持,但是,它提供了修改 Spring Environment 中包含的值所需的钩子点。 EnvironmentPostProcessor 接口允许您在应用程序启动之前操作 Environment。有关详细信息,请参阅在应用程序启动前自定义 Environment 或 ApplicationContext

如果您需要一种安全的方式来存储凭据和密码,Spring Cloud Vault 项目提供了将外部化配置存储在 HashiCorp Vault 中的支持。

使用 YAML

YAML 是 JSON 的超集,因此是一种方便的格式,用于指定分层配置数据。只要您的 classpath 中包含 SnakeYAML 库,SpringApplication 类会自动支持 YAML 作为属性文件的替代方案。

如果您使用 starter,SnakeYAML 会由 spring-boot-starter 自动提供。

将 YAML 映射到属性

YAML 文档需要从其分层格式转换为平面结构,以便与 Spring Environment 一起使用。例如,考虑以下 YAML 文档

environments:
  dev:
    url: "https://dev.example.com"
    name: "Developer Setup"
  prod:
    url: "https://another.example.com"
    name: "My Cool App"

为了从 Environment 访问这些属性,它们将被展平如下

environments.dev.url=https://dev.example.com
environments.dev.name=Developer Setup
environments.prod.url=https://another.example.com
environments.prod.name=My Cool App

同样,YAML 列表也需要展平。它们表示为带有 [index] 解引用符的属性键。例如,考虑以下 YAML

 my:
  servers:
  - "dev.example.com"
  - "another.example.com"

上面的示例将转换为这些属性

my.servers[0]=dev.example.com
my.servers[1]=another.example.com
使用 [index] 符号的属性可以使用 Spring Boot 的 Binder 类绑定到 Java ListSet 对象。有关更多详细信息,请参阅下面的类型安全的配置属性一节。
不能使用 @PropertySource@TestPropertySource 注解加载 YAML 文件。因此,如果您需要通过这种方式加载值,则需要使用 properties 文件。

直接加载 YAML

Spring Framework 提供了两个方便的类,可用于加载 YAML 文档。 YamlPropertiesFactoryBean 将 YAML 加载为 Properties,而 YamlMapFactoryBean 将 YAML 加载为 Map

如果您希望将 YAML 加载为 Spring PropertySource,您还可以使用 YamlPropertySourceLoader 类。

配置随机值

RandomValuePropertySource 对于注入随机值(例如,用于 secret 或测试用例)非常有用。它可以生成整数、长整数、UUID 或字符串,如以下示例所示

  • Properties

  • YAML

my.secret=${random.value}
my.number=${random.int}
my.bignumber=${random.long}
my.uuid=${random.uuid}
my.number-less-than-ten=${random.int(10)}
my.number-in-range=${random.int[1024,65536]}
my:
  secret: "${random.value}"
  number: "${random.int}"
  bignumber: "${random.long}"
  uuid: "${random.uuid}"
  number-less-than-ten: "${random.int(10)}"
  number-in-range: "${random.int[1024,65536]}"

random.int* 语法是 OPEN value (,max) CLOSE,其中 OPEN,CLOSE 是任何字符,value,max 是整数。如果提供了 max,则 value 是最小值,而 max 是最大值(不包含)。

配置系统环境变量

Spring Boot 支持为环境变量设置前缀。如果系统环境被具有不同配置要求的多个 Spring Boot 应用程序共享,这将非常有用。系统环境变量的前缀可以直接在 SpringApplication 上设置。

例如,如果将前缀设置为 input,则像 remote.timeout 这样的属性也将在系统环境中解析为 input.remote.timeout

类型安全的配置属性

使用 @Value("${property}") 注解注入配置属性有时可能很麻烦,特别是当您处理多个属性或数据具有分层性质时。Spring Boot 提供了一种处理属性的替代方法,允许强类型 bean 控制和验证应用程序的配置。

另请参阅 @Value 与类型安全的配置属性之间的区别

JavaBean 属性绑定

可以绑定声明标准 JavaBean 属性的 bean,如以下示例所示

  • Java

  • Kotlin

import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties("my.service")
public class MyProperties {

	private boolean enabled;

	private InetAddress remoteAddress;

	private final Security security = new Security();

	// getters / setters...

	public boolean isEnabled() {
		return this.enabled;
	}

	public void setEnabled(boolean enabled) {
		this.enabled = enabled;
	}

	public InetAddress getRemoteAddress() {
		return this.remoteAddress;
	}

	public void setRemoteAddress(InetAddress remoteAddress) {
		this.remoteAddress = remoteAddress;
	}

	public Security getSecurity() {
		return this.security;
	}

	public static class Security {

		private String username;

		private String password;

		private List<String> roles = new ArrayList<>(Collections.singleton("USER"));

		// getters / setters...

		public String getUsername() {
			return this.username;
		}

		public void setUsername(String username) {
			this.username = username;
		}

		public String getPassword() {
			return this.password;
		}

		public void setPassword(String password) {
			this.password = password;
		}

		public List<String> getRoles() {
			return this.roles;
		}

		public void setRoles(List<String> roles) {
			this.roles = roles;
		}

	}

}
import org.springframework.boot.context.properties.ConfigurationProperties
import java.net.InetAddress

@ConfigurationProperties("my.service")
class MyProperties {

	var isEnabled = false

	var remoteAddress: InetAddress? = null

	val security = Security()

	class Security {

		var username: String? = null

		var password: String? = null

		var roles: List<String> = ArrayList(setOf("USER"))

	}

}

上面的 POJO 定义了以下属性

  • my.service.enabled,默认值为 false

  • my.service.remote-address,其类型可以从 String 强制转换。

  • my.service.security.username,带有一个嵌套的“security”对象,其名称由属性名称决定。特别是,这里的类型根本不使用,也可以是 SecurityProperties

  • my.service.security.password.

  • my.service.security.roles,包含一个 String 集合,默认值为 USER

要在属性名称中使用保留关键字,例如 my.service.import,请在属性的字段上使用 @Name 注解。
映射到 Spring Boot 中提供的 @ConfigurationProperties 类的属性(通过 properties 文件、YAML 文件、环境变量和其他机制配置)是公共 API,但类本身的访问器(getter/setter)并非 intended 直接使用。

这种安排依赖于默认的空构造函数,并且 getter 和 setter 通常是必需的,因为绑定是通过标准的 Java Beans 属性描述符进行的,就像在 Spring MVC 中一样。在以下情况下可以省略 setter

  • Map,只要它们已初始化,就需要 getter 但不一定需要 setter,因为它们可以由 binder 修改。

  • 集合和数组可以通过索引(通常与 YAML 一起使用)或使用单个逗号分隔的值(properties 文件)进行访问。在后一种情况下,setter 是强制性的。我们建议始终为这些类型添加 setter。如果您初始化一个集合,请确保它不是不可变的(如前面的示例)。

  • 如果嵌套的 POJO 属性已初始化(如前面示例中的 Security 字段),则不需要 setter。如果您希望 binder 使用其默认构造函数即时创建实例,则需要 setter。

有些人使用 Project Lombok 自动添加 getter 和 setter。请确保 Lombok 不为此类类型生成任何特定构造函数,因为它由容器自动用于实例化对象。

最后,仅考虑标准的 Java Bean 属性,不支持静态属性的绑定。

构造函数绑定

上一节的示例可以用不可变的方式重写,如以下示例所示

  • Java

  • Kotlin

import java.net.InetAddress;
import java.util.List;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.DefaultValue;

@ConfigurationProperties("my.service")
public class MyProperties {

	// fields...

	private final boolean enabled;

	private final InetAddress remoteAddress;

	private final Security security;


	public MyProperties(boolean enabled, InetAddress remoteAddress, Security security) {
		this.enabled = enabled;
		this.remoteAddress = remoteAddress;
		this.security = security;
	}

	// getters...

	public boolean isEnabled() {
		return this.enabled;
	}

	public InetAddress getRemoteAddress() {
		return this.remoteAddress;
	}

	public Security getSecurity() {
		return this.security;
	}

	public static class Security {

		// fields...

		private final String username;

		private final String password;

		private final List<String> roles;


		public Security(String username, String password, @DefaultValue("USER") List<String> roles) {
			this.username = username;
			this.password = password;
			this.roles = roles;
		}

		// getters...

		public String getUsername() {
			return this.username;
		}

		public String getPassword() {
			return this.password;
		}

		public List<String> getRoles() {
			return this.roles;
		}

	}

}
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.context.properties.bind.DefaultValue
import java.net.InetAddress

@ConfigurationProperties("my.service")
class MyProperties(val enabled: Boolean, val remoteAddress: InetAddress,
		val security: Security) {

	class Security(val username: String, val password: String,
			@param:DefaultValue("USER") val roles: List<String>)

}

在这种设置中,存在单个带参数的构造函数意味着应使用构造函数绑定。这意味着 binder 将找到一个带有您希望绑定的参数的构造函数。如果您的类有多个构造函数,可以使用 @ConstructorBinding 注解指定用于构造函数绑定的构造函数。对于具有单个带参数构造函数的类,要退出构造函数绑定,该构造函数必须用 @Autowired 注解或设为 private。构造函数绑定可与 records 一起使用。除非您的 record 有多个构造函数,否则无需使用 @ConstructorBinding

构造函数绑定类的嵌套成员(如上面示例中的 Security)也将通过其构造函数进行绑定。

可以使用构造函数参数和 record 组件上的 @DefaultValue 指定默认值。转换服务将应用于将注解的 String 值强制转换为缺失属性的目标类型。

回到前面的例子,如果没有属性绑定到 Security,则 MyProperties 实例将包含 securitynull 值。即使没有属性绑定到它(使用 Kotlin 时,这需要将 Securityusernamepassword 参数声明为可空,因为它们没有默认值),若要使其包含非 null 的 Security 实例,请使用空的 @DefaultValue 注解

  • Java

  • Kotlin

	public MyProperties(boolean enabled, InetAddress remoteAddress, @DefaultValue Security security) {
		this.enabled = enabled;
		this.remoteAddress = remoteAddress;
		this.security = security;
	}
class MyProperties(val enabled: Boolean, val remoteAddress: InetAddress,
		@DefaultValue val security: Security) {

	class Security(val username: String?, val password: String?,
			@param:DefaultValue("USER") val roles: List<String>)

}
要使用构造函数绑定,必须使用 @EnableConfigurationProperties 或配置属性扫描来启用该类。您不能将构造函数绑定与由常规 Spring 机制创建的 bean 一起使用(例如 @Component bean、使用 @Bean 方法创建的 bean 或使用 @Import 加载的 bean)。
要使用构造函数绑定,必须使用 -parameters 编译该类。如果您使用 Spring Boot 的 Gradle 插件或使用 Maven 和 spring-boot-starter-parent,这将自动发生。
不建议将 Optional@ConfigurationProperties 一起使用,因为它主要 intended 用作返回类型。因此,它不太适合配置属性注入。为了与其他类型属性保持一致,如果您声明一个 Optional 属性但它没有值,将绑定 null 而不是空的 Optional
要在属性名称中使用保留关键字,例如 my.service.import,请在构造函数参数上使用 @Name 注解。

启用 @ConfigurationProperties 注解的类型

Spring Boot 提供了绑定 @ConfigurationProperties 类型并将其注册为 bean 的基础设施。您可以按类启用配置属性,也可以启用配置属性扫描,其工作方式类似于组件扫描。

有时,用 @ConfigurationProperties 注解的类可能不适合扫描,例如,如果您正在开发自己的自动配置或希望有条件地启用它们。在这种情况下,使用 @EnableConfigurationProperties 注解指定要处理的类型列表。这可以在任何 @Configuration 类上完成,如以下示例所示

  • Java

  • Kotlin

import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(SomeProperties.class)
public class MyConfiguration {

}
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Configuration

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(SomeProperties::class)
class MyConfiguration
  • Java

  • Kotlin

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties("some.properties")
public class SomeProperties {

}
import org.springframework.boot.context.properties.ConfigurationProperties

@ConfigurationProperties("some.properties")
class SomeProperties

要使用配置属性扫描,请将 @ConfigurationPropertiesScan 注解添加到您的应用程序中。通常,它被添加到用 @SpringBootApplication 注解的主应用程序类上,但也可以添加到任何 @Configuration 类上。默认情况下,扫描将从声明注解的类所在的包开始。如果您想定义特定的扫描包,可以如下示例所示进行

  • Java

  • Kotlin

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;

@SpringBootApplication
@ConfigurationPropertiesScan({ "com.example.app", "com.example.another" })
public class MyApplication {

}
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.context.properties.ConfigurationPropertiesScan

@SpringBootApplication
@ConfigurationPropertiesScan("com.example.app", "com.example.another")
class MyApplication

当使用配置属性扫描或通过 @EnableConfigurationProperties 注册 @ConfigurationProperties bean 时,该 bean 具有一个常规名称:<prefix>-<fqn>,其中 <prefix> 是在 @ConfigurationProperties 注解中指定的环境键前缀,<fqn> 是 bean 的完全限定名。如果注解没有提供任何前缀,则仅使用 bean 的完全限定名。

假设它在 com.example.app 包中,上面的 SomeProperties 示例的 bean 名称是 some.properties-com.example.app.SomeProperties

我们建议 @ConfigurationProperties 仅处理环境,特别是不要从上下文中注入其他 bean。对于特殊情况,可以使用 setter 注入或框架提供的任何 *Aware 接口(如果您需要访问 Environment,例如 EnvironmentAware)。如果您仍然希望使用构造函数注入其他 bean,则配置属性 bean 必须使用 @Component 注解并使用基于 JavaBean 的属性绑定。

使用 @ConfigurationProperties 注解的类型

这种配置方式与 SpringApplication 外部 YAML 配置结合使用效果尤其好,如以下示例所示

my:
  service:
    remote-address: 192.168.1.1
    security:
      username: "admin"
      roles:
      - "USER"
      - "ADMIN"

要使用 @ConfigurationProperties bean,您可以像注入任何其他 bean 一样注入它们,如以下示例所示

  • Java

  • Kotlin

import org.springframework.stereotype.Service;

@Service
public class MyService {

	private final MyProperties properties;

	public MyService(MyProperties properties) {
		this.properties = properties;
	}

	public void openConnection() {
		Server server = new Server(this.properties.getRemoteAddress());
		server.start();
		// ...
	}

	// ...

}
import org.springframework.stereotype.Service

@Service
class MyService(val properties: MyProperties) {

	fun openConnection() {
		val server = Server(properties.remoteAddress)
		server.start()
		// ...
	}

	// ...

}
使用 @ConfigurationProperties 还允许您生成元数据文件,IDE 可以使用这些文件为您的自定义键提供自动补全。有关详细信息,请参阅附录

第三方配置

除了使用 @ConfigurationProperties 注解类外,您还可以将其用于公共的 @Bean 方法。当您想将属性绑定到您无法控制的第三方组件时,这样做特别有用。

要从 Environment 属性配置 bean,请将 @ConfigurationProperties 添加到其 bean 注册中,如以下示例所示

  • Java

  • Kotlin

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class ThirdPartyConfiguration {

	@Bean
	@ConfigurationProperties(prefix = "another")
	public AnotherComponent anotherComponent() {
		return new AnotherComponent();
	}

}
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration(proxyBeanMethods = false)
class ThirdPartyConfiguration {

	@Bean
	@ConfigurationProperties(prefix = "another")
	fun anotherComponent(): AnotherComponent = AnotherComponent()

}

任何使用 another 前缀定义的 JavaBean 属性都将以类似于前面 SomeProperties 示例的方式映射到该 AnotherComponent bean。

宽松绑定

Spring Boot 使用一些宽松规则将 Environment 属性绑定到 @ConfigurationProperties bean,因此 Environment 属性名和 bean 属性名之间无需完全匹配。这有用的常见示例包括使用连字符分隔的环境属性(例如,context-path 绑定到 contextPath)和首字母大写的环境属性(例如,PORT 绑定到 port)。

例如,考虑以下 @ConfigurationProperties

  • Java

  • Kotlin

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "my.main-project.person")
public class MyPersonProperties {

	private String firstName;

	public String getFirstName() {
		return this.firstName;
	}

	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}

}
import org.springframework.boot.context.properties.ConfigurationProperties

@ConfigurationProperties(prefix = "my.main-project.person")
class MyPersonProperties {

	var firstName: String? = null

}

通过上述代码,以下所有属性名称都可以使用

表 2. 宽松绑定
属性 注意

my.main-project.person.first-name

Kebab 命名法,推荐用于 .properties 和 YAML 文件中。

my.main-project.person.firstName

标准的驼峰命名法语法。

my.main-project.person.first_name

下划线表示法,是 .properties 和 YAML 文件中使用的另一种格式。

MY_MAINPROJECT_PERSON_FIRSTNAME

大写格式,推荐在系统环境变量中使用。

注解的 prefix必须是 kebab 命名法(小写并用 - 分隔,例如 my.main-project.person)。
表 3. 按属性源区分的宽松绑定规则
属性源 简单类型 列表

Properties 文件

驼峰命名法、kebab 命名法或下划线表示法

使用 [ ] 或逗号分隔值的标准列表语法

YAML 文件

驼峰命名法、kebab 命名法或下划线表示法

标准的 YAML 列表语法或逗号分隔值

环境变量

使用下划线作为分隔符的大写格式(参见 从环境变量绑定)。

被下划线包围的数字值(参见 从环境变量绑定

系统属性

驼峰命名法、kebab 命名法或下划线表示法

使用 [ ] 或逗号分隔值的标准列表语法

我们建议,如果可能,属性以小写 kebab 格式存储,例如 my.person.first-name=Rod

绑定 Map

当绑定到 Map 属性时,您可能需要使用特殊的括号表示法,以便保留原始的 key 值。如果 key 没有被 [] 包围,任何非字母数字、-. 的字符都将被移除。

例如,考虑将以下属性绑定到 Map<String,String>

  • Properties

  • YAML

my.map[/key1]=value1
my.map[/key2]=value2
my.map./key3=value3
my:
  map:
    "[/key1]": "value1"
    "[/key2]": "value2"
    "/key3": "value3"
对于 YAML 文件,括号需要用引号括起来,以便正确解析 key。

上述属性将绑定到一个 Map,其 key 为 /key1/key2key3key3 中的斜杠已被移除,因为它没有被方括号包围。

当绑定到标量值时,包含 . 的 key 不需要被 [] 包围。标量值包括枚举以及 java.lang 包中的所有类型(除了 Object)。将 a.b=c 绑定到 Map<String, String> 将保留 key 中的 .,并返回一个包含条目 {"a.b"="c"} 的 Map。对于任何其他类型,如果您的 key 包含 .,则需要使用括号表示法。例如,将 a.b=c 绑定到 Map<String, Object> 将返回一个包含条目 {"a"={"b"="c"}} 的 Map,而 [a.b]=c 将返回一个包含条目 {"a.b"="c"} 的 Map。

从环境变量绑定

大多数操作系统对环境变量的名称有严格的规则。例如,Linux shell 变量只能包含字母(azAZ)、数字(09)或下划线字符(_)。按照惯例,Unix shell 变量的名称也通常采用大写。

Spring Boot 的宽松绑定规则尽可能地设计为与这些命名限制兼容。

要将规范形式(canonical-form)的属性名称转换为环境变量名称,您可以遵循以下规则

  • 将点号 (.) 替换为下划线 (_)。

  • 移除所有连字符 (-)。

  • 转换为大写。

例如,配置属性 spring.main.log-startup-info 将对应名为 SPRING_MAIN_LOGSTARTUPINFO 的环境变量。

环境变量也可用于绑定对象列表。要绑定到 List,变量名称中的元素索引应被下划线包围。

例如,配置属性 my.service[0].other 将使用名为 MY_SERVICE_0_OTHER 的环境变量。

从环境变量绑定的支持应用于 systemEnvironment 属性源以及任何名称以 -systemEnvironment 结尾的附加属性源。

从环境变量绑定 Map

当 Spring Boot 将环境变量绑定到属性类时,它会在绑定前将环境变量名称转换为小写。大多数情况下,这个细节并不重要,除非绑定到 Map 属性。

Map 中的 key 始终是小写的,如下例所示

  • Java

  • Kotlin

import java.util.HashMap;
import java.util.Map;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "my.props")
public class MyMapsProperties {

	private final Map<String, String> values = new HashMap<>();

	public Map<String, String> getValues() {
		return this.values;
	}

}
import org.springframework.boot.context.properties.ConfigurationProperties

@ConfigurationProperties(prefix = "my.props")
class MyMapsProperties {

	val values: Map<String, String> = HashMap()

}

当设置 MY_PROPS_VALUES_KEY=value 时,values Map 包含一个条目 {"key"="value"}

只有环境变量的 名称 被转换为小写,而不是值。当设置 MY_PROPS_VALUES_KEY=VALUE 时,values Map 包含一个条目 {"key"="VALUE"}

缓存

宽松绑定使用缓存来提高性能。默认情况下,此缓存仅应用于不可变属性源。要自定义此行为,例如为可变属性源启用缓存,请使用 ConfigurationPropertyCaching

合并复杂类型

当列表在多个地方配置时,覆盖的工作方式是替换整个列表。

例如,假设一个 MyPojo 对象,其 namedescription 属性默认为 null。以下示例从 MyProperties 中暴露了一个 MyPojo 对象列表

  • Java

  • Kotlin

import java.util.ArrayList;
import java.util.List;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties("my")
public class MyProperties {

	private final List<MyPojo> list = new ArrayList<>();

	public List<MyPojo> getList() {
		return this.list;
	}

}
import org.springframework.boot.context.properties.ConfigurationProperties

@ConfigurationProperties("my")
class MyProperties {

	val list: List<MyPojo> = ArrayList()

}

考虑以下配置

  • Properties

  • YAML

my.list[0].name=my name
my.list[0].description=my description
#---
spring.config.activate.on-profile=dev
my.list[0].name=my another name
my:
  list:
  - name: "my name"
    description: "my description"
---
spring:
  config:
    activate:
      on-profile: "dev"
my:
  list:
  - name: "my another name"

如果 dev profile 未激活,MyProperties.list 包含一个 MyPojo 条目,如前所述。但是,如果 dev profile 启用,list 仍然只包含一个条目(名称为 my another name,描述为 null)。此配置不会向列表中添加第二个 MyPojo 实例,并且不会合并项目。

List 在多个 profile 中指定时,只使用优先级最高的那一个。考虑以下示例

  • Properties

  • YAML

my.list[0].name=my name
my.list[0].description=my description
my.list[1].name=another name
my.list[1].description=another description
#---
spring.config.activate.on-profile=dev
my.list[0].name=my another name
my:
  list:
  - name: "my name"
    description: "my description"
  - name: "another name"
    description: "another description"
---
spring:
  config:
    activate:
      on-profile: "dev"
my:
  list:
  - name: "my another name"

在前面的示例中,如果 dev profile 处于激活状态,MyProperties.list 包含一个 MyPojo 条目(名称为 my another name,描述为 null)。对于 YAML,可以使用逗号分隔列表和 YAML 列表来完全覆盖列表的内容。

对于 Map 属性,您可以使用来自多个源的属性值进行绑定。但是,对于多个源中的同一个属性,使用优先级最高的那个。以下示例从 MyProperties 中暴露了一个 Map<String, MyPojo>

  • Java

  • Kotlin

import java.util.LinkedHashMap;
import java.util.Map;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties("my")
public class MyProperties {

	private final Map<String, MyPojo> map = new LinkedHashMap<>();

	public Map<String, MyPojo> getMap() {
		return this.map;
	}

}
import org.springframework.boot.context.properties.ConfigurationProperties

@ConfigurationProperties("my")
class MyProperties {

	val map: Map<String, MyPojo> = LinkedHashMap()

}

考虑以下配置

  • Properties

  • YAML

my.map.key1.name=my name 1
my.map.key1.description=my description 1
#---
spring.config.activate.on-profile=dev
my.map.key1.name=dev name 1
my.map.key2.name=dev name 2
my.map.key2.description=dev description 2
my:
  map:
    key1:
      name: "my name 1"
      description: "my description 1"
---
spring:
  config:
    activate:
      on-profile: "dev"
my:
  map:
    key1:
      name: "dev name 1"
    key2:
      name: "dev name 2"
      description: "dev description 2"

如果 dev profile 未激活,MyProperties.map 包含一个 key 为 key1 的条目(名称为 my name 1,描述为 my description 1)。但是,如果 dev profile 启用,map 包含两个条目,key 分别为 key1(名称为 dev name 1,描述为 my description 1)和 key2(名称为 dev name 2,描述为 dev description 2)。

上述合并规则适用于所有属性源的属性,而不仅仅是文件。

属性转换

当 Spring Boot 绑定到 @ConfigurationProperties bean 时,它会尝试将外部应用属性强制转换为正确的类型。如果您需要自定义类型转换,可以提供一个 ConversionService bean( bean 名称为 conversionService),或通过 CustomEditorConfigurer bean 提供自定义属性编辑器,或提供标记为 @ConfigurationPropertiesBinding 注解的 bean 定义的自定义转换器。

由于此 bean 在应用生命周期的早期就被请求,请确保限制您的 ConversionService 所使用的依赖项。通常,您需要的任何依赖项在创建时可能尚未完全初始化。如果您的自定义 ConversionService 不是配置 key 强制转换所必需的,并且只依赖于使用 @ConfigurationPropertiesBinding 限定的自定义转换器,则您可能需要重命名它。

转换 Duration

Spring Boot 为表示持续时间(duration)提供了专门的支持。如果您暴露 Duration 属性,应用属性中可使用以下格式

  • 常规的 long 表示法(除非指定了 @DurationUnit,否则默认单位为毫秒)

  • 标准 ISO-8601 格式,Duration 使用

  • 更易读的格式,其中值和单位结合(10s 表示 10 秒)

考虑以下示例

  • Java

  • Kotlin

import java.time.Duration;
import java.time.temporal.ChronoUnit;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.convert.DurationUnit;

@ConfigurationProperties("my")
public class MyProperties {

	@DurationUnit(ChronoUnit.SECONDS)
	private Duration sessionTimeout = Duration.ofSeconds(30);

	private Duration readTimeout = Duration.ofMillis(1000);

	// getters / setters...

	public Duration getSessionTimeout() {
		return this.sessionTimeout;
	}

	public void setSessionTimeout(Duration sessionTimeout) {
		this.sessionTimeout = sessionTimeout;
	}

	public Duration getReadTimeout() {
		return this.readTimeout;
	}

	public void setReadTimeout(Duration readTimeout) {
		this.readTimeout = readTimeout;
	}

}
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.convert.DurationUnit
import java.time.Duration
import java.time.temporal.ChronoUnit

@ConfigurationProperties("my")
class MyProperties {

	@DurationUnit(ChronoUnit.SECONDS)
	var sessionTimeout = Duration.ofSeconds(30)

	var readTimeout = Duration.ofMillis(1000)

}

要指定 30 秒的会话超时,30PT30S30s 都等效。500ms 的读取超时可以用以下任一形式指定:500PT0.5S500ms

您也可以使用任何支持的单位。它们是

  • ns 表示纳秒

  • us 表示微秒

  • ms 表示毫秒

  • s 表示秒

  • m 表示分钟

  • h 表示小时

  • d 表示天

默认单位是毫秒,可以使用 @DurationUnit 覆盖,如上面的示例所示。

如果您更喜欢使用构造函数绑定,可以暴露相同的属性,如下例所示

  • Java

  • Kotlin

import java.time.Duration;
import java.time.temporal.ChronoUnit;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.DefaultValue;
import org.springframework.boot.convert.DurationUnit;

@ConfigurationProperties("my")
public class MyProperties {

	// fields...
	private final Duration sessionTimeout;

	private final Duration readTimeout;

	public MyProperties(@DurationUnit(ChronoUnit.SECONDS) @DefaultValue("30s") Duration sessionTimeout,
			@DefaultValue("1000ms") Duration readTimeout) {
		this.sessionTimeout = sessionTimeout;
		this.readTimeout = readTimeout;
	}

	// getters...

	public Duration getSessionTimeout() {
		return this.sessionTimeout;
	}

	public Duration getReadTimeout() {
		return this.readTimeout;
	}

}
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.context.properties.bind.DefaultValue
import org.springframework.boot.convert.DurationUnit
import java.time.Duration
import java.time.temporal.ChronoUnit

@ConfigurationProperties("my")
class MyProperties(@param:DurationUnit(ChronoUnit.SECONDS) @param:DefaultValue("30s") val sessionTimeout: Duration,
		@param:DefaultValue("1000ms") val readTimeout: Duration)
如果您正在升级 Long 属性,请确保在单位不是毫秒时定义单位(使用 @DurationUnit)。这样做可以提供一个透明的升级路径,同时支持更丰富的格式。

转换 Period

除了持续时间(duration),Spring Boot 还可以使用 Period 类型。应用属性中可使用以下格式

  • 常规的 int 表示法(除非指定了 @PeriodUnit,否则默认单位为天)

  • 标准 ISO-8601 格式,Period 使用

  • 更简单的格式,其中值和单位对结合(1y3d 表示 1 年零 3 天)

简单格式支持以下单位

  • y 表示年

  • m 表示月

  • w 表示周

  • d 表示天

Period 类型实际上从不存储周数,它只是一个表示“7 天”的快捷方式。

转换数据大小

Spring Framework 有一个 DataSize 值类型,它表示以字节为单位的大小。如果您暴露 DataSize 属性,应用属性中可使用以下格式

  • 常规的 long 表示法(除非指定了 @DataSizeUnit,否则默认单位为字节)

  • 更易读的格式,其中值和单位结合(10MB 表示 10 兆字节)

考虑以下示例

  • Java

  • Kotlin

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.convert.DataSizeUnit;
import org.springframework.util.unit.DataSize;
import org.springframework.util.unit.DataUnit;

@ConfigurationProperties("my")
public class MyProperties {

	@DataSizeUnit(DataUnit.MEGABYTES)
	private DataSize bufferSize = DataSize.ofMegabytes(2);

	private DataSize sizeThreshold = DataSize.ofBytes(512);

	// getters/setters...

	public DataSize getBufferSize() {
		return this.bufferSize;
	}

	public void setBufferSize(DataSize bufferSize) {
		this.bufferSize = bufferSize;
	}

	public DataSize getSizeThreshold() {
		return this.sizeThreshold;
	}

	public void setSizeThreshold(DataSize sizeThreshold) {
		this.sizeThreshold = sizeThreshold;
	}

}
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.convert.DataSizeUnit
import org.springframework.util.unit.DataSize
import org.springframework.util.unit.DataUnit

@ConfigurationProperties("my")
class MyProperties {

	@DataSizeUnit(DataUnit.MEGABYTES)
	var bufferSize = DataSize.ofMegabytes(2)

	var sizeThreshold = DataSize.ofBytes(512)

}

要指定 10 兆字节的缓冲区大小,1010MB 等效。256 字节的大小阈值可以指定为 256256B

您也可以使用任何支持的单位。它们是

  • B 表示字节

  • KB 表示千字节

  • MB 表示兆字节

  • GB 表示千兆字节

  • TB 表示太字节

默认单位是字节,可以使用 @DataSizeUnit 覆盖,如上面的示例所示。

如果您更喜欢使用构造函数绑定,可以暴露相同的属性,如下例所示

  • Java

  • Kotlin

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.DefaultValue;
import org.springframework.boot.convert.DataSizeUnit;
import org.springframework.util.unit.DataSize;
import org.springframework.util.unit.DataUnit;

@ConfigurationProperties("my")
public class MyProperties {

	// fields...
	private final DataSize bufferSize;

	private final DataSize sizeThreshold;

	public MyProperties(@DataSizeUnit(DataUnit.MEGABYTES) @DefaultValue("2MB") DataSize bufferSize,
			@DefaultValue("512B") DataSize sizeThreshold) {
		this.bufferSize = bufferSize;
		this.sizeThreshold = sizeThreshold;
	}

	// getters...

	public DataSize getBufferSize() {
		return this.bufferSize;
	}

	public DataSize getSizeThreshold() {
		return this.sizeThreshold;
	}

}
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.context.properties.bind.DefaultValue
import org.springframework.boot.convert.DataSizeUnit
import org.springframework.util.unit.DataSize
import org.springframework.util.unit.DataUnit

@ConfigurationProperties("my")
class MyProperties(@param:DataSizeUnit(DataUnit.MEGABYTES) @param:DefaultValue("2MB") val bufferSize: DataSize,
		@param:DefaultValue("512B") val sizeThreshold: DataSize)
如果您正在升级 Long 属性,请确保在单位不是字节时定义单位(使用 @DataSizeUnit)。这样做可以提供一个透明的升级路径,同时支持更丰富的格式。

转换 Base64 数据

Spring Boot 支持解析 Base64 编码的二进制数据。如果您暴露一个 Resource 属性,可以将 Base64 编码文本作为值提供,并加上 base64: 前缀,如下例所示

  • Properties

  • YAML

my.property=base64:SGVsbG8gV29ybGQ=
my:
  property: base64:SGVsbG8gV29ybGQ=
Resource 属性也可用于提供资源的路径,使其更加灵活。

@ConfigurationProperties 验证

每当 @ConfigurationProperties 类使用 Spring 的 @Validated 注解时,Spring Boot 会尝试对其进行验证。您可以直接在配置类上使用 JSR-303 jakarta.validation 约束注解。为此,请确保 classpath 中存在兼容的 JSR-303 实现,然后向您的字段添加约束注解,如下例所示

  • Java

  • Kotlin

import java.net.InetAddress;

import jakarta.validation.constraints.NotNull;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;

@ConfigurationProperties("my.service")
@Validated
public class MyProperties {

	@NotNull
	private InetAddress remoteAddress;

	// getters/setters...

	public InetAddress getRemoteAddress() {
		return this.remoteAddress;
	}

	public void setRemoteAddress(InetAddress remoteAddress) {
		this.remoteAddress = remoteAddress;
	}

}
import jakarta.validation.constraints.NotNull
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.validation.annotation.Validated
import java.net.InetAddress

@ConfigurationProperties("my.service")
@Validated
class MyProperties {

	var remoteAddress: @NotNull InetAddress? = null

}
您也可以通过使用 @Validated 注解标记创建配置属性的 @Bean 方法来触发验证。

要将验证级联到嵌套属性,相关字段必须使用 @Valid 进行注解。以下示例基于前面的 MyProperties 示例

  • Java

  • Kotlin

import java.net.InetAddress;

import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;

@ConfigurationProperties("my.service")
@Validated
public class MyProperties {

	@NotNull
	private InetAddress remoteAddress;

	@Valid
	private final Security security = new Security();

	// getters/setters...

	public InetAddress getRemoteAddress() {
		return this.remoteAddress;
	}

	public void setRemoteAddress(InetAddress remoteAddress) {
		this.remoteAddress = remoteAddress;
	}

	public Security getSecurity() {
		return this.security;
	}

	public static class Security {

		@NotEmpty
		private String username;

		// getters/setters...

		public String getUsername() {
			return this.username;
		}

		public void setUsername(String username) {
			this.username = username;
		}

	}

}
import jakarta.validation.Valid
import jakarta.validation.constraints.NotEmpty
import jakarta.validation.constraints.NotNull
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.validation.annotation.Validated
import java.net.InetAddress

@ConfigurationProperties("my.service")
@Validated
class MyProperties {

	var remoteAddress: @NotNull InetAddress? = null

	@Valid
	val security = Security()

	class Security {

		@NotEmpty
		var username: String? = null

	}

}

您还可以通过创建一个名为 configurationPropertiesValidator 的 bean 定义来添加自定义 Spring Validator。该 @Bean 方法应声明为 static。配置属性验证器在应用的生命周期早期创建,将 @Bean 方法声明为 static 允许创建 bean,而无需实例化 @Configuration 类。这样做可以避免早期实例化可能导致的任何问题。

spring-boot-actuator 模块包含一个暴露所有 @ConfigurationProperties bean 的端点。在您的网络浏览器中访问 /actuator/configprops 或使用等效的 JMX 端点。详情请参见 生产就绪特性 部分。

@ConfigurationProperties 对比 @Value

@Value 注解是核心容器特性,它不提供与类型安全的配置属性相同的功能。下表总结了 @ConfigurationProperties@Value 支持的功能

特性 @ConfigurationProperties @Value

宽松绑定

有限(参见 下面的说明

SpEL 评估

如果您确实想使用 @Value,我们建议您使用其规范形式(仅使用小写字母的 kebab-case)引用属性名称。这将允许 Spring Boot 使用与对 @ConfigurationProperties 进行宽松绑定时相同的逻辑。

例如,@Value("${demo.item-price}") 将从 application.properties 文件中获取 demo.item-pricedemo.itemPrice 形式,并从系统环境中获取 DEMO_ITEMPRICE。如果您改用 @Value("${demo.itemPrice}"),则不会考虑 demo.item-priceDEMO_ITEMPRICE

如果您为自己的组件定义了一组配置 key,我们建议您将它们组合到一个使用 @ConfigurationProperties 注解的 POJO 中。这样做将为您提供结构化、类型安全的对象,您可以将其注入到自己的 bean 中。

应用属性文件中的 SpEL 表达式在解析这些文件和填充环境时不会被处理。但是,可以在 @Value 中编写 SpEL 表达式。如果来自应用属性文件的属性值是 SpEL 表达式,则在使用 @Value 消费时会对其进行评估。