外部化配置

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

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

Spring Boot 使用一个非常特殊的 PropertySource 顺序,该顺序旨在允许合理地覆盖值。 后面的属性源可以覆盖前面定义的属性值。

按以下顺序考虑配置源:

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

  2. @PropertySource 注解在你的 @Configuration 类上。 请注意,在应用程序上下文被刷新之前,这些属性源不会被添加到 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 中的 特定于配置文件的应用程序属性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,如下例所示:

  • 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 中指定的文件将直接导入。

注意:目录和文件位置值也会被扩展以检查 特定于配置文件的文件。 例如,如果你有 spring.config.locationclasspath:myconfig.properties,你还会发现适当的 classpath:myconfig-<profile>.properties 文件被加载。

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

如果你有复杂的位置设置,并且使用特定于配置文件的配置文件,你可能需要提供进一步的提示,以便 Spring Boot 知道它们应该如何分组。 位置组是同时考虑的所有位置的集合。 例如,你可能想要先分组所有类路径位置,然后分组所有外部位置。 位置组内的项目应该用 ; 分隔。 有关更多详细信息,请参阅 特定于配置文件的文件 部分中的示例。

使用 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: 位置中使用通配符。

特定于配置文件的文件

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

特定于配置文件的属性从与标准 application.properties 相同的位置加载,特定于配置文件的文件始终覆盖非特定文件。 如果指定了多个配置文件,则应用最后获胜策略。 例如,如果通过 spring.profiles.active 属性指定了配置文件 prod,live,则 application-prod.properties 中的值可以被 application-live.properties 中的值覆盖。

最后获胜策略在 位置组级别应用。 classpath:/cfg/,classpath:/ext/spring.config.locationclasspath:/cfg/;classpath:/ext/ 不具有相同的覆盖规则。

例如,继续我们上面的 prod,live 示例,我们可能有以下文件:

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

当我们有 classpath:/cfg/,classpath:/ext/spring.config.location 时,我们在处理所有 /ext 文件之前处理所有 /cfg 文件:

  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 有一组默认配置文件(默认为 [default]),如果未设置活动配置文件,则使用这些配置文件。 换句话说,如果没有显式激活配置文件,则考虑来自 application-default 的属性。

注意:属性文件只加载一次。 如果你已经直接 导入了特定于配置文件的属性文件,则不会再次导入它。

导入其他数据

应用程序属性可以使用 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 重新定义为不同的值。

无论声明多少次,导入都只会导入一次。

使用"固定"和"导入相对"位置

导入可以指定为_固定_或_导入相对_位置。 固定位置始终解析为相同的基础资源,无论 spring.config.import 属性在哪里声明。 导入相对位置相对于声明 spring.config.import 属性的文件进行解析。

以正斜杠(/)或 URL 样式前缀(file:classpath: 等)开头的位置被视为固定位置。 所有其他位置都被视为导入相对位置。

注意:在确定位置是固定还是导入相对时,不考虑 optional: 前缀。

例如,假设我们有一个包含 application.jar 文件的 /demo 目录。 我们可能添加一个具有以下内容的 /demo/application.properties 文件:

spring.config.import=optional:core/core.properties

这是一个导入相对位置,因此如果存在,它将尝试加载文件 /demo/core/core.properties

如果 /demo/core/core.properties 具有以下内容:

spring.config.import=optional:extra/extra.properties

它将尝试加载 /demo/core/extra/extra.propertiesoptional:extra/extra.properties 相对于 /demo/core/core.properties,所以完整目录是 /demo/core/ + extra/extra.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 键下指定多个位置。 位置将按定义的顺序处理,后面的导入优先。

注意:在适当的情况下,也会考虑 特定于配置文件的变体进行导入。 上面的例子将导入 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)上运行应用程序时,你通常需要读取平台提供的配置值。 你可以为此使用环境变量,或者你可以使用 配置树

你甚至可以在(多行)环境变量中以属性或 yaml 格式存储整个配置,并使用 env: 前缀加载它们。 假设有一个名为 MY_CONFIGURATION 的环境变量,其内容如下:

