Spring Boot 配置外部化中文文档

本文为官方文档直译版本。原文链接

Spring Boot 配置外部化中文文档

    • 前言
    • 访问命令行属性
    • JSON 应用程序属性
    • 外部应用程序属性
      • 可选位置
      • 通配符位置
      • 特定配置文件
      • 导入附加数据
      • 导入无扩展名的文件
      • 使用配置树
      • 属性占位符
      • 使用多文档文件
    • 激活属性
    • 加密属性
    • 使用 YAML
      • 映射 YAML 到属性
      • 直接加载 YAML
    • 配置随机值
    • 设置系统环境属性
    • 类型安全的配置属性
      • JavaBean 属性绑定
      • 构造函数绑定
      • 启用 @ConfigurationProperties
      • 使用 @ConfigurationProperties
      • 第三方配置
      • 宽松绑定
        • 绑定Map
        • 从环境变量绑定
        • 缓存
      • 合并复杂类型
      • 属性转换
        • 转换Duration
        • 转换Period
        • 转换DataSize
      • @ConfigurationProperties 验证
      • @ConfigurationProperties 对比 @Value

前言

Spring Boot 允许您将配置外部化,这样您就可以在不同的环境中使用相同的应用程序代码。您可以使用各种外部配置源,包括 Java 属性文件、YAML 文件、环境变量和命令行参数。
属性值可以通过 @Value 注解直接注入到 Bean 中,也可以通过 Spring 的Environment抽象访问,还可以通过 @ConfigurationProperties 绑定到结构化对象中。
Spring Boot 使用一种非常特殊的 PropertySource 顺序,旨在允许对值进行合理的覆盖。后面的属性源可以覆盖前面属性源中定义的值。属性源按以下顺序考虑:

  1. 默认属性(通过设置 SpringApplication.setDefaultProperties 指定)。
  2. 配置类上的 @PropertySource 注解。请注意,在应用程序上下文刷新之前,此类属性源不会添加到环境中。这对于配置某些属性(如 logging.*spring.main.*)来说为时已晚,因为这些属性是在刷新开始前读取的。
  3. 配置数据(如 application.properties 文件)。
  4. RandomValuePropertySource 只包含 random.* 中的属性。
  5. 操作系统环境变量。
  6. Java 系统属性(System.getProperties())。
  7. 来自 java:comp/env.NET 的 JNDI 属性。
  8. ServletContext 初始参数。
  9. ServletConfig 初始参数。
  10. SPRING_APPLICATION_JSON 中的属性(嵌入到环境变量或系统属性中的内联 JSON)。
  11. 命令行参数。
  12. 测试属性 在 @SpringBootTest 和测试注解中可用,用于测试应用程序的特定片段。
  13. 在测试中使用 @DynamicPropertySource 注解。
  14. 测试中的 @TestPropertySource 注解。
  15. Devtools 激活时,$HOME/.config/spring-boot 目录中的 Devtools 全局设置属性。

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

  1. 打包在 jar 中的应用程序属性(application.properties 和 YAML 变体)。
  2. 打包在 jar 中的特定于配置文件的应用程序属性(application-{profile}.properties 和 YAML 变体)。
  3. 打包在 jar 之外的应用程序属性(application.properties 和 YAML 变体)。
  4. 打包的 jar 之外的特定配置文件应用程序属性(application-{profile}.properties 和 YAML 变体)。

建议整个应用程序使用一种格式。如果在同一位置同时存在 .properties 和 YAML 格式的配置文件,则 .properties 优先。

如果使用环境变量而不是系统属性,大多数操作系统不允许使用以句点分隔的键名,但可以使用下划线代替(例如,用 SPRING_CONFIG_NAME 代替 spring.config.name)。详情请参阅从环境变量绑定。

如果应用程序在 servlet 容器或应用程序服务器中运行,则可以使用 JNDI 属性(java:comp/env 中)或 servlet 上下文初始化参数来代替或与环境变量或系统属性一样使用。

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

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

@Component
public class MyBean {

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

