24、外部化配置

Spring Boot 可以让您的配置外部化,以便可以在不同环境中使用相同的应用程序代码。您可以使用 properties 文件、YAML 文件、环境变量或者命令行参数来外部化配置。可以使用 @Value 注解将属性值直接注入到 bean 中,可通过 Spring 的 Environment 访问,或者通过 @ConfigurationProperties 绑定到结构化对象

Spring Boot 使用了一个非常特别的 PropertySource 指令,用于智能覆盖默认值。属性将按照以下顺序处理:

  1. 在您的主目录(当 devtools 被激活,则为 ~/.spring-boot-devtools.properties )中的 Devtools 全局设置属性
  2. 在测试中使用到的 @TestPropertySource 注解。
  3. 在测试中使用到的 properties 属性,可以是 @SpringBootTest用于测试应用程序某部分的测试注解
  4. 命令行参数。
  5. 来自 SPRING_APPLICATION_JSON 的属性(嵌入在环境变量或者系统属性【system propert】中的内联 JSON)。
  6. ServletConfig 初始化参数。
  7. ServletContext 初始化参数。
  8. 来自 java:comp/env 的 JNDI 属性。
  9. Java 系统属性(System.getProperties())。
  10. 操作系统环境变量。
  11. 只有 random.* 属性的 RandomValuePropertySource
  12. 在已打包的 jar 外部的指定 profile 的应用属性文件application-{profile}.properties 和 YAML 变量)。
  13. 在已打包的 jar 内部的指定 profile 的应用属性文件application-{profile}.properties 和 YAML 变量)。
  14. 在已打包的 jar 外部的应用属性文件(application.properties 和 YAML 变量)。
  15. 在已打包的 jar 内部的应用属性文件(application.properties 和 YAML 变量)。
  16. @Configuration 类上的 @PropertySource 注解。
  17. 默认属性(使用 SpringApplication.setDefaultProperties 指定)。

举个例子,假设开发的 @Component 使用了 name 属性,可以这样:

  1. import org.springframework.stereotype.*;
  2. import org.springframework.beans.factory.annotation.*;
  3. @Component
  4. public class MyBean {
  5. @Value("${name}")
  6. private String name;
  7. // ...
  8. }

在您的应用程序的 classpath 中(比如在 jar 中),您可以有一个 application.properties,它为 name 提供了一个合适的默认属性值。当在新环境中运行时,您可以在 jar 外面提供一个 application.properties 来覆盖 name。对于一次性测试,您可以使用命令行指定形式启动(比如 java -jar app.jar --name="Spring")。

提示

SPRING_APPLICATION_JSON 属性可以在命令行中提供一个环境变量。比如在 UN*X shell 中:$ SPRING_APPLICATION_JSON='{"acme":{"name":"test"}}' java -jar myapp.jar在此示例中,您可以在 Spring Environment 中使用 acme.name=test,也可以在系统属性(System property)中将 JSON 作为 spring.application.json 属性提供:$ java -Dspring.application.json='{"name":"test"}' -jar myapp.jar或者以命令行参数形式:$ java -jar myapp.jar --spring.application.json='{"name":"test"}'或者将 JSON 作为一个 JNDI 变量:java:comp/env/spring.application.json

24.1、配置随机值

RandomValuePropertySource 对于随机值注入非常有用(比如在保密场景或者测试用例中)。它可以产生 integer、long、uuid 和 string。如下示例:

  1. my.secret=${random.value}
  2. my.number=${random.int}
  3. my.bignumber=${random.long}
  4. my.uuid=${random.uuid}
  5. my.number.less.than.ten=${random.int(10)}
  6. my.number.in.range=${random.int[1024,65536]}

random.int* 语法为 OPEN value (,max) CLOSEOPEN,CLOSE 可为任意字符,value,max 为整数。如果使用了 maxvalue 则为最小值,max 为最大值。

24.2、访问命令行属性

默认情况下,SpringApplication 将所有命令行选项参数(即以 -- 开头的参数,比如 --server.port=9000)转换为属性,并将它们添加到 Spring Environment 中。如之前所述,命令行属性始终优先于其他属性源。

如果您不希望将命令行属性添加到 Environment,可以使用 SpringApplication.setAddCommandLineProperties(false) 来禁用它们。