my.name=Service1
my.cluster=Cluster1

使用 env: 前缀,可以从这个变量中导入所有属性:

  • Properties

  • YAML

spring.config.import=env:MY_CONFIGURATION
spring:
  config:
    import: "env:MY_CONFIGURATION"

提示:此功能还支持 指定扩展名。 默认扩展名是 .properties

使用配置树

在环境变量中存储配置值有一些缺点,特别是当值需要保密时。

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

可以使用两种常见的卷挂载模式:

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

  2. 多个文件写入到目录树中,文件名成为 ‘key’,内容成为 ‘value’。

对于第一种情况,你可以直接使用 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.usernamedb.passwordmq.usernamemq.password 属性。

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

配置树也可以用于 Docker secrets。 当 Docker swarm 服务被授予访问密钥的权限时,密钥会被挂载到容器中。 例如,如果一个名为 db.password 的密钥挂载在 /run/secrets/ 位置,你可以使用以下方式使 db.password 在 Spring 环境中可用:

  • 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 属性的"`short`"变体。 有关详细信息,请参阅"`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 注解来加载。

激活属性

有时,只有在满足某些条件时才激活给定的属性集很有用。 例如,你可能有一些属性只在特定配置文件激活时才相关。

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

以下激活属性可用:

Table 1. activation properties
Property Note

on-profile

必须匹配才能使文档激活的配置文件表达式,或者至少一个必须匹配才能使文档激活的配置文件表达式列表。

on-cloud-platform

必须检测到的 CloudPlatform,才能使文档激活。

例如,以下指定第二个文档仅在运行在 Kubernetes 上且仅当 “prod” 或 “staging” 配置文件激活时才激活:

  • 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

如果你需要一种安全的方式来存储凭据和密码,https://cloud.spring.io/spring-cloud-vault/[Spring Cloud Vault] 项目提供了在 HashiCorp Vault 中存储外部化配置的支持。

使用 YAML

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

注意:如果你使用 starters,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 对象。 有关更多详细信息,请参阅下面的 类型安全的配置属性 部分。

警告:YAML 文件不能通过使用 @PropertySource@TestPropertySource 注解来加载。 因此,如果你需要以这种方式加载值,你需要使用 properties 文件。

直接加载 YAML

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

如果你想将 YAML 加载为 Spring PropertySource,你也可以使用 YamlPropertySourceLoader 类。

配置随机值

RandomValuePropertySource 对于注入随机值(例如,到密钥或测试用例中)很有用。 它可以生成整数、长整数、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 应用程序共享,这很有用。 系统环境属性的前缀可以通过在应用程序运行之前调用 setEnvironmentPrefix(…​) 方法直接在 SpringApplication 上设置。

例如,如果你将前缀设置为 input,则 remote.timeout 等属性将在系统环境中解析为 INPUT_REMOTE_TIMEOUT

注意:前缀_仅_适用于系统环境属性。 上面的示例在从其他源读取属性时将继续使用 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 注解。

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

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

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

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

  • 如果嵌套的 POJO 属性被初始化(如前面示例中的 Security 字段),则不需要 setter。 如果你希望绑定器使用其默认构造函数动态创建实例,则需要 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>)

}

在这种设置中,单个参数化构造函数的存在意味着应该使用构造函数绑定。 这意味着绑定器将找到具有你想要绑定的参数的构造函数。 如果你的类有多个构造函数,可以使用 @ConstructorBinding 注解来指定用于构造函数绑定的构造函数。

要选择退出类的构造函数绑定,参数化构造函数必须用 @Autowired 注解或设为 private。 Kotlin 开发人员可以使用空的主构造函数来选择退出构造函数绑定。

例如:

  • Java

  • Kotlin

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties("my")
public class MyProperties {

	// fields...

	final MyBean myBean;

	private String name;


	@Autowired
	public MyProperties(MyBean myBean) {
		this.myBean = myBean;
	}

	// getters / setters...


	public String getName() {
		return this.name;
	}