    // ...

}

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

envconfigprops 端点有助于确定属性具有特定值的原因。你可以使用这两个端点来诊断意外的属性值。有关详情,请参阅 "生产就绪功能 "部分。

访问命令行属性

默认情况下,SpringApplication 会将任何命令行选项参数(即以 -- 开头的参数,如 --server.port=9000)转换为属性,并将其添加到 Spring Environment 中。如前所述,命令行属性总是优先于基于文件的属性源。
如果不想将命令行属性添加到环境中,可以使用 SpringApplication.setAddCommandLineProperties(false) 将其禁用。

JSON 应用程序属性

环境变量和系统属性通常有一些限制,意味着某些属性名称不能使用。为了帮助解决这个问题,Spring Boot 允许您将一组属性编码为单一的 JSON 结构。
当应用程序启动时,spring.application.jsonSPRING_APPLICATION_JSON 属性将被解析并添加到环境中。
例如,SPRING_APPLICATION_JSON 属性可以作为环境变量在 UNIX shell 的命令行中提供:

$ 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 中的空值会被添加到生成的属性源中,但 PropertySourcesPropertyResolver 会将空属性视为缺失值。这意味着 JSON 无法用空值覆盖低阶属性源中的属性。

外部应用程序属性

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

  1. 来自类路径
    1. 类路径根目录
    2. 类路径 /config
  2. 从当前目录
    1. 当前目录
    2. 当前目录中的 config/ 子目录
    3. config/ 子目录的直接子目录

该列表按优先级排序(较低项目的值优先于较早项目的值)。加载文件中的文档将作为 PropertySources 添加到 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 中指定的文件将被直接导入。

目录和文件位置值也会扩展,以检查特定配置文件。例如,如果 spring.config.locationclasspath:myconfig.properties,则也会加载相应的 classpath:myconfig-.properties 文件。

在大多数情况下,你添加的每个 spring.config.location 项都将引用一个文件或目录。位置按照定义的顺序进行处理,后面的位置可以覆盖前面位置的值。
如果您有复杂的位置设置,并且使用了特定于配置文件的配置文件,您可能需要提供进一步的提示,以便 Spring Boot 知道应该如何对它们进行分组。位置组是在同一级别考虑的位置的集合。例如,您可能想分组所有 classpath 位置,然后是所有外部位置。位置组内的项目应以 ; 分隔。更多详情,请参阅 "配置文件特定文件 "部分的示例。
使用 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(...) 或系统/环境变量设置忽略值。

通配符位置

如果配置文件位置的最后一个路径段包含 * 字符,则视为通配符位置。通配符会在加载配置时展开,因此直接子目录也会被检查。在 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 属性使用通配符位置。

通配符位置必须只包含一个 *,搜索目录位置时以 */ 结尾,搜索文件位置时以 */ 结尾。带有通配符的位置会根据文件名的绝对路径按字母顺序排序。

通配符位置只适用于外部目录。不能在 classpath: 位置中使用通配符。

特定配置文件

除了应用程序属性文件外,Spring Boot 还会尝试使用命名约定 application-{profile} 加载特定于配置文件的文件。例如,如果您的应用程序激活了名为 prod 的配置文件并使用 YAML 文件,那么 application.yamlapplication-prod.yaml 都将被考虑。
特定配置文件属性的加载位置与标准 application.properties 相同,特定配置文件总是优先于非特定文件。如果指定了多个配置文件,则采用后胜策略。例如,如果通过 spring.profiles.active 属性指定了配置文件 prod、liveapplication-prod.properties 中的值就会被 application-live.properties 中的值覆盖。

后胜策略适用于位置组级别。spring.config.locationclasspath:/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

环境有一组默认配置文件(默认为 [default]),如果没有设置激活的配置文件,就会使用这些配置文件。换句话说,如果没有明确激活任何配置文件,则会考虑application-default配置文件中的属性。

属性文件只加载一次。如果您已经直接导入了某个配置文件的特定属性文件,则不会再导入第二次。