24.3、应用程序属性文件

SpringApplication 从以下位置的 application.properties 文件中加载属性(properties),并将它们添加到 Spring Environment 中:

  1. 当前目录的 /config 子目录
  2. 当前目录
  3. classpath 上的 /config
  4. classpath 根路径

列表按序号优先级排序,序号越小,优先级越高。

注意

您还可以使用 YAML(.yml)文件来替代 .properties

如果您不喜欢 application.properties 作为配置文件名,则可以通过指定 spring.config.name 环境属性来切换到另一个文件名。您还可以使用 spring.config.location 环境属性来引用一个显式位置(以逗号分隔的目录位置或文件路径列表)。以下示例展示了如何指定其他文件名:

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

以下示例展示了如何指定两个位置:

  1. $ java -jar myproject.jar --spring.config.location=classpath:/default.properties,classpath:/override.properties

警告

spring.config.namespring.config.location 在程序启动早期就用来确定哪些文件必须加载,因此必须将它们定义为环境属性(通常是 OS 环境变量、系统属性或命令行参数)。

如果 spring.config.location 包含目录(而不是文件),则它们应该以 / 结尾(并且在运行期间,在加载之前追加从 spring.config.name 生成的名称,包括指定 profile 的文件名)。 spring.config.location 中指定的文件按原样使用,不支持指定 profile 形式,并且可被任何指定 profile 的文件的属性所覆盖。

配置位置以相反的顺序搜索。默认情况下,配置的位置为 classpath:/,classpath:/config/,file:./,file:./config/。生成的搜索顺序如下:

  1. file:./config/
  2. file:./
  3. classpath:/config/
  4. classpath:/

使用了 spring.config.location 配置自定义配置位置时,默认位置配置将被替代。例如,如果 spring.config.location 配置为 classpath:/custom-config/,file:./custom-config/,搜索顺序将变为以下:

  1. file:./custom-config/
  2. classpath:custom-config/

或者,当使用 spring.config.additional-location 配置自定义配置位置时,除了使用默认位置外,还会使用它们。这些其他(additional)位置将在默认位置之前搜索。例如,如果将其他位置配置为 classpath:/custom-config/,file:./custom-config/,则搜索顺序将变为以下内容:

  1. file:./custom-config/
  2. classpath:custom-config/
  3. file:./config/
  4. file:./
  5. classpath:/config/
  6. classpath:/

该搜索顺序允许您在一个配置文件中指定默认值,然后有选择地覆盖另一个配置文件中的值。您可以在 application.properties(或您使用 spring.config.name 指定的其他文件)中的某个默认位置为应用程序提供默认值。之后,在运行时,这些默认值将被自定义位置中的某个文件所覆盖。

注意

如果您使用的是环境变量而不是系统属性,大部分操作系统都不允许使用 . 分隔的键名,但您可以使用下划线来代替(例如,使用 SPRING_CONFIG_NAME 而不是 spring.config.name)。

注意

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

24.4、特定 Profile 的属性文件

application.properties 文件外,还可以使用以下命名约定定义特定 profile 的属性文件:application-{profile}.propertiesEnvironment 有一组默认配置文件(默认情况下为 default),如果未设置激活的(active)profile,则使用这些配置文件。换句话说,如果没有显式激活 profile,则会加载 application-default.properties 中的属性。

特定 profile 的属性文件从与标准 application.properties 相同的位置加载,特定 profile 的属性文件无论是否在打包的 jar 内部,都始终覆盖非特定文件。

如果指定了多个配置文件,则应用 last-wins 策略(优先采取最后一个)。例如,spring.profiles.active 属性指定的配置文件是在使用 SpringApplication API 配置的配置文件之后添加的,因此优先应用。

注意

如果在 spring.config.location 中指定了文件,则不考虑这些文件的特定 profile 形式。如果您还想使用特定 profile 的属性文件,请在 spring.config.location 中使用目录形式。

24.5、属性中的占位符

application.properties 中的值在使用时通过现有的 Environment 进行过滤,因此您可以返回之前定义的值(例如,从系统属性)。

  1. app.name=MyApp
  2. app.description=${app.name} is a Spring Boot application

提示

您还可以使用此技术创建现有 Spring Boot 属性的简短形式。有关详细信息,请参见第 77.4 章节:使用简短命令行参数