	public void setName(String name) {
		this.name = name;
	}


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

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

	constructor(name: String) : this() {
		this.name = name
	}

	// vars...

	var name: String? = null

}

构造函数绑定可以与记录一起使用。 除非你的记录有多个构造函数,否则不需要使用 @ConstructorBinding

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

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

参考前面的示例,如果没有属性绑定到 SecurityMyProperties 实例将包含 securitynull 值。 要使它在没有属性绑定到它时也包含 Security 的非空实例(使用 Kotlin 时,这将要求 Securityusernamepassword 参数声明为可空,因为它们没有默认值),使用空的 @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 一起使用,因为它主要用于作为返回类型。 因此,它不适合配置属性注入。 为了与其他类型的属性保持一致,如果你确实声明了 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 beans,你可以像使用任何其他 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 也可以让你生成元数据文件,这些文件可以被 IDEs 用于为你的自定义键提供自动完成功能。 有关详细信息,请参阅 附录

第三方配置

除了使用 @ConfigurationProperties 注释类,你还可以在公共 @Bean 方法上使用它。 这样做可以特别有用,当你想要将属性绑定到你控制范围之外的第三方组件时。

要配置 Environment 属性中的 bean,请在 bean 注册中添加 @ConfigurationProperties,如下例所示:

  • 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("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("another")
	fun anotherComponent(): AnotherComponent = AnotherComponent()

}

任何 JavaBean 属性定义为 another 前缀的都映射到 AnotherComponent bean,与前面 SomeProperties 示例类似。

宽松绑定

Spring Boot 使用一些宽松的规则来绑定 Environment 属性到 @ConfigurationProperties beans,所以不需要 Environment 属性名称和 bean 属性名称之间有精确匹配。 常见用例包括 dash-separated 环境属性(例如,context-path 绑定到 contextPath)和 capitalized 环境属性(例如,PORT 绑定到 port)。

例如,考虑以下 @ConfigurationProperties 类:

  • Java

  • Kotlin

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