导入附加数据

应用程序属性可使用 spring.config.import 属性从其他位置导入更多配置数据。导入会在被发现时进行处理,并被视为紧接在声明导入的文件下方插入的附加文件。
例如,在你的类路径下 application.properties 文件中可能有以下内容:

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

这将触发导入当前目录下的 dev.properties 文件(如果存在此类文件)。导入的 dev.properties 中的值将优先于触发导入的文件。在上例中,dev.properties 可以将 spring.application.name 重新定义为不同的值。
无论导入声明多少次,都只能导入一次。导入在 properties/yaml 文件的单个文档中定义的顺序并不重要。例如,下面两个示例产生的结果是一样的:

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

在上述两个示例中,my.properties 文件中的值优先于触发导入的文件。
可在单个 spring.config.import 关键字下指定多个位置。位置将按照定义的顺序进行处理,后导入的位置优先。

在适当情况下,也会考虑导入特定配置文件的变量。上例将同时导入 my.properties 和任何 my-.properties 变体。

Spring Boot 包含可插拔的 API,允许支持各种不同的位置地址。默认情况下,您可以导入 Java 属性、YAML 和 “配置树”。
第三方 jar 可以提供对其他技术的支持(不要求文件是本地的)。例如,你可以想象配置数据来自外部存储,如 Consul、Apache ZooKeeper 或 Netflix Archaius。
如果您想支持自己的位置,请参阅 org.springframework.boot.context.config 包中的 ConfigDataLocationResolverConfigDataLoader 类。

导入无扩展名的文件

某些云平台无法为卷加载的文件添加文件扩展名。要导入这些无扩展名的文件,需要给 Spring Boot 一个提示,以便它知道如何加载这些文件。为此,您可以将扩展名提示放在方括号中。
例如,假设您有一个 /etc/config/myconfig 文件,希望以 yaml 格式导入。你可以使用下面的方法从 application.properties 中导入该文件:

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

使用配置树