24.6、加密属性

Spring Boot 没有为加密属性值提供任何内置支持,然而,它提供了修改 Spring Environment 包含的值所必需的钩子。EnvironmentPostProcessor 接口允许您在应用程序启动之前操作 Environment。有关详细信息,请参见第 76.3 章节:在启动前自定义 Environment 或 ApplicationContext

如果您正在寻找一种可用于存储凭据和密码的安全方法,Spring Cloud Vault 项目支持在 HashiCorp Vault 中存储外部化配置。

24.7、使用 YAML 代替属性文件

YAML 是 JSON 的超集,是一个可用于指定层级配置数据的便捷格式。只要在 classpath 上有 SnakeYAML 库,SpringApplication 类就会自动支持 YAML 作为属性文件(properties)的替代。

注意

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

24.7.1、加载 YAML

Spring Framework 提供了两个便捷类,可用于加载 YAML 文档。YamlPropertiesFactoryBean 将 YAML 加载为 PropertiesYamlMapFactoryBean 将 YAML 加载为 Map

例如以下 YAML 文档:

  1. environments:
  2. dev:
  3. url: http://dev.example.com
  4. name: Developer Setup
  5. prod:
  6. url: http://another.example.com
  7. name: My Cool App

前面的示例将转换为以下属性(properties):

  1. environments.dev.url=http://dev.example.com
  2. environments.dev.name=Developer Setup
  3. environments.prod.url=http://another.example.com
  4. environments.prod.name=My Cool App

YAML 列表表示带有 [index] 下标引用的属性键。例如以下 YAML:

  1. my:
  2. servers:
  3. - dev.example.com
  4. - another.example.com

以上示例将转成以下属性:

  1. my.servers[0]=dev.example.com
  2. my.servers[1]=another.example.com

要使用 Spring Boot 的 Binder 工具来绑定这样配置到属性(这是 @ConfigurationProperties 所做的),你需要在目标 bean 中有一个 java.util.List(或 Set)类型的属性,你需要为其提供一个 setter 或者使用可变值初始化它。 例如,以下示例展示将上述的配置与属性绑定:

  1. @ConfigurationProperties(prefix="my")
  2. public class Config {
  3. private List<String> servers = new ArrayList<String>();
  4. public List<String> getServers() {
  5. return this.servers;
  6. }
  7. }

24.7.2、在 Spring Environment 中将 YAML 暴露为属性

YamlPropertySourceLoader 类可用于在 Spring Environment 中将 YAML 暴露为 PropertySource。这样做可以让您使用带占位符语法的 @Value 注解来访问 YAML 属性。

24.7.3、多 profile YAML 文档

您可以使用 spring.profiles key 在单个文件中指定多个特定 profile 的 YAML 文档,以指示文档何时应用,如下所示:

  1. server:
  2. address: 192.168.1.100
  3. ---
  4. spring:
  5. profiles: development
  6. server:
  7. address: 127.0.0.1
  8. ---
  9. spring:
  10. profiles: production & eu-central
  11. server:
  12. address: 192.168.1.120

在前面示例中,如果 development profile 处于激活状态,则 server.address 属性得值为 127.0.0.1。 同样,如果 productioneu-central profile 处于激活状态,则 server.address 属性的值为 192.168.1.120。如果未激活 developmentproductioneu-central profile,则该属性的值为 192.168.1.100

注意

因此,spring.profiles 可以包含一个简单的 profile 名称(例如 production)或一个 profile 表达式。profile 表达式允许表达更复杂的 profile 逻辑,例如 production & (eu-central | eu-west)。有关详细信息,请查阅参考指南

如果在应用程序上下文启动时没有显式激活,则激活默认 profile。因此,在以下 YAML 中,我们为 spring.security.user.password 设置了一个值,该值default profile 中可用:

  1. server:
  2. port: 8000
  3. ---
  4. spring:
  5. profiles: default
  6. security:
  7. user:
  8. password: weak

然而,在以下示例中,始终设置密码,因为它未附加到任何 profile,如果需要更改,必须在所有其他 profile 中显式重置:

  1. server:
  2. port: 8000
  3. spring:
  4. security:
  5. user:
  6. password: weak