@ConfigurationProperties("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("my.main-project.person")
class MyPersonProperties {

	var firstName: String? = null

}

使用前面的代码,以下属性名称都可以使用:

Table 2. relaxed binding
Property Note

my.main-project.person.first-name

Kebab case,推荐用于 .properties 和 YAML 文件。

my.main-project.person.firstName

标准 camel case 语法。

my.main-project.person.first_name

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

MY_MAINPROJECT_PERSON_FIRSTNAME

大写格式,推荐用于系统环境变量。

注意:注解的 prefix 值_必须_是小写 kebab 格式(小写并用 - 分隔,例如 my.main-project.person)。

Table 3. relaxed binding rules per property source
Property Source Simple List

Properties Files

Camel case, kebab case, or underscore notation

Standard list syntax using [ ] or comma-separated values

YAML Files

Camel case, kebab case, or underscore notation

Standard YAML list syntax or comma-separated values

Environment Variables

Upper case format with underscore as the delimiter (see 从环境变量绑定).

Numeric values surrounded by underscores (see 从环境变量绑定)

System properties

Camel case, kebab case, or underscore notation

Standard list syntax using [ ] or comma-separated values

提示:我们建议,当可能时,属性存储在小写的 kebab 格式中,例如 my.person.first-name=Rod

绑定 Maps

当绑定到 Map 属性时,你可能需要使用特殊的括号表示法,以便保留原始的 key 值。 如果 key 没有被 [] 包围,任何不是 alpha-numeric、-. 的字符都会被删除。

例如,考虑绑定以下属性到 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 文件,键需要用引号括起来才能正确解析。

上面的属性将绑定到 Map 中,其中 /key1/key2key3 是 map 中的键。 key3 中的斜杠已被删除,因为它没有被 [] 包围。

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

从环境变量绑定

大多数操作系统对环境变量的名称施加了严格的规则。 例如,Linux shell 变量只能包含字母 (a to z or A to Z)、数字 (0 to 9) 或下划线字符 (_)。 按照惯例,Unix shell 变量也将使用大写字母命名。

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

要将属性名称转换为环境变量名称,可以遵循以下规则:

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

  • 删除任何破折号 (-)。

  • 转换为大写。

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

环境变量也可以用于绑定到对象列表。 要绑定到 List,元素编号应该在变量名称中用下划线括起来。

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

支持从环境变量绑定到 systemEnvironment 属性源和任何以 -systemEnvironment 结尾的其他属性源。

从环境变量绑定 Maps

当 Spring Boot 将环境变量绑定到属性类时,它会在绑定之前将环境变量名称转换为小写。 大多数情况下,这无关紧要,除非绑定到 Map 属性。

Map 中的键总是小写的,如下例所示:

  • Java

  • Kotlin

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

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

@ConfigurationProperties("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("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 配置文件未激活,MyProperties.list 包含一个 MyPojo 条目,如前所述。 如果 dev 配置文件已激活,然而,list 仍然 只包含一个条目(名称 my another name 和描述 null)。 此配置 添加第二个 MyPojo 实例到列表中,也不合并项目。

List 在多个配置文件中指定时,优先级最高的一个(仅此一个)被使用。 考虑以下示例:

  • 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 配置文件已激活,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 配置文件未激活,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 beans 时将外部应用程序属性强制转换为正确的类型。 如果你需要自定义类型转换,你可以提供一个 ConversionService bean(带有名为 conversionService 的 bean)或自定义属性编辑器(通过 CustomEditorConfigurer bean)或自定义转换器(带有 @ConfigurationPropertiesBinding 注释的 bean 定义)。

用于属性转换的 beans 在应用程序生命周期的早期被请求,因此请确保限制 ConversionService 使用的依赖项。 通常,任何你需要的依赖项可能不会在创建时完全初始化。

提示:如果你不希望自定义 ConversionService 用于配置键强制转换,并且只依赖于带有 @ConfigurationPropertiesBinding 注释的合格转换器,你可以重命名你的自定义 ConversionService。 当使用 @ConfigurationPropertiesBinding 注释对 @Bean 方法进行资格时,方法应该是 static,以避免 “bean is not eligible for getting processed by all BeanPostProcessors” 警告。

转换持续时间

Spring Boot 有专门的支持来表达持续时间。 如果你暴露一个 Duration 属性,以下格式的应用程序属性是可用的:

  • 常规 long 表示(使用毫秒作为默认单位,除非 @DurationUnit 已指定)

  • 标准 ISO-8601 格式 used by 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),如果它不是毫秒。 这样做可以支持更丰富的格式,同时保持透明升级路径。

转换期间

除了持续时间,Spring Boot 还可以处理 Period 类型。 以下格式的应用程序属性是可用的:

  • 常规 int 表示(使用天作为默认单位,除非 @PeriodUnit 已指定)

  • 标准 ISO-8601 格式 used by Period

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

以下单位支持简单格式:

  • y 表示年

  • m 表示月

  • w 表示周

  • d 表示天

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

转换数据大小

Spring Framework 有一个 DataSize 值类型,表示以字节为单位的大小。 如果你暴露一个 DataSize 属性,以下格式的应用程序属性是可用的:

  • 常规 long 表示(使用字节作为默认单位,除非 @DataSizeUnit 已指定)

  • 一种更易读的格式,其中值和单位是耦合的(10MB 表示 10 MB)

考虑以下示例:

  • 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 MB 的缓冲区大小,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 验证

Spring Boot 尝试在 @ConfigurationProperties 类被 Spring 的 @Validated 注解注释时验证它们。 你可以直接在配置类上使用 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

}

提示:你还可以通过注解 @Bean 方法来触发验证,该方法创建配置属性,如下例所示:

  • 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 beans。 将你的 web 浏览器指向 /actuator/configprops 或使用等效的 JMX 端点。 有关详细信息,请参阅 生产就绪功能部分。

@ConfigurationProperties 与 @Value

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

Feature @ConfigurationProperties @Value

宽松绑定

Yes

Limited (see note below)

元数据支持

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 消耗时被评估。