24、外部化配置
Spring Boot 可以让您的配置外部化,以便可以在不同环境中使用相同的应用程序代码。您可以使用 properties 文件、YAML 文件、环境变量或者命令行参数来外部化配置。可以使用 @Value
注解将属性值直接注入到 bean 中,可通过 Spring 的 Environment
访问,或者通过 @ConfigurationProperties
绑定到结构化对象。
Spring Boot 使用了一个非常特别的 PropertySource
指令,用于智能覆盖默认值。属性将按照以下顺序处理:
- 在您的主目录(当 devtools 被激活,则为
~/.spring-boot-devtools.properties
)中的 Devtools 全局设置属性。 - 在测试中使用到的
@TestPropertySource
注解。 - 在测试中使用到的
properties
属性,可以是@SpringBootTest
和用于测试应用程序某部分的测试注解。 - 命令行参数。
- 来自
SPRING_APPLICATION_JSON
的属性(嵌入在环境变量或者系统属性【system propert】中的内联 JSON)。 ServletConfig
初始化参数。ServletContext
初始化参数。- 来自
java:comp/env
的 JNDI 属性。 - Java 系统属性(
System.getProperties()
)。 - 操作系统环境变量。
- 只有
random.*
属性的RandomValuePropertySource
。 - 在已打包的 jar 外部的指定 profile 的应用属性文件(
application-{profile}.properties
和 YAML 变量)。 - 在已打包的 jar 内部的指定 profile 的应用属性文件(
application-{profile}.properties
和 YAML 变量)。 - 在已打包的 jar 外部的应用属性文件(
application.properties
和 YAML 变量)。 - 在已打包的 jar 内部的应用属性文件(
application.properties
和 YAML 变量)。 - 在
@Configuration
类上的@PropertySource
注解。 - 默认属性(使用
SpringApplication.setDefaultProperties
指定)。
举个例子,假设开发的 @Component
使用了 name
属性,可以这样:
import org.springframework.stereotype.*;
import org.springframework.beans.factory.annotation.*;
@Component
public class MyBean {
@Value("${name}")
private String name;
// ...
}
在您的应用程序的 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
在此示例中,您可以在 SpringEnvironment
中使用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。如下示例:
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]}
random.int*
语法为 OPEN value (,max) CLOSE
,OPEN,CLOSE
可为任意字符,value,max
为整数。如果使用了 max
,value
则为最小值,max
为最大值。
24.2、访问命令行属性
默认情况下,SpringApplication
将所有命令行选项参数(即以 --
开头的参数,比如 --server.port=9000
)转换为属性,并将它们添加到 Spring Environment
中。如之前所述,命令行属性始终优先于其他属性源。
如果您不希望将命令行属性添加到 Environment
,可以使用 SpringApplication.setAddCommandLineProperties(false)
来禁用它们。
24.3、应用程序属性文件
SpringApplication
从以下位置的 application.properties
文件中加载属性(properties),并将它们添加到 Spring Environment
中:
- 当前目录的
/config
子目录 - 当前目录
- classpath 上的
/config
包 - classpath 根路径
列表按序号优先级排序,序号越小,优先级越高。
注意
您还可以使用 YAML(.yml)文件来替代 .properties。
如果您不喜欢 application.properties
作为配置文件名,则可以通过指定 spring.config.name
环境属性来切换到另一个文件名。您还可以使用 spring.config.location
环境属性来引用一个显式位置(以逗号分隔的目录位置或文件路径列表)。以下示例展示了如何指定其他文件名:
$ java -jar myproject.jar --spring.config.name=myproject
以下示例展示了如何指定两个位置:
$ java -jar myproject.jar --spring.config.location=classpath:/default.properties,classpath:/override.properties
警告
spring.config.name
和spring.config.location
在程序启动早期就用来确定哪些文件必须加载,因此必须将它们定义为环境属性(通常是 OS 环境变量、系统属性或命令行参数)。
如果 spring.config.location
包含目录(而不是文件),则它们应该以 /
结尾(并且在运行期间,在加载之前追加从 spring.config.name
生成的名称,包括指定 profile 的文件名)。 spring.config.location
中指定的文件按原样使用,不支持指定 profile 形式,并且可被任何指定 profile 的文件的属性所覆盖。
配置位置以相反的顺序搜索。默认情况下,配置的位置为 classpath:/,classpath:/config/,file:./,file:./config/
。生成的搜索顺序如下:
file:./config/
file:./
classpath:/config/
classpath:/
使用了 spring.config.location
配置自定义配置位置时,默认位置配置将被替代。例如,如果 spring.config.location
配置为 classpath:/custom-config/,file:./custom-config/
,搜索顺序将变为以下:
file:./custom-config/
classpath:custom-config/
或者,当使用 spring.config.additional-location
配置自定义配置位置时,除了使用默认位置外,还会使用它们。这些其他(additional)位置将在默认位置之前搜索。例如,如果将其他位置配置为 classpath:/custom-config/,file:./custom-config/
,则搜索顺序将变为以下内容:
file:./custom-config/
classpath:custom-config/
file:./config/
file:./
classpath:/config/
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}.properties
。Environment
有一组默认配置文件(默认情况下为 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
进行过滤,因此您可以返回之前定义的值(例如,从系统属性)。
app.name=MyApp
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 加载为 Properties
,YamlMapFactoryBean
将 YAML 加载为 Map
。
例如以下 YAML 文档:
environments:
dev:
url: http://dev.example.com
name: Developer Setup
prod:
url: http://another.example.com
name: My Cool App
前面的示例将转换为以下属性(properties):
environments.dev.url=http://dev.example.com
environments.dev.name=Developer Setup
environments.prod.url=http://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
要使用 Spring Boot 的 Binder
工具来绑定这样配置到属性(这是 @ConfigurationProperties
所做的),你需要在目标 bean 中有一个 java.util.List
(或 Set
)类型的属性,你需要为其提供一个 setter 或者使用可变值初始化它。 例如,以下示例展示将上述的配置与属性绑定:
@ConfigurationProperties(prefix="my")
public class Config {
private List<String> servers = new ArrayList<String>();
public List<String> getServers() {
return this.servers;
}
}
24.7.2、在 Spring Environment 中将 YAML 暴露为属性
YamlPropertySourceLoader
类可用于在 Spring Environment
中将 YAML 暴露为 PropertySource
。这样做可以让您使用带占位符语法的 @Value
注解来访问 YAML 属性。
24.7.3、多 profile YAML 文档
您可以使用 spring.profiles
key 在单个文件中指定多个特定 profile 的 YAML 文档,以指示文档何时应用,如下所示:
server:
address: 192.168.1.100
---
spring:
profiles: development
server:
address: 127.0.0.1
---
spring:
profiles: production & eu-central
server:
address: 192.168.1.120
在前面示例中,如果 development
profile 处于激活状态,则 server.address
属性得值为 127.0.0.1
。 同样,如果 production
和 eu-central
profile 处于激活状态,则 server.address
属性的值为 192.168.1.120
。如果未激活 development
、production
或 eu-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 中可用:
server:
port: 8000
---
spring:
profiles: default
security:
user:
password: weak
然而,在以下示例中,始终设置密码,因为它未附加到任何 profile,如果需要更改,必须在所有其他 profile 中显式重置:
server:
port: 8000
spring:
security:
user:
password: weak
使用 spring.profiles
元素来指定 Spring profile 可以选择通过使用 !
字符来取反(否定)。如果为单个文档指定了否定和非否定的 profile,则至少一个非否定的 profile 必须匹配,没有否定的 profile 可以匹配。
24.7.4、YAML 的缺点
无法使用 @PropertySource
注解加载 YAML 文件。因此,如果您需要以这种方式加载值,请使用属性文件(properties)。
24.8、类型安全的配置属性
使用 @Value("${property}")
注解来注入配置属性有时会很麻烦,特别是如果您使用了多个属性或者您的数据本质上是分层结构。Spring Boot 提供了另一种使用属性的方法,该方法使用强类型的 bean 来管理和验证应用程序的配置,如下所示:
package com.example;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("acme")
public class AcmeProperties {
private boolean enabled;
private InetAddress remoteAddress;
private final Security security = new Security();
public boolean isEnabled() { ... }
public void setEnabled(boolean enabled) { ... }
public InetAddress getRemoteAddress() { ... }
public void setRemoteAddress(InetAddress remoteAddress) { ... }
public Security getSecurity() { ... }
public static class Security {
private String username;
private String password;
private List<String> roles = new ArrayList<>(Collections.singleton("USER"));
public String getUsername() { ... }
public void setUsername(String username) { ... }
public String getPassword() { ... }
public void setPassword(String password) { ... }
public List<String> getRoles() { ... }
public void setRoles(List<String> roles) { ... }
}
}
前面的 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 属性,不支持对静态属性的绑定。
提示
您还需要列出要在 @EnableConfigurationProperties
注解中注册的属性类,如下所示:
@Configuration
@EnableConfigurationProperties(AcmeProperties.class)
public class MyConfiguration {
}
注意
当以这种方式注册@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
,如下所示:
@Component
@ConfigurationProperties(prefix="acme")
public class AcmeProperties {
// ... see the preceding example
}
这种配置风格特别适用于 SpringApplication
外部 YAML 配置,如下所示:
# application.yml
acme:
remote-address: 192.168.1.1
security:
username: admin
roles:
- USER
- ADMIN
# additional configuration as required
要使用 @ConfigurationProperties
bean,您可以使用与其他 bean 相同的方式注入它们,如下所示:
@Service
public class MyService {
private final AcmeProperties properties;
@Autowired
public MyService(AcmeProperties properties) {
this.properties = properties;
}
//...
@PostConstruct
public void openConnection() {
Server server = new Server(this.properties.getRemoteAddress());
// ...
}
}
提示
使用
@ConfigurationProperties
还可以生成元数据文件,IDE 可以通过这些文件来为您自己的 key 提供自动完成功能。有关详细信息,请参阅附录 B:配置元数据。
24.8.1、第三方配置
@ConfigurationProperties
除了可以使用来注解类之外,您还可以在公共的 @Bean 方法上使用。当您想要将属性绑定到您掌控之外的第三方组件时,这样做特别有用。
要使用 Environment
属性配置 bean,请将 @ConfigurationProperties
添加到 bean 注册上,如下所示:
@ConfigurationProperties(prefix = "another")
@Bean
public AnotherComponent anotherComponent() {
...
}
使用 another
前缀定义的所有属性都使用与前面的 AcmeProperties
示例类似的方式映射到 AnotherComponent
bean。
24.8.2、宽松绑定
Spring Boot 使用一些宽松的规则将 Environment
属性绑定到 @ConfigurationProperties
bean,因此 Environment
属性名不需要和 bean 属性名精确匹配。常见的示例包括使用了 -
符号分割的环境属性(例如,context-path
绑定到 contextPath
)和大写环境属性(例如,PORT
绑定到 port
)。
如下 @ConfigurationProperties
类:
@ConfigurationProperties(prefix="acme.my-project.person")
public class OwnerProperties {
private String firstName;
public String getFirstName() {
return this.firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
}
在上述示例中,同样可以使用以下属性名称:
表 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
:
acme:
map:
"[/key1]": value1
"[/key2]": value2
/key3: value3
上面的属性将绑定到一个 Map
上,其中 /key1
,/key2
和 key3
作为 map 的 key。
24.8.3、合并复杂类型
当列表集合(list)在多个地方配置时,整个列表集合将被替换。
例如,假设带有 name
和 description
属性的 MyPojo
对象默认为 null
。以下示例中,AcmeProperties
暴露了一个 MyPojo
对象列表集合:
@ConfigurationProperties("acme")
public class AcmeProperties {
private final List<MyPojo> list = new ArrayList<>();
public List<MyPojo> getList() {
return this.list;
}
}
配置可以如下:
acme:
list:
- name: my name
description: my description
---
spring:
profiles: dev
acme:
list:
- name: my another name
如果 dev
配置文件未激活,则 AcmeProperties.list
只包含一条 MyPojo
条目,如之前所述。但是,如果激活了 dev
配置文件,列表集合仍然只包含一个条目(name
属性值为 my another name
,description
为 null
)。此配置不会向列表集合中添加第二个 MyPojo
实例,也不会合并条目。
在多个配置文件中指定一个 List
时,最高优先级(并且只有一个)的列表集合将被使用。可做如下配置:
acme:
list:
- name: my name
description: my description
- name: another name
description: another description
---
spring:
profiles: dev
acme:
list:
- name: my another name
在前面示例中,如果 dev
配置文件处于活动状态,则 AcmeProperties.list
包含一个 MyPojo
条目(name
为 my another name
,description
为 null
)。对于 YAML 而言,逗号分隔的列表和YAML 列表同样会完全覆盖列表集合的内容。
对于 Map
属性,您可以绑定来自多个源中提取的属性值。但是,对于多个源中的相同属性,则使用高优先级最高的属性。以下示例从 AcmeProperties
暴露了一个 Map<String, MyPojo>
:
@ConfigurationProperties("acme")
public class AcmeProperties {
private final Map<String, MyPojo> map = new HashMap<>();
public Map<String, MyPojo> getMap() {
return this.map;
}
}
可以考虑以下配置:
acme:
map:
key1:
name: my name 1
description: my description 1
---
spring:
profiles: dev
acme:
map:
key1:
name: dev name 1
key2:
name: dev name 2
description: dev description 2
如果 dev
配置文件未激活,则 AcmeProperties.map
只包含一个带 key1
key 的条目(name
为 my name 1
,description
为 my description 1
)。但是,如果激活了 dev 配置文件,则 map
将包含两个条目, key 分别为 key1
(name
为 dev name 1
和 description
为 my description 1
)和 key2
(name
为 dev name 2
和 description
为 dev 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 秒)
思考以下示例:
@ConfigurationProperties("app.system")
public class AppSystemProperties {
@DurationUnit(ChronoUnit.SECONDS)
private Duration sessionTimeout = Duration.ofSeconds(30);
private Duration readTimeout = Duration.ofMillis(1000);
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;
}
}
指定一个会话超时时间为 30 秒,使用 30
、PT30S
和 30s
等形式都是可以的。读取超时时间设置为 500ms,可以采用以下任何一种形式:500
、PT0.5S
和 500ms
。
您也可以使用任何支持的单位来标识:
ns
为纳秒us
为微秒ms
为毫秒s
为秒m
为分钟h
为小时d
为天
默认单位是毫秒,可以使用 @DurationUnit
配合上面的单位示例重写。
提示
要从先前仅使用
Long
来表示持续时间的版本进行升级,如果切换到Duration
时不是毫秒,请定义单位(使用@DurationUnit
)。这样做可以提供透明的升级路径,同时支持更丰富的格式。
转换 Data Size
Spring Framework 有一个 DataSize
值类型,允许以字节表示大小。如果暴露一个 DataSize
属性,则可以在应用程序属性中使用以下格式:
- 常规的
Long
表示(使用字节作为默认单位,除非指定了@DataSizeUnit
) - 更具有可读性的格式,值和单位在一起(例如
10MB
表示10
兆字节)
请思考以下示例:
@ConfigurationProperties("app.io")
public class AppIoProperties {
@DataSizeUnit(DataUnit.MEGABYTES)
private DataSize bufferSize = DataSize.ofMegabytes(2);
private DataSize sizeThreshold = DataSize.ofBytes(512);
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;
}
}
要指定 10 兆字节的缓冲大小,使用 10
和 10MB
是等效的。256 字节的大小可以指定为 256
或 256B
。
您也可以使用任何支持的单位:
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 上,然后将约束注解添加到字段上,如下所示:
@ConfigurationProperties(prefix="acme")
@Validated
public class AcmeProperties {
@NotNull
private InetAddress remoteAddress;
// ... getters and setters
}
提示
您还可以通过使用
@Validated
注解创建配置属性的@Bean
方法来触发验证。
虽然绑定时也会验证嵌套属性,但最好的做法还是将关联字段注解上 @Valid
。这可确保即使未找到嵌套属性也会触发验证。以下示例基于前面的 AcmeProperties
示例:
@ConfigurationProperties(prefix="acme")
@Validated
public class AcmeProperties {
@NotNull
private InetAddress remoteAddress;
@Valid
private final Security security = new Security();
// ... getters and setters
public static class Security {
@NotEmpty
public String username;
// ... getters and setters
}
}
您还可以通过创建一个名为 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
表达式,但来自应用程序属性文件的此类表达式并不会被处理。