使用 spring.profiles 元素来指定 Spring profile 可以选择通过使用 ! 字符来取反(否定)。如果为单个文档指定了否定和非否定的 profile,则至少一个非否定的 profile 必须匹配,没有否定的 profile 可以匹配。

24.7.4、YAML 的缺点

无法使用 @PropertySource 注解加载 YAML 文件。因此,如果您需要以这种方式加载值,请使用属性文件(properties)。

24.8、类型安全的配置属性

使用 @Value("${property}") 注解来注入配置属性有时会很麻烦,特别是如果您使用了多个属性或者您的数据本质上是分层结构。Spring Boot 提供了另一种使用属性的方法,该方法使用强类型的 bean 来管理和验证应用程序的配置,如下所示:

  1. package com.example;
  2. import java.net.InetAddress;
  3. import java.util.ArrayList;
  4. import java.util.Collections;
  5. import java.util.List;
  6. import org.springframework.boot.context.properties.ConfigurationProperties;
  7. @ConfigurationProperties("acme")
  8. public class AcmeProperties {
  9. private boolean enabled;
  10. private InetAddress remoteAddress;
  11. private final Security security = new Security();
  12. public boolean isEnabled() { ... }
  13. public void setEnabled(boolean enabled) { ... }
  14. public InetAddress getRemoteAddress() { ... }
  15. public void setRemoteAddress(InetAddress remoteAddress) { ... }
  16. public Security getSecurity() { ... }
  17. public static class Security {
  18. private String username;
  19. private String password;
  20. private List<String> roles = new ArrayList<>(Collections.singleton("USER"));
  21. public String getUsername() { ... }
  22. public void setUsername(String username) { ... }
  23. public String getPassword() { ... }
  24. public void setPassword(String password) { ... }
  25. public List<String> getRoles() { ... }
  26. public void setRoles(List<String> roles) { ... }
  27. }
  28. }

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

  • acme.enabled,默认值为 false
  • acme.remote-address,可以从 String 强制转换的类型。
  • acme.security.username,内嵌一个 security 对象,其名称由属性名称决定。特别是,返回类型根本没有使用,可能是 SecurityProperties
  • acme.security.password
  • acme.security.roles,String 集合。

注意

getter 和 setter 通常是必需的,因为绑定是通过标准的 Java Bean 属性描述符来完成,就像在 Spring MVC 中一样。以下情况可以省略 setter:- Map,只要它们要初始化,就需要一个 getter 但不一定需要setter,因为它们可以被 binder 修改。- 集合和数组可以通过一个索引(通常使用 YAML)或使用单个逗号分隔值(属性)进行访问。最后一种情况必须使用 setter。我们建议始终为此类型添加 setter。如果初始化集合,请确保它是可变的(如上例所示)。- 如果初始化嵌套的 POJO 属性(如前面示例中的 Security 字段),则不需要 setter。如果您希望 binder 使用其默认构造函数动态创建实例,则需要一个 setter。有些人可能会使用 Project Lombok 来自动生成 getter 和 setter。请确保 Lombok 不为此类型生成任何特定构造函数,因为容器会自动使用它来实例化对象。最后,考虑到标准 Java Bean 属性,不支持对静态属性的绑定。

提示

另请参阅 @Value 和 @ConfigurationProperties 之间的差异

您还需要列出要在 @EnableConfigurationProperties 注解中注册的属性类,如下所示:

  1. @Configuration
  2. @EnableConfigurationProperties(AcmeProperties.class)
  3. public class MyConfiguration {
  4. }

注意

当以这种方式注册 @ConfigurationProperties bean 时,bean 具有一个固定格式的名称:<prefix>-<fqn>,其中 <prefix>@ConfigurationProperties 注解中指定的环境 key 前缀,<fqn> 是 bean 的完全限定类名。如果注解未提供任何前缀,则仅使用 bean 的完全限定类名。上面示例中的 bean 名称为 acme-com.example.AcmeProperties

即使前面的配置为 AcmeProperties 创建了一个 bean,我们也建议 @ConfigurationProperties 只处理环境(environment),特别是不要从上下文中注入其他 bean。话虽如此,@EnableConfigurationProperties 注解也会自动应用到您的项目,以便从 Environment 配置使用了 @ConfigurationProperties 注解的所有现有的 bean。您可以通过确保 AcmeProperties 已经是一个 bean 来快捷生成 MyConfiguration,如下所示:

  1. @Component
  2. @ConfigurationProperties(prefix="acme")
  3. public class AcmeProperties {
  4. // ... see the preceding example
  5. }