在云平台(如 Kubernetes)上运行应用程序时,您经常需要读取平台提供的配置值。为此目的使用环境变量的情况并不少见,但这样做也有缺点,尤其是当配置值需要保密时。
作为环境变量的替代方案,许多云平台现在都允许将配置映射到挂载的数据卷中。例如,Kubernetes 可以将[ConfigMaps](https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/#populate-a-volume-with-data-stored-in-a-configmap)[Secrets](https://kubernetes.io/docs/concepts/configuration/secret/#using-secrets-as-files-from-a-pod) 卷挂载。
有两种常见的卷挂载模式可供选择:

  1. 单个文件包含一整套属性(通常写成 YAML)。
  2. 多个文件被写入一个目录树,文件名成为 “key”,内容成为 “value”。

对于第一种情况,可以如上所述使用 spring.config.import 直接导入 YAML 或 Properties 文件。对于第二种情况,您需要使用 configtree: 前缀,以便 Spring Boot 知道它需要将所有文件作为属性公开。
举个例子,假设 Kubernetes 挂载了以下卷:

etc/
  config/
    myapp/
      username
      password

username文件的内容将是一个配置值,password的内容将是一个秘密。
要导入这些属性,可以在 application.propertiesapplication.yaml 文件中添加以下内容:

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

然后,您就可以通过常规方式从环境中访问或注入 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/*/ 作为导入位置:

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

这将添加 db.usernamedb.passwordmq.usernamemq.password 属性。

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

配置树也可用于 Docker 秘密。当 Docker swarm 服务获准访问某个秘密时,该秘密就会被挂载到容器中。例如,如果名为 db.password 的秘密被挂载到 /run/secrets/ 位置,你就可以使用下面的方法让 Spring 环境访问 db.password

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

属性占位符

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

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 属性的 "简短 "变体。详情请参阅 howto.html 方法。

使用多文档文件

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 注解无法加载多文档属性文件。

激活属性

有时,只有在满足特定条件时才激活特定的属性集是非常有用的。例如,你可能有一些属性只有在特定配置文件处于激活状态时才相关。
你可以使用 spring.config.activate.* 有条件地激活属性文档。
以下激活属性可用:

Property Note
on-profile 配置文件表达式,必须匹配该表达式,文档才会激活。
on-cloud-platform 文档激活时必须检测到的云平台。

例如,下面指定第二个文档只有在 Kubernetes 上运行时才会激活,而且只有在 "prod "或 "staging "配置文件激活时才会激活:

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

加密属性

Spring Boot 不提供任何用于加密属性值的内置支持,但它提供了修改 Spring Environment 中所含值所需的钩点。EnvironmentPostProcessor 接口允许您在应用程序启动前操作环境。详见 howto.htm
如果您需要一种安全的方式来存储凭证和密码,Spring Cloud Vault 项目支持在 HashiCorp Vault 中存储外部化配置。

使用 YAML

YAML 是 JSON 的超集,因此是指定分层配置数据的便捷格式。只要你的类路径上有 SnakeYAML 库,SpringApplication 类就会自动支持 YAML 作为属性的替代。

如果使用 “Starters”,Spring-boot-starter 会自动提供 SnakeYAML。

映射 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 文件。因此,如果需要以这种方式加载值,则需要使用属性文件。

直接加载 YAML

Spring Framework 提供了两个方便的类,可用于加载 YAML 文档。YamlPropertiesFactoryBean 将 YAML 作为属性加载,而 YamlMapFactoryBean 将 YAML 作为Map加载。
如果您想将 YAML 作为 Spring PropertySource 加载,还可以使用 YamlPropertySourceLoader 类。

配置随机值

RandomValuePropertySource 用于注入随机值(例如,注入到秘密或测试用例中)。它可以产生整数、longs、uuids 或字符串,如下例所示:

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 上设置。
例如,如果将前缀设置为 inputremote.timeout 等属性也将在系统环境中解析为 input.remote.timeout

类型安全的配置属性

使用 @Value("${property}")注解注入配置属性有时会很麻烦,尤其是在处理多个属性或数据具有层次性的情况下。Spring Boot 提供了另一种处理属性的方法,让强类型 Bean 管理和验证应用程序的配置。

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

JavaBean 属性绑定

如下例所示,可以绑定一个声明了标准 JavaBean 属性的 Bean:

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

    private boolean enabled;

    private InetAddress remoteAddress;

    private final Security security = new Security();

    // getters / setters...

    public static class Security {

        private String username;

        private String password;

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

        // getters / setters...

    }

}

上面的 POJO 定义了以下属性:

  • my.service.enabled,默认值为 false
  • my.service.remote-address(远程地址),其类型可从字符串中提取。
  • my.service.security.username,嵌套 "security "对象,其名称由属性名称决定。特别要指出的是,这里根本没有使用类型,可能是 SecurityProperties
  • my.service.security.password
  • my.service.security.roles 包含一个默认为 USER 的字符串集合。

映射到 Spring Boot 中可用的 @ConfigurationProperties 类的属性(通过属性文件、YAML 文件、环境变量和其他机制进行配置)是公共 API,但类本身的访问器(getters/setters)不能直接使用。

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

  • Map(只要已初始化)需要 getter,但不一定需要 setter,因为绑定器可以更改它们。
  • 可以通过索引(通常使用 YAML)或使用单个逗号分隔的值(属性)来访问集合和数组。在后一种情况下,必须使用setter。我们建议始终为此类类型添加一个设置器。如果要初始化一个集合,请确保它不是不可变的(如前面的示例)。
  • 如果嵌套的 POJO 属性被初始化(如上例中的 Security 字段),则不需要setter。如果希望Binder使用默认构造函数即时创建实例,则需要使用setter

有些人使用 Project Lombok 自动添加gettersetter。请确保 Lombok 不会为这种类型生成任何特定的构造函数,因为容器会自动使用它来实例化对象。

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

构造函数绑定

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

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

    // fields...

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

    // getters...

    public static class Security {

        // fields...

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

        // getters...

    }

}

在此设置中,单个参数化构造函数的存在意味着应使用构造函数绑定。这意味着绑定器将找到一个带有您希望绑定的参数的构造函数。如果你的类有多个构造函数,可以使用 @ConstructorBinding 注解来指定使用哪个构造函数进行构造函数绑定。如果类只有一个参数化的构造函数,则必须使用 @Autowired 注解才能退出构造函数绑定。构造函数绑定可用于记录。除非您的记录有多个构造函数,否则没有必要使用 @ConstructorBinding
构造函数绑定类(如上例中的 Security)的嵌套成员也将通过其构造函数绑定。
可以在构造函数参数和记录组件上使用 @DefaultValue 指定默认值。转换服务将用于将注解的字符串值强制转换为缺失属性的目标类型。
参考前面的示例,如果没有任何属性绑定到 SecurityMyProperties 实例将包含一个null值的 Security 。要使 MyProperties 实例在没有绑定任何属性的情况下也包含一个非空的 Security 实例(在使用 Kotlin 时,这需要将 Securityusernamepassword参数声明为可空,因为它们没有默认值),请使用空的 @DefaultValue 注解:

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

要使用构造函数绑定,必须使用 @EnableConfigurationProperties 或配置属性扫描启用该类。对于通过常规 Spring 机制创建的 Bean(例如 @Component Bean、通过 @Bean 方法创建的 Bean 或通过 @Import 加载的 Bean),您不能使用构造函数绑定。

要在本地镜像中使用构造函数绑定,必须使用 -parameters 对类进行编译。如果使用 Spring Boot 的 Gradle 插件,或者使用 Maven 和 spring-boot-starter-parent,编译会自动进行。

不建议将 java.util.Optional@ConfigurationProperties 结合使用,因为它主要是用作返回类型。因此,它并不适合配置属性注入。为了与其他类型的属性保持一致,如果您确实声明了一个 Optional 属性,但它没有值,那么绑定的将是 null 而不是空的 Optional

启用 @ConfigurationProperties

Spring Boot 提供了绑定 @ConfigurationProperties 类型并将其注册为 Bean 的基础架构。您既可以按类启用配置属性,也可以启用配置属性扫描,其工作方式与组件扫描类似。
有时,使用 @ConfigurationProperties 注解的类可能不适合扫描,例如,如果您正在开发自己的自动配置,或者您想有条件地启用它们。在这种情况下,请使用 @EnableConfigurationProperties 注解指定要处理的类型列表。这可以在任何 @Configuration 类上完成,如下例所示:

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.ConfigurationProperties;

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

}

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

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

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

}

当使用配置属性扫描或通过 @EnableConfigurationProperties 注册 @ConfigurationProperties Bean 时,该 Bean 具有一个常规名称: -,其中 @ConfigurationProperties 注解中指定的环境键前缀, 是 bean 的完全限定名称。如果注解没有提供任何前缀,则只使用 bean 的完整限定名称。
假设它位于 com.example.app 包中,则上述 SomeProperties 示例的 Bean 名称为 some.properties-com.example.app.SomeProperties

我们建议 @ConfigurationProperties 只处理环境,尤其不要注入上下文中的其他 Bean。在某些情况下,可以使用setter注入或框架提供的任何 *Aware 接口(如需要访问环境时使用 EnvironmentAware)。如果您仍想使用构造函数注入其他 Bean,则必须使用 @Component 对配置属性 Bean 进行注解,并使用基于 JavaBean 的属性绑定。

使用 @ConfigurationProperties

这种配置方式与 SpringApplication 外部 YAML 配置配合使用效果尤佳,如下例所示:

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

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

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();
        // ...
    }

    // ...

}

使用 @ConfigurationProperties 还可以生成元数据文件,供集成开发环境使用,为自己的键提供自动完成功能。详情请参见附录。

第三方配置

除了使用 @ConfigurationProperties 对类进行注解外,您还可以在公共 @Bean 方法中使用它。当您想将属性绑定到您无法控制的第三方组件时,这样做会特别有用。
要从环境属性配置 Bean,请在其 Bean 注册中添加 @ConfigurationProperties,如下例所示:

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();
    }

}

任何以 another 前缀定义的 JavaBean 属性都会映射到 AnotherComponent Bean 上,映射方式与前面的 SomeProperties 示例类似。

宽松绑定

Spring Boot 在将Environment属性绑定到 @ConfigurationProperties Bean 时使用了一些宽松的规则,因此Environment属性名称和 Bean 属性名称之间无需完全匹配。常见的有用例子包括以破折号分隔的环境属性(例如,context-path 绑定到 contextPath)和大写的环境属性(例如,PORT 绑定到 port)。
举例来说,请看下面的 @ConfigurationProperties 类:

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;
    }

}

通过前面的代码,可以使用以下所有属性名称:

Property Note
my.main-project.person.first-name Kebab-case,建议在 .properties 和 YAML 文件中使用。
my.main-project.person.firstName 标准驼峰式语法
my.main-project.person.first_name 下划线符号,是 .properties 和 YAML 文件的另一种格式。
MY_MAINPROJECT_PERSON_FIRSTNAME 大写格式,建议在使用系统环境变量时使用。

注解的前缀值必须使用 kebab-case 写(小写并用 - 分隔,如 my.main-project.person)。

Property Source Simple List
Properties Files 驼峰格式、破折号或下划线连接 使用 [ ] 或逗号分隔值的标准列表语法
YAML Files 驼峰格式、破折号或下划线连接 标准 YAML 列表语法或用逗号分隔的值
Environment Variables 大写格式,下划线为分隔符(请参阅 “从环境变量绑定”)。 用下划线连起来的数值(请参阅从环境变量绑定)。
System properties 驼峰格式、破折号或下划线连接 使用 [ ] 或逗号分隔值的标准列表语法

我们建议,在可能的情况下,属性以减号连接的格式存储,例如 my.person.first-name=Rod

绑定Map

绑定属性到 Map 时,可能需要使用特殊的括号符号,以便保留原始key值。如果键值没有用[]包围,任何非字母数字、-.的字符都会被删除。
例如,将以下属性绑定到 Map 中:

my:
  map:
    "[/key1]": "value1"
    "[/key2]": "value2"
    "/key3": "value3"

对于 YAML 文件,括号需要用引号包围,这样才能正确解析键值。

上述属性将绑定到一个MapMap中的键为 /key1/key2key3。由于 key3 没有被方括号包围,因此删除了其中的斜线。
绑定到标量值时,带有 .的键无需用 [] 包围。标量值包括枚举和 java.lang 包中除 Object 之外的所有类型。将 a.b=c 绑定到 Map 将保留键中的 .,并返回一个包含 {"a.b"="c"} 条目的 Map。对于其他类型,如果键包含 .,则需要使用括号符号。例如,将 a.b=c 绑定到 Map 将返回包含 {"a"={"b"="c"}} 条目的 Map,而 [a.b]=c 将返回包含 {"a.b"="c"} 条目的 Map。

从环境变量绑定

大多数操作系统对环境变量的名称都有严格规定。例如,Linux shell 变量只能包含字母(azAZ)、数字(09)或下划线字符 (_)。按照惯例,Unix shell 变量的名称也是大写的。
Spring Boot 的宽松绑定规则尽可能与这些命名限制兼容。
要将规范格式的属性名转换为环境变量名,可以遵循以下规则:

  • 用下划线 (_) 代替点 (.) 。
  • 删除任何破折号 (-)。
  • 转换为大写字母。

例如,配置属性 spring.main.log-startup-info 就是一个名为 SPRING_MAIN_LOGSTARTUPINFO 的环境变量。
在绑定对象列表时,也可以使用环境变量。要绑定到列表,应在变量名中用下划线括起元素编号。
例如,配置属性 my.service[0].other 将使用名为 MY_SERVICE_0_OTHER 的环境变量。

缓存

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

合并复杂类型

当列表配置在多个地方时,覆盖的作用是替换整个列表。
例如,假设 MyPojo 对象的 namedescription 属性默认为空。下面的示例从 MyProperties 中公开了一个 MyPojo 对象列表:

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;
    }

}

请考虑以下配置:

my:
  list:
  - name: "my name"
    description: "my description"
---
spring:
  config:
    activate:
      on-profile: "dev"
my:
  list:
  - name: "my another name"

如果dev配置文件未激活,MyProperties.list 会包含一个 MyPojo 条目,如前所述。但如果启用了dev配置文件,列表中仍然只有一个条目(名称为my another name,描述为null)。这种配置不会在列表中添加第二个 MyPojo 实例,也不会合并项目。
如果在多个配置文件中指定了一个列表,则会使用优先级最高的配置文件(也只有该配置文件)。请看下面的例子:

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配置文件处于活动状态,MyProperties.list 将包含一个 MyPojo 条目(名称为my another name,描述为null)。对于 YAML,逗号分隔列表和 YAML 列表都可用于完全覆盖列表内容。
对于 Map 属性,可以绑定从多个来源获取的属性值。不过,对于多个来源中的同一属性,将使用优先级最高的那个。下面的示例公开了来自 MyPropertiesMap

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;
    }

}

请考虑以下配置:

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配置文件未激活,MyProperties.map 会包含一个 key1 条目(名称为my name 1,描述为my description 1)。但如果启用了dev配置文件,map 会包含两个条目,键值分别为 key1(名称为dev name 1,描述为my description 1)和 key2(名称为dev name 2,描述为dev description 2)。

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

属性转换

Spring Boot 在绑定 @ConfigurationProperties Bean 时,会尝试将外部应用程序属性强制转换为正确的类型。如果需要自定义类型转换,可以提供 ConversionService Bean(使用名为 conversionService 的 Bean)或自定义属性编辑器(通过 CustomEditorConfigurer Bean)或自定义转换器(使用注释为 @ConfigurationPropertiesBinding 的 Bean 定义)。
由于此 Bean 在应用程序生命周期的早期就会被请求,因此请确保限制 ConversionService 使用的依赖关系。通常情况下,您所需要的任何依赖关系在创建时可能尚未完全初始化。如果配置键强制不需要自定义 ConversionService,您可能需要将其重命名,并且只依赖于使用 @ConfigurationPropertiesBinding 限定的自定义转换器。

转换Duration

Spring Boot 为表达Duration提供了专门支持。如果暴露了 java.time.Duration 属性,应用程序属性中的以下格式就可用:

  • 常规长表示法(使用毫秒作为默认单位,除非指定了 @DurationUnit
  • java.time.Duration 使用的标准 ISO-8601 格式
  • 一种更易读的格式,其中值和单位是耦合的(10s 表示 10 秒)

请看下面这个例子:

@ConfigurationProperties("my")
public class MyProperties {

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

    private Duration readTimeout = Duration.ofMillis(1000);

    // getters / setters...

}

要指定会话超时 30 秒,30PT30S30s 都是等价的。读取超时 500 毫秒可以用以下任何一种形式指定: 500PT0.5S500ms
您也可以使用任何支持的单位。它们是:

  • ns 代表纳秒
  • us 代表微秒
  • ms 代表毫秒
  • s 代表秒
  • m 代表分钟
  • h 代表小时
  • d 代表天

默认单位是毫秒,可使用 @DurationUnit 进行重载,如上例所示。
如果您喜欢使用构造函数绑定,也可以公开相同的属性,如下例所示:

@ConfigurationProperties("my")
public class MyProperties {

    // fields...

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

    // getters...

}

如果要升级长属性,请确保定义单位(使用 @DurationUnit)(如果不是毫秒)。这样可以提供一个透明的升级路径,同时支持更丰富的格式。

转换Period

除了Duration,Spring Boot 还可以使用 java.time.Period 类型。应用程序属性中可使用以下格式:

  • 常规 int 表示法(使用天数作为默认单位,除非指定了 @PeriodUnit
  • java.time.Period 使用的标准 ISO-8601 格式
  • 一种更简单的格式,其中值和单位对是耦合的(1y3d 表示 1 年零 3 天)

使用简单格式可支持以下单位:

  • y 代表年
  • m 代表月
  • w 代表周
  • d 代表天数

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

转换DataSize

Spring Framework 有一种 DataSize 值类型,以字节为单位表示大小。如果公开 DataSize 属性,应用程序属性中的以下格式将可用:

  • 常规的长表示法(使用字节作为默认单位,除非指定了 @DataSizeUnit
  • 可读性更强的格式,其中值和单位是耦合的(10MB 表示 10 兆字节)

请看下面的示例:

@ConfigurationProperties("my")
public class MyProperties {

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

    private DataSize sizeThreshold = DataSize.ofBytes(512);

    // getters/setters...

}

要指定 10 兆字节的缓冲区大小,1010MB 是等价的。256 字节的大小阈值可以指定为 256256B
您也可以使用任何支持的单位。这些单位是:

  • B 代表字节
  • KB 代表千字节
  • MB 代表兆字节
  • GB 代表千兆字节
  • TB 代表太字节

默认单位是字节,可使用 @DataSizeUnit 进行重载,如上例所示。
如果您喜欢使用构造函数绑定,也可以公开相同的属性,如下例所示:

@ConfigurationProperties("my")
public class MyProperties {

    // fields...

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

    // getters...

}

如果要升级长属性,请确保定义单位(使用 @DataSizeUnit)(如果不是字节)。这样可以提供透明的升级路径,同时支持更丰富的格式。

@ConfigurationProperties 验证

只要 @ConfigurationProperties 类使用了 Spring 的 @Validated 注解,Spring Boot 就会尝试对其进行验证。您可以直接在配置类上使用 JSR-303 jakarta.validation 约束注解。为此,请确保在类路径上有符合要求的 JSR-303 实现,然后在字段中添加约束注解,如下例所示:

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

    @NotNull
    private InetAddress remoteAddress;

    // getters/setters...

}

您还可以通过在创建配置属性的 @Bean 方法中注释 @Validated 来触发验证。

为确保嵌套属性始终触发验证,即使没有找到任何属性,也必须用 @Valid 对相关字段进行注解。下面的示例以前面的 MyProperties 示例为基础:

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

    @NotNull
    private InetAddress remoteAddress;

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

    // getters/setters...

    public static class Security {

        @NotEmpty
        private String username;

        // getters/setters...

    }

}

您还可以通过创建名为 configurationPropertiesValidator 的 Bean 定义来添加自定义 Spring 验证器。@Bean 方法应声明为静态。配置属性验证器是在应用程序生命周期的早期创建的,将 @Bean 方法声明为静态方法可让 Bean 在创建时无需实例化 @Configuration 类。这样做可以避免提前实例化可能导致的任何问题。

spring-boot-actuator 模块包含一个可公开所有 @ConfigurationProperties Bean 的端点。将网络浏览器指向 /actuator/configprops,或使用相应的 JMX 端点。有关详情,请参阅 "生产就绪功能 "部分。

@ConfigurationProperties 对比 @Value

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

Feature @ConfigurationProperties @Value
宽松绑定 Yes 支持有限(见下方说明)
元数据支持 Yes No
SpEL 评估 No Yes

如果您确实想使用 @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

如果您为自己的组件定义了一组配置键,我们建议您将它们组合到一个注有 @ConfigurationProperties 的 POJO 中。这样做将为您提供结构化、类型安全的对象,您可以将其注入到自己的 Bean 中。
在解析应用程序属性文件和填充环境时,不会处理这些文件中的 SpEL 表达式。不过,可以在 @Value 中写入 SpEL 表达式。如果应用程序属性文件中的属性值是 SpEL 表达式,则在通过 @Value 消耗时将对其进行评估。

你可能感兴趣的:(spring,boot,spring,boot,后端,java)