这种配置风格特别适用于 SpringApplication 外部 YAML 配置,如下所示:

  1. # application.yml
  2. acme:
  3. remote-address: 192.168.1.1
  4. security:
  5. username: admin
  6. roles:
  7. - USER
  8. - ADMIN
  9. # additional configuration as required

要使用 @ConfigurationProperties bean,您可以使用与其他 bean 相同的方式注入它们,如下所示:

  1. @Service
  2. public class MyService {
  3. private final AcmeProperties properties;
  4. @Autowired
  5. public MyService(AcmeProperties properties) {
  6. this.properties = properties;
  7. }
  8. //...
  9. @PostConstruct
  10. public void openConnection() {
  11. Server server = new Server(this.properties.getRemoteAddress());
  12. // ...
  13. }
  14. }

提示

使用 @ConfigurationProperties 还可以生成元数据文件,IDE 可以通过这些文件来为您自己的 key 提供自动完成功能。有关详细信息,请参阅附录 B:配置元数据

24.8.1、第三方配置

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

要使用 Environment 属性配置 bean,请将 @ConfigurationProperties 添加到 bean 注册上,如下所示:

  1. @ConfigurationProperties(prefix = "another")
  2. @Bean
  3. public AnotherComponent anotherComponent() {
  4. ...
  5. }

使用 another 前缀定义的所有属性都使用与前面的 AcmeProperties 示例类似的方式映射到 AnotherComponent bean。

24.8.2、宽松绑定

Spring Boot 使用一些宽松的规则将 Environment 属性绑定到 @ConfigurationProperties bean,因此 Environment 属性名不需要和 bean 属性名精确匹配。常见的示例包括使用了 - 符号分割的环境属性(例如,context-path 绑定到 contextPath)和大写环境属性(例如,PORT 绑定到 port)。

如下 @ConfigurationProperties 类:

  1. @ConfigurationProperties(prefix="acme.my-project.person")
  2. public class OwnerProperties {
  3. private String firstName;
  4. public String getFirstName() {
  5. return this.firstName;
  6. }
  7. public void setFirstName(String firstName) {
  8. this.firstName = firstName;
  9. }
  10. }

在上述示例中,同样可以使用以下属性名称:

表 24.1、宽松绑定

属性 描述
acme.my-project.person.first-name Kebab 风格(短横线命名),建议在 .properties.yml 文件中使用。
acme.myProject.person.firstName 标准驼峰式风格。
acme.my_project.person.first_name 下划线表示法,.properties.yaml 文件中的另外一种格式。
ACME_MYPROJECT_PERSON_FIRSTNAME 大写风格,当使用系统环境变量时推荐使用该风格。

注意

注解的 prefix 值必须是 kebab (短横线命名)风格(小写并用 - 分隔,例如 acme.my-project.person)。

表 24.2、每种属性源(property source)的宽松绑定规则

属性源 简单类型 列表集合类型
properties 文件 驼峰式、短横线式或下划线式 标准列表语法使用 [] 或逗号分隔值
YAML 文件 驼峰式、短横线式或者下划线式 标准 YAML 列表语法或者逗号分隔值
环境变量 大写并且以下划线作为定界符,_ 不能放在属性名之间使用 数字值两边使用下划线连接,例如 MY_ACME_1_OTHER = my.acme[1].other
系统属性 驼峰式、短横线式或者下划线式 标准列表语法使用 [] 或逗号分隔值

提示

我们建议,属性尽可能以小写的短横线格式存储,比如 my.property-name=acme

当绑定到 Map 属性时,如果 key 包含除小写字母数字字符或 - 以外的任何内容,则需要使用括号表示法来保留原始值。如果 key 没有使用 [] 包裹,则里面的任何非字母数字字符或 - 的字符都将被删除。例如,将以下属性绑定到一个 Map

  1. acme:
  2. map:
  3. "[/key1]": value1
  4. "[/key2]": value2
  5. /key3: value3

上面的属性将绑定到一个 Map 上,其中 /key1/key2key3 作为 map 的 key。

24.8.3、合并复杂类型

当列表集合(list)在多个地方配置时,整个列表集合将被替换。

例如,假设带有 namedescription 属性的 MyPojo 对象默认为 null。以下示例中,AcmeProperties 暴露了一个 MyPojo 对象列表集合:

  1. @ConfigurationProperties("acme")
  2. public class AcmeProperties {
  3. private final List<MyPojo> list = new ArrayList<>();
  4. public List<MyPojo> getList() {
  5. return this.list;
  6. }
  7. }

配置可以如下:

  1. acme:
  2. list:
  3. - name: my name
  4. description: my description
  5. ---
  6. spring:
  7. profiles: dev
  8. acme:
  9. list:
  10. - name: my another name

如果 dev 配置文件未激活,则 AcmeProperties.list 只包含一条 MyPojo 条目,如之前所述。但是,如果激活了 dev 配置文件,列表集合仍然只包含一个条目(name 属性值为 my another namedescriptionnull)。此配置不会向列表集合中添加第二个 MyPojo 实例,也不会合并条目。

在多个配置文件中指定一个 List 时,最高优先级(并且只有一个)的列表集合将被使用。可做如下配置:

  1. acme:
  2. list:
  3. - name: my name
  4. description: my description
  5. - name: another name
  6. description: another description
  7. ---
  8. spring:
  9. profiles: dev
  10. acme:
  11. list:
  12. - name: my another name

在前面示例中,如果 dev 配置文件处于活动状态,则 AcmeProperties.list 包含一个 MyPojo 条目(namemy another namedescriptionnull)。对于 YAML 而言,逗号分隔的列表和YAML 列表同样会完全覆盖列表集合的内容。

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

  1. @ConfigurationProperties("acme")
  2. public class AcmeProperties {
  3. private final Map<String, MyPojo> map = new HashMap<>();
  4. public Map<String, MyPojo> getMap() {
  5. return this.map;
  6. }
  7. }

可以考虑以下配置:

  1. acme:
  2. map:
  3. key1:
  4. name: my name 1
  5. description: my description 1
  6. ---
  7. spring:
  8. profiles: dev
  9. acme:
  10. map:
  11. key1:
  12. name: dev name 1
  13. key2:
  14. name: dev name 2
  15. description: dev description 2

如果 dev 配置文件未激活,则 AcmeProperties.map 只包含一个带 key1 key 的条目(namemy name 1descriptionmy description 1)。但是,如果激活了 dev 配置文件,则 map 将包含两个条目, key 分别为 key1namedev name 1descriptionmy description 1)和 key2namedev name 2descriptiondev description 2)。

注意

前面的合并规则适用于所有不同属性源的属性,而不仅仅是 YAML 文件。

24.8.4、属性转换

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

注意

由于该 bean 在应用程序生命周期早期就被请求 ,因此请限制 ConversionService 使用的依赖。您在创建时可能无法完全初始化所需的依赖。如果配置 key 为非强制需要,您可能希望重命名自定义的 ConversionService,并仅依赖于使用 @ConfigurationPropertiesBinding 限定的自定义转换器。

转换 duration

Spring Boot 支持持续时间(duration)表达。如果您暴露一个 java.time.Duration 属性,则可以在应用程序属性中使用以下格式:

  • 常规 long 表示(除非指定 @DurationUnit,否则使用毫秒作为默认单位)
  • java.util.Duration 使用的标准 ISO-8601 格式
  • 一种更易读的格式,值和单位在一起(例如 10s 表示 10 秒)

思考以下示例:

  1. @ConfigurationProperties("app.system")
  2. public class AppSystemProperties {
  3. @DurationUnit(ChronoUnit.SECONDS)
  4. private Duration sessionTimeout = Duration.ofSeconds(30);
  5. private Duration readTimeout = Duration.ofMillis(1000);
  6. public Duration getSessionTimeout() {
  7. return this.sessionTimeout;
  8. }
  9. public void setSessionTimeout(Duration sessionTimeout) {
  10. this.sessionTimeout = sessionTimeout;
  11. }
  12. public Duration getReadTimeout() {
  13. return this.readTimeout;
  14. }
  15. public void setReadTimeout(Duration readTimeout) {
  16. this.readTimeout = readTimeout;
  17. }
  18. }

指定一个会话超时时间为 30 秒,使用 30PT30S30s 等形式都是可以的。读取超时时间设置为 500ms,可以采用以下任何一种形式:500PT0.5S500ms

您也可以使用任何支持的单位来标识:

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

默认单位是毫秒,可以使用 @DurationUnit 配合上面的单位示例重写。

提示

要从先前仅使用 Long 来表示持续时间的版本进行升级,如果切换到 Duration 时不是毫秒,请定义单位(使用 @DurationUnit)。这样做可以提供透明的升级路径,同时支持更丰富的格式。

转换 Data Size

Spring Framework 有一个 DataSize 值类型,允许以字节表示大小。如果暴露一个 DataSize 属性,则可以在应用程序属性中使用以下格式:

  • 常规的 Long 表示(使用字节作为默认单位,除非指定了 @DataSizeUnit
  • 更具有可读性的格式,值和单位在一起(例如 10MB 表示 10 兆字节)

请思考以下示例:

  1. @ConfigurationProperties("app.io")
  2. public class AppIoProperties {
  3. @DataSizeUnit(DataUnit.MEGABYTES)
  4. private DataSize bufferSize = DataSize.ofMegabytes(2);
  5. private DataSize sizeThreshold = DataSize.ofBytes(512);
  6. public DataSize getBufferSize() {
  7. return this.bufferSize;
  8. }
  9. public void setBufferSize(DataSize bufferSize) {
  10. this.bufferSize = bufferSize;
  11. }
  12. public DataSize getSizeThreshold() {
  13. return this.sizeThreshold;
  14. }
  15. public void setSizeThreshold(DataSize sizeThreshold) {
  16. this.sizeThreshold = sizeThreshold;
  17. }
  18. }

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

您也可以使用任何支持的单位:

  • B 表示字节
  • KB 为千字节
  • MB 为兆字节
  • GB 为千兆字节
  • TB 为兆兆字节

默认单位是字节,可以使用 @DataSizeUnit 配合上面的示例单位重写。

提示

要从先前仅使用 Long 来表示大小的版本进行升级,请确保在切换到 DataSize 不是字节的情况下定义单位(使用 @DataSizeUnit)。这样做可以提供透明的升级路径,同时支持更丰富的格式。

24.8.5、@ConfigurationProperties 验证

只要使用了 Spring 的 @Validated 注解,Spring Boot 就会尝试验证 @ConfigurationProperties 类。您可以直接在配置类上使用 JSR-303 javax.validation 约束注解。为此,请确保 JSR-303 实现在 classpath 上,然后将约束注解添加到字段上,如下所示:

  1. @ConfigurationProperties(prefix="acme")
  2. @Validated
  3. public class AcmeProperties {
  4. @NotNull
  5. private InetAddress remoteAddress;
  6. // ... getters and setters
  7. }

提示

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

虽然绑定时也会验证嵌套属性,但最好的做法还是将关联字段注解上 @Valid。这可确保即使未找到嵌套属性也会触发验证。以下示例基于前面的 AcmeProperties 示例:

  1. @ConfigurationProperties(prefix="acme")
  2. @Validated
  3. public class AcmeProperties {
  4. @NotNull
  5. private InetAddress remoteAddress;
  6. @Valid
  7. private final Security security = new Security();
  8. // ... getters and setters
  9. public static class Security {
  10. @NotEmpty
  11. public String username;
  12. // ... getters and setters
  13. }
  14. }

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

提示

spring-boot-actuator 模块包括一个暴露所有 @ConfigurationProperties bean 的端点。可将 Web 浏览器指向 /actuator/configprops 或使用等效的 JMX 端点。有关详细信息,请参阅生产就绪功能部分。

24.8.6、@ConfigurationProperties 与 @Value 对比

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

功能 @ConfigurationProperties @Value
宽松绑定
元数据支持
SpEL 表达式

如果您要为自己的组件定义一组配置 key,我们建议您将它们分组到使用 @ConfigurationProperties 注解的 POJO 中。您应该知道,由于 @Value 不支持宽松绑定,因此如果您需要通过环境变量来提供值,它并不是一个好的选择。

最后,虽然您可以在 @Value 中编写 SpEL 表达式,但来自应用程序属性文件的此类表达式并不会被处理。