外部化配置
外部化配置(Externalized Configuration)
Spring Boot 允许将配置外部化,方便在不同的环境中使用相同的应用程序代码。你可以使用各种外部配置源,包括 Java 配置文件、YAML 文件、环境变量和命令行参数。 属性值可以使用 @Value 注解直接注入到 bean 中,通过 Spring 的 Environment 抽象访问,或者通过 @ConfigurationProperties 绑定到结构化对象。
Spring Boot 使用了一个非常特殊的 PropertySource 顺序,旨在允许合理地重写值。以后的属性源可以替代以前的属性源中定义的值。信息来源按以下顺序考虑:
- 默认属性(通过设置
SpringApplication.setDefaultProperties指定); @PropertySource注解注释在你的@Configuration配置类上。请注意,在刷新应用程序上下文之前,此类属性源不会添加到环境中。现在配置某些属性为时已晚,例如logging.*和spring.main.*,它们是在刷新开始之前读取的;- 配置数据(例如
application.properties文件); RandomValuePropertySource只在random.*中有属性;- 操作系统环境变量;
- Java 系统属性(
System.getProperties()); - 来自
java:comp/env的 JNDI 属性; ServletContext初始化参数;ServletConfig初始化参数;SPRING_APPLICATION_JSON的属性(内嵌在环境变量或系统属性中的内联 JSON);- 命令行参数;
- 在你的测试上的
properties属性,在@SpringBootTest和测试注解中可用,用于测试应用程序的特定部分; - 用于你的测试类
@TestPropertySource注解; - 当 DevTools 处于活动状态时,
$HOME/.config/spring-boot目录中的 DevTools 全局设置属性。
配置数据文件按以下顺序考虑:
- 打包在 JAR 中的应用程序属性(application.properties 和 YAML 变体);
- 打包在 JAR 中的特定于配置文件的应用程序属性(application-{profile}.properties 和 YAML 变体);
- 打包在 JAR 之外的应用程序属性(application.properties 和 YAML 变体);
- 打包在 JAR 之外的特定于配置文件的应用程序属性(application-{profile}.properties 和 YAML 变体);
注意:
对于整个应用程序,建议坚持使用一种格式。如果在同一位置具有同时具有 .properties 和 .yml 格式的配置文件,则 .properties 优先。
为了提供一个具体的示例,假设开发了一个使用 name 属性的 @Component,如下例所示:
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class MyBean {
@Value("${name}")
private String name;
public String getName() {
return name;
}
}
在应用程序类路径上(例如,在 jar 中),可以有一个 application.properties 文件,该文件为 name 提供合理的默认属性值。在新环境中运行时,可以在 jar 外部提供 application.properties 文件来覆盖 name 值。对于一次性测试,可以使用特定的命令行开关启动(例如,java -jar app.jar --name="Spring")。
application.yml:
name: springboot
运行启动配置如下图所示:

控制台输出内容如下:
...
2023-02-20 11:53:37.123 INFO 14740 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2023-02-20 11:53:37.130 INFO 14740 --- [ main] c.o.s.core.externalconfig.MyApplication : Started MyApplication in 0.959 seconds (JVM running for 1.611)
name:spring
TIP:
env 和 configprops 端点在确定属性为何具有特定值时非常有用。可以使用这两个端点诊断意外的属性值。
1.访问命令行属性
默认情况下,SpringApplication 将任何命令行选项参数(即以 -- 开头的参数,例如 --server.port=9000)转换为 property,并将其添加到 Spring Environment 中。如前所述,命令行属性始终优先于基于文件的属性源。
如果不希望将命令行属性添加到 Environment 中,可以禁用它们。通过使用 SpringApplication.setAddCommandLineProperties(false)。如下例所示:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
final SpringApplication springApplication = new SpringApplication(MyApplication.class);
// 禁用命令行参数
springApplication.setAddCommandLineProperties(false);
springApplication.run(args);
}
}
2.JSON 应用程序属性
环境变量和系统属性通常有一些限制,这意味着某些属性名称不能使用。为了帮助实现这一点,Spring Boot 允许将属性块编码到单个 JSON 结构中。
当应用程序启动时,任何 spring.application.json 或 SPRING_APPLICATION_JSON 属性都将被解析并添加到 Environment 中。
例如,SPRING_APPLICATION_JSON 属性可以在 UN*X 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 的 null 值将被添加到结果属性源,但 PropertySourcesPropertyResolver 将 null 属性视为缺少的值。这意味着 JSON 不能用 null 值覆盖来自低级属性源的属性。
3.外部应用程序属性
当应用程序启动时,Spring Boot 将自动从以下位置查找并加载 application.properties 和 application.yaml 文件:
类路径
a. 类路径根
b. 类路径
/config包当前目录
a. 当前目录
b. 当前目录下的
config/子目录c.
config/子目录的直接子目录
列表按优先级排序(来自较低项的值覆盖较早的项)。加载文件中的文档作为 PropertySources 添加到 Spring Environment 中。
如果你不喜欢将 application 作为配置文件名,可以通过指定 spring.config.name 环境属性来切换到其他文件名。例如,要查找 myproject.properties 和 myproject.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
TIP:
使用 optional 前缀:如果位置是可选的,并且不介意它们是否存在。
警告:
很早就使用 spring.config.name、spring.config.location 和 spring.config.additional-location 来确定必须加载哪些文件。它们必须定义为环境属性(通常是 OS 环境变量、系统属性或命令行参数)。
如果 spring.config.location 包含目录(而不是文件),它们应该以 / 结尾。在运行时,它们将在加载之前附加从 spring.config.name 生成的名称。直接导入 spring.config.location 中指定的文件。
注意:
目录和文件位置值也被展开,以检查特定于概要文件的文件。例如,如果你有一个 classpath:myconfig.properties 的 spring.config.location ,还将找到适当的 classpath:myconfig-<profile>.properties 加载文件。
在大多数情况下,你添加的每个 spring.config.location 项都将引用单个文件或目录。位置按照定义的顺序进行处理,后一个位置可以覆盖前一个位置的值。
如果有一个复杂的位置要设置,并且使用特定于配置的配置文件,可能需要提供进一步的提示,以便 Spring Boot 知道它们应该如何分组。位置组是一组位置,这些位置都被视为处于同一级别。例如,可能希望对所有类路径位置进行分组,然后对所有外部位置进行分组。位置组中的项目应以 ";" 分隔。
使用 spring.config.location 配置的位置将替换默认位置。例如,如果 spring.config.location 配置值为 optional:classpath:/custom-config/,optional:file:./custom-config/,则考虑的完整位置集为:
optional:classpath:custom-config/optional:file:./custom-config/
如果希望添加其他位置而不是替换它们,可以使用 spring.config.additional-location 。从其他位置加载的属性可以覆盖默认位置中的属性。例如,如果 spring.config.additional-location 配置了 optional:classpath:/custom-config/,optional:file:./custom-config/,考虑的完整位置集是:
optional:classpath:/;optional:classpath:/config/optional:file:./;optional:file:./config/;optional:file:./config/*/optional:classpath:custom-config/optional:file:./custom-config/
这种搜索顺序允许你在一个配置文件中指定默认值,然后选择性地覆盖另一个配置中的这些值。可以在其中一个默认位置的 application.properties(或使用 spring.config.name 选择的任何其他基本名称)中为应用程序提供默认值。然后,可以在运行时使用位于其中一个自定义位置的不同文件覆盖这些默认值。
注意:
如果你使用环境变量而不是系统属性,则大多数操作系统不允许使用句点分隔的键名,但可以使用下划线(例如,SPRING_CONFIG_NAME 而不是 spring.config.name)。
注意:
如果你的应用程序在 Servlet 容器或应用程序服务器中运行,则可以使用 JNDI 属性(在 java:comp/env 中)或 Servlet 上下文初始化参数来代替环境变量或系统属性。
可选位置
默认情况下,当指定的配置数据位置不存在时,Spring Boot 将抛出 ConfigDataLocationNotFoundException 异常并且应用程序将无法启动。
如果想指定一个位置,但不介意它是否总是存在,可以使用 optional: 前缀。可以在 spring.config.location 和 spring.config.additional-location 属性,以及 spring.config.import 声明。
例如,spring.config.import 值为 optional:file:/myconfig.properties 允许应用程序启动,即使 myconfig.properties 文件丢失也是如此。如下例所示:
application.yml:
spring:
config:
import: optional:/myconfig.properties
如果要忽略所有 ConfigDataLocationNotFoundExceptions 并始终继续启动应用程序,可以使用 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.location 和 spring.config.additional-location 属性一起使用。
注意:
通配符位置只能包含一个 *,对于目录搜索位置,必须以 */ 结尾,对于文件搜索位置,则必须以 */<filename> 结尾。带有通配符的位置根据文件名的绝对路径按字母顺序排序。
TIP:
通配符位置仅适用于外部目录。不能在 classpath: 位置中使用通配符。
配置特定文件
除了应用程序属性文件之外,Spring Boot 还将尝试使用命名约定 application-{profile} 加载特定配置文件。例如,如果应用程序激活名为 prod 的配置文件并使用 YAML 文件,那么将同时考虑 application.yml 和 application-prod.yml。
特定配置文件的属性从与标准 application.properties 相同的位置加载,特定配置文件总是覆盖非特定的文件。如果指定了多个配置文件,则采用最后获胜策略。例如,如果配置文件 prod,live 由 spring.profiles.active 属性指定 ,application-prod.properties 中的值可以被 application-live.properties 中的值覆盖。
注意:
最后获胜策略应用于位置组级别。classpath:/cfg/,classpath:/ext/ 的 spring.config.location 不会有与 classpath:/cfg/;classpath:/ext/ 相同的重写规则。
例如,继续上面的 prod,live 示例,我们可能有以下文件:
/cfg
application-live.properties
/ext
application-live.properties
application-prod.properties
当我们有 classpath:/cfg/,classpath/ext/we 的 spring.config.Location 时。先处理所有 /cfg 文件,然后处理所有 /ext 文件:
/cfg/application-live.properties/ext/application-prod.properties/ext/application-live.properties
当我们有 classpath:/cfg/;classpath:/ext/ 时(带有 ; 分隔符)我们在同一级别上处理 /cfg 和 /ext:
/ext/application-prod.properties/cfg/application-live.properties/ext/application-live.properties
Environment 具有一组默认配置文件(默认情况下为 [default]),如果未设置活动配置文件,则使用这些配置文件。换句话说,如果没有显式激活配置文件,那么将考虑 application-default 属性。
注意:
属性文件只加载一次。 如果已经直接导入了特定配置文件的属性文件,则不会再次导入。
导入附加数据
应用程序属性可以使用 spring.config.import 属性从其他位置导入更多配置数据。导入将在发现时进行处理,并被视为插入声明导入的文档下面的附加文档。
例如,类路径 application.properties 文件中可能包含以下内容:
Properties
spring.application.name=myapp
spring.config.import=optional:file:./dev.properties
Yaml
spring:
application:
name: "myapp"
config:
import: "optional:file:./dev.properties"
这将触发当前目录中 dev.properties 文件的导入(如果存在这样的文件)。导入的 dev.properties 中的值将优先于触发导入的文件。在上面的示例中,dev.properties 可以将 spring.application.name 重新定义为不同的值。
无论声明多少次,导入都只会导入一次。在 properties/yaml 文件中的单个文档中定义导入的顺序并不重要。例如,下面的两个示例产生相同的结果:
Properties
spring.config.import=my.properties
my.property=value
Yaml
spring:
config:
import: "my.properties"
my:
property: "value"
Properties
my.property=value
spring.config.import=my.properties
Yaml
my:
property: "value"
spring:
config:
import: "my.properties"
在上述两个示例中,my.properties 文件中的值将优先于触发其导入的文件。
可以在单个 spring.config.import 键下指定多个位置。位置将按照定义的顺序进行处理,后面导入的将优先。
注意:
在适当的时候,也会考虑导入特定配置文件的变量。上面的示例将同时导入 my.properties 和任何 my-<profile>.properties 变量。
TIP:
Spring Boot 包括可插拔API,允许支持各种不同的位置地址。默认情况下,可以导入 Java 属性、YAML和 “配置树”。
第三方 jar 可以提供对其他技术的支持(不要求文件是本地的)。例如,你可以想象配置数据来自 Consul、Apache ZooKeeper 或 Netflix Archaius 等外部存储。
如果要支持自己的位置,请查看 org.springframework.boot.context.config 包中的 ConfigDataLocationResolver 和 ConfigDataLoader 类。
导入无扩展名文件
某些云平台无法向卷装载的文件添加文件扩展名。要导入这些无扩展名文件,您需要给 Spring Boot 一个提示,以便它知道如何加载它们。您可以通过在方括号中放置扩展提示来完成此操作。
例如,假设您有一个 /etc/config/myconfig 文件,希望将其作为 yaml 导入。你可以使用以下命令从 application.properties 导入它:
Properties
spring.config.import=file:/etc/config/myconfig[.yaml]
Yaml
spring:
config:
import: "file:/etc/config/myconfig[.yaml]"
使用配置树
在云平台(如 Kubernetes)上运行应用程序时,通常需要读取平台提供的配置值。出于这种目的使用环境变量并不罕见,但这可能会有缺点,特别是如果值应该保密的话。
作为环境变量的替代方案,许多云平台现在允许您将配置映射到装载的数据卷中。例如,Kubernetes 可以卷装载 ConfigMaps 和 Secrets。
可以使用两种常见的卷装载模式:
- 一个文件包含一组完整的属性(通常写为 YAML)。
- 多个文件被写入目录树,其中文件名成为 “key”,内容成为 “value”。
对于第一种情况,可以像上面描述的那样直接使用 spring.config.import 导入 YAML 或 Properties 文件。对于第二种情况,需要使用 configtree: 前缀,以便 Spring Boot 知道它需要将所有文件公开为属性。
作为一个例子,让我们想象 Kubernetes 已经挂载了以下卷:
etc/
config/
myapp/
username
password
username 文件的内容将是一个配置值,password 的内容将是一个秘钥。
要导入这些属性,你可以将以下内容添加到你的 applation.properties 或 applation.yaml文件中:
Properties
spring.config.import=optional:configtree:/etc/config/
Yaml
spring:
config:
import: "optional:configtree:/etc/config/"
然后,可以按照通常的方式从 Environment 访问或注入 myapp.username 和 myapp.password 属性。
TIP:
配置树下的文件夹构成属性名称。在上述示例中,以 username 和 password 访问属性,可以将 spring.config.import 设置为 optional:configtree:/etc/config/myapp。
注意:
带点符号的文件名也被正确映射。例如,在上面的示例中,/etc/config 中名为 myapp.username 的文件将导致 Environment 中的 myapp.username 属性。
TIP:
配置树值可以绑定到字符串 String 和 byte[] 类型,具体取决于预期的内容。
如果要从同一父文件夹导入多个配置树,则可以使用通配符快捷方式。任何以 /*/ 结尾的 configtree:location 都会将所有直接子级作为配置树导入。
例如,给定以下卷:
etc/
config/
dbconfig/
db/
username
password
mqconfig/
mq/
username
password
你可以使用 configtree:/etc/config/*/ 作为导入位置:
Properties
spring.config.import=optional:configtree:/etc/config/*/
Yaml
spring:
config:
import: "optional:configtree:/etc/config/*/"
这将添加 db.username, db.password, mq.username 和 mq.password 属性。
注意:
使用通配符加载的目录按字母顺序排序。如果你需要不同的顺序,则应将每个位置作为单独的导入列出。
配置树也可以用于 Docker 秘钥。授予 Docker 群服务时访问一个秘钥,该秘钥被装入容器中。例如,如果一个名为 db.password 安装在 /run/secrets/ 位置,您可以使 db.password 对 Spring 可用环境,使用以下选项:
Properties
spring.config.import=optional:configtree:/run/secrets/
Yaml
spring:
config:
import: "optional:configtree:/run/secrets/"
属性占位符
application.properties 和 application.yml 中的值在使用时会通过现有的 Environment 进行过滤,因此可以重新引用以前定义的值(例如,从系统属性或环境变量) 。标准的 ${name} 属性占位符语法可以在值内的任何位置使用。属性占位符还可以使用 : 指定默认值,以将默认值与属性名称分开,例如 ${name:default}。
以下示例显示了带默认值和不带默认值的占位符的使用:
Properties
app.name=MyApp
app.description=${app.name} is a Spring Boot application written by ${username:Unknown}
Yaml
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。
注意:
你应该始终使用占位符中的规范形式(仅使用小写字母的串形写法)引用占位符中的属性名称。这将允许 Spring Boot 使用与放松绑定 @ConfigurationProperties 时相同的逻辑。
例如,${demo.item-price} 将从 application.properties 文件中获取 demo.iterm-price 和 demo.itemPrice 形式数据,并从系统环境中获取 DEMO_ITEMPRICE。如果改用 ${demo.itemPrice},则不会考虑 demo.item-price 和 DEMO_ITEMPRICE。
TIP:
你还可以使用此技术来创建现有 Spring Boot 属性的 “short” 变量。
使用多文档文件
Spring Boot 允许你将单个物理文件拆分为多个逻辑文档,每个逻辑文档都是独立添加的。文档按照从上到下的顺序进行处理。后续文档可以覆盖早期文档中定义的属性。
对于 application.yml 文件,使用标准的 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
注意:
属性文件分隔符不能有任何前导空格,并且必须正好有三个连字符。分隔符前后的行不能是相同的注释前缀。
TIP:
多文档属性文件通常与激活属性(如 spring.config.activate.on-profile)结合使用。
警告:
无法使用 @PropertySource 或 @TestPropertySource 批注加载多文档属性文件。
激活属性
有时,仅在满足某些条件时激活一组给定的属性非常有用。例如,你可能具有仅在特定配置文件处于活动状态时才相关的属性。
你可以使用 spring.config.activate.* 有条件地激活属性文档。
以下激活属性可用:
| 属性 | 说明 |
|---|---|
| on-profile | 必须匹配才能激活文档的配置文件表达式。 |
| on-cloud-platform | 要使文档处于活动状态,必须检测到的CloudPlatform。 |
例如,下面指定第二个文档仅在 Kubernetes 上运行时有效,并且仅在 “prod” 或 “staging” 配置文件处于活动状态时有效:
Properties
myprop=always-set
#---
spring.config.activate.on-cloud-platform=kubernetes
spring.config.activate.on-profile=prod | staging
myotherprop=sometimes-set
Yaml
myprop:
"always-set"
---
spring:
config:
activate:
on-cloud-platform: "kubernetes"
on-profile: "prod | staging"
myotherprop: "sometimes-set"
4.加密属性
Spring Boot 不提供任何内置的对加密属性值的支持,但是,它提供了修改 Spring 环境中包含的值所必需的钩子点。EnvironmentPostProcessor 接口允许你在应用程序启动之前操作环境。
如果你需要一种安全的方式来存储凭证和密码,请使用 Spring Cloud Vault 项目提供在 HashiCorp Vault 中存储外部化配置的支持。
5.使用 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
TIP:
使用 [index] 表示法的属性可以使用 Spring Boot 的 Binder 类绑定到 Java List 或 Set 对象。
警告:
YAML 文件不能通过使用 @PropertySource 或 @TestPropertySource 注解来加载。因此,在需要以这种方式加载值的情况下,需要使用 properties 文件。
直接加载 YAML
Spring Framework 提供了两个方便的类,可用于加载 YAML 文档。YamlPropertiesFactoryBean 将 YAML 作为 Properties 加载,YamlMapFactoryBean 将 YAML 作为 Map 加载。
如果要将 YAML 作为 Spring PropertySource 加载,也可以使用 YamlPropertySourceLoader 类。
6.配置随机值
RandomValuePropertySource 用于注入随机值(例如,注入秘钥或测试用例)。它可以生成整数、longs、uuids 或字符串,如下例所示:
Properties
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]}
Yaml
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 为最大值(不包括)。
7.配置系统环境属性
Spring Boot 支持为环境属性设置前缀。如果系统环境由多个具有不同配置要求的 Spring Boot 应用程序共享,这将非常有用。可以在 SpringApplication 上直接设置系统环境属性的前缀。
例如,如果将前缀设置为 input,则 remote.timeout 等属性也将解析为系统环境中的 input.remote.timeout。
8.类型安全配置属性
使用 @Value("${property}") 注解来注入配置属性有时会很麻烦,特别是如果你正在处理多个属性,或者你的数据本质上是分层的。Spring Boot 提供了另一种使用属性的方法,该方法允许强类型 bean 管理和验证应用程序的配置。
TIP:
可以查看 @Value 和类型安全配置属性之间的区别。
JavaBean 属性绑定
可以绑定一个 bean 声明标准 JavaBean 属性,如下例所示:
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@Component
@ConfigurationProperties("my.service")
public class MyProperties {
private boolean enabled;
private InetAddress remoteAddress;
private final Security security = new Security();
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"));
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public List<String> getRoles() {
return roles;
}
public void setRoles(List<String> roles) {
this.roles = roles;
}
}
}
前面的 POJO 定义了以下属性:
my.service.enabled,默认值为false;my.service.remote-address,使用可以从String强制转换的类型;my.service.security.username,使用嵌套的 “security” 对象,其名称由属性的名称确定。特别是,这里根本没有使用该类型,可能是SecurityProperties;my.service.security.password;my.service.security.roles,具有默认为USER的String集合。
Yaml
my:
service:
enabled: false
remote-address: ""
security:
username: "admin"
password: "admin"
roles:
- USER
注意:
通过 properties 文件、YAML 文件、环境变量和其他机制配置的映射到 Spring Boot 中可用的 @ConfigurationProperties 类的属性是公共 API,但类本身的访问器( getters/setters)不能直接使用。
这种安排依赖于默认的空构造函数,getter 和 setter 通常是强制性的,因为绑定是通过标准的 JavaBeans 属性描述符进行的,就像在 SpringMVC 中一样。在下列情况下,可以省略设置器:
- Maps,只要它们被初始化,就需要一个 getter,但不一定需要一个 setter,因为它们可以被绑定器改变;
- 可以通过索引(通常使用 YAML)或使用单个逗号分隔值(属性)访问集合和数组。在后一种情况下,必须使用 setter。我们建议始终为这类类型添加 setter。如果初始化集合,请确保它不是不可变的(如上例所示);
- 如果初始化了嵌套的 POJO 属性(如上例中的
Security字段),则不需要 setter。如果希望绑定器创建实例。通过使用它的默认构造函数,你需要一个 setter。
有些人使用 Project Lombok 自动添加 getter 和 setter。确保 Lombok 不会为这种类型生成任何特定的构造函数,因为容器会自动使用它来实例化对象。
最后,只考虑标准的 Java Bean 属性,不支持绑定静态属性。
构造函数绑定
上一节中的示例可以以不可变的方式重写,如下例所示:
import java.net.InetAddress;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.ConstructorBinding;
import org.springframework.boot.context.properties.bind.DefaultValue;
import org.springframework.stereotype.Component;
@Component
@ConstructorBinding
@ConfigurationProperties("my.service")
public class MyProperties {
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;
}
public boolean isEnabled() {
return this.enabled;
}
public InetAddress getRemoteAddress() {
return this.remoteAddress;
}
public Security getSecurity() {
return this.security;
}
public static class Security {
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;
}
public String getUsername() {
return this.username;
}
public String getPassword() {
return this.password;
}
public List<String> getRoles() {
return this.roles;
}
}
}
在此设置中,@ConstructorBinding 注解用于指示应使用构造函数绑定。这意味着绑定器将期望找到一个具有你希望绑定的参数的构造函数。如果你使用的是 Java16 或更高版本,则可以对记录使用构造函数绑定。在这种情况下,除非记录有多个构造函数,否则不需要使用 @ConstructorBinding。
@ConstructorBinding 类的嵌套成员(例如上面示例中的 Security)也将通过其构造函数绑定。
默认值可以在构造函数参数上使用 @DefaultValue 指定,或者在使用 Java 16 或更高版本时使用记录组件指定。转换服务将应用于将 String 值强制转换为缺失属性的目标类型。
参考前面的示例,如果没有属性绑定到 Security, MyProperties 实例将包含一个用于安全性的 null 值。为了使它包含一个非空的 Security 实例,即使没有属性绑定到它(在使用 Kotlin 时,这将要求 Security 的 username 和 password 参数被声明为空,因为它们没有默认值),使用一个空的 @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)
TIP:
如果你的类有多个构造函数,你也可以在应该绑定的构造函数上直接使用 @ConstructorBinding。
注意:
不建议 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 {
}
要使用配置属性扫描,请将 @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 具有常规名称:<prefix>-<fqn> ,其中 <prefix> 是在 @ConfigurationProperties 注解和 <fqn> 是 bean 的完全限定名称。如果注释不提供任何前缀,则只使用 bean 的完全限定名称。
上面示例中的 bean 名称是 com.example.app-com.example.app.SomeProperties。
我们建议 @ConfigurationProperties 只处理环境,特别是不要从上下文注入其他 Bean。对于特殊情况,可以使用 setter 注入或框架提供的任何 *Aware 接口( 例如,如果你需要访问 Environment,则可以使用 EnvironmentAware)。如果你仍然希望使用构造函数注入其他 Bean,则配置属性 Bean 必须使用 @Component 进行注释,并使用基于 JavaBean 的属性绑定。
使用 @ConfigurationProperties 注解类型
这种类型的配置在 SpringApplication 外部 YAML 配置中尤其适用,如下例所示:
my:
service:
remote-address: 192.168.1.1
security:
username: "admin"
roles:
- "USER"
- "ADMIN"
要使用 @ConfigurationProperties bean,可以以与任何其他 bean 相同的方式注入它们,如下例所示:
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();
// ...
}
// ...
}
TIP:
使用 @ConfigurationProperties 还可以生成元数据文件,ide 可以使用该文件为自己的键提供自动补全功能。
第三方配置
除了使用 @ConfigurationProperties 注释类之外,还可以在公共 @Bean 方法上使用它。当你想要将属性绑定到你无法控制的第三方组件时,这样做可能特别有用。
要从 Environment 属性配置 Bean,请将 @ConfigurationProperties 添加到其 Bean 注册中,如下例所示:
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;
}
}
在前面的代码中,可以使用以下属性名称:
| 属性 | 说明 |
|---|---|
| my.main-project.person.first-name | 串行大小写格式,建议在 .properties 和 .yml 文件中使用 |
| my.main-project.person.firstName | 标准的驼峰大小写语法 |
| my.main-project.person.first_name | 下划线表示法,这是在 .properties 和 .yml 文件中使用的另一种格式 |
| MY_MAINPROJECT_PERSON_FIRSTNAME | 大写格式,使用系统环境变量时建议使用 |
注意:
注解的 prefix 必须是串行大小写(小写并用 - 分隔,例如 my.main-project.person)。
| 属性源 | 普通 | 列表 |
|---|---|---|
| Properties 文件 | 骆驼式,串行式,或者下划线符号 | 使用 [ ] 或逗号分隔值的标准列表语法 |
| YAML 文件 | 骆驼式,串行式,或者下划线符号 | 标准 YAML 列表语法或逗号分隔值 |
| 环境变量 | 以下划线作为分隔符的大写格式 | 由下划线包围的数值 |
| 系统属性 | 骆驼式,串行式,或者下划线符号 | 使用 [ ] 或逗号分隔值的标准列表语法 |
TIP:
我们建议尽可能以小写的串行格式存储属性,例如 my.person.first-name=Rod。
绑定 Map
绑定到 Map 属性时,可能需要使用特殊的括号表示法,以便保留原始键值。如果键没有用 [] 括起来,则为非字母数字、- 或 . 的任何字符都被移除了。
例如,考虑将以下属性绑定到 Map<String,String>:
Properties
my.map.[/key1]=value1
my.map.[/key2]=value2
my.map./key3=value3
Yaml
my:
map:
"[/key1]": "value1"
"[/key2]": "value2"
"/key3": "value3"
注意:
对于 YAML 文件,括号需要用引号括起来,以便正确地解析键。
上面的属性将绑定到一个 Map,其中 /key1、/key2 和 key3 是映射中的键。已将斜杠从 key3 中删除,因为它没有用方括号括起来。
绑定到标量值时,其中携带 . 的键不需要被 [] 包围。标量值包括枚举和 java.lang 包中除 Object 之外的所有类型。将 a.b=c 绑定到 Map<String,String> 将保留在键中的 . 并返回带有条目 {"a.b"="c"} 的 Map。对于任何其他类型,如果键包含 . 。例如,将 a.b=c 绑定到 Map<String,Object> 将返回条目为 {"a"={"b"="c"}} 的 Map,而 [a.b]=c 将返回条目 {"a.b"="c"} 的 Map。
从环境变量绑定
大多数操作系统对可用于环境变量的名称施加严格的规则。例如,Linux shell 变量只能包含字母(a 到 z 或 A 到 Z)、数字(0 到 9)或下划线字符(_)。按照惯例,Unix shell 变量的名称也将以大写字母表示。
Spring Boot 的宽松绑定规则尽可能与这些命名限制兼容。
要将规范形式的属性名称转换为环境变量名称,可以遵循以下规则:
- 将点(
.)替换为下划线(_); - 删除所有破折号(
-); - 转换为大写。
例如,配置属性 spring.main.log-startup-info 将是一个名为 SPRING_MAIN_LOGSTARTUPINFO 的环境变量。
当绑定到对象列表时,也可以使用环境变量。要绑定到 List,元素序号在变量名中应该用下划线括起来。
例如,配置属性 my.service[0].other 将使用名为 MY_SERVICE_0_OTHER 的环境变量。
合并复杂类型
当在多个地方配置列表时,重写将通过替换整个列表来工作。
例如,假设 MyPojo 对象的 name 和 description 属性默认为 null。下面的例子显示了 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;
}
}
考虑以下配置:
Properties
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
Yaml
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 实例添加到列表中,也不会合并项目。
当在多个配置文件中指定 List 时,将使用具有最高优先级的配置文件(且仅使用该配置文件)。请考虑以下示例:
Properties
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
Yaml
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>:
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;
}
}
考虑以下配置:
Properties
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
Yaml
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 的条目(名称为 myname 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)或自定义 Converters( 带有注解为 @ConfigurationPropertiesBinding 的 bean 定义)。
注意:
由于此 Bean 在应用程序生命周期的早期就被请求,因此请确保限制 ConversionService 正在使用的依赖项。通常,在创建时可能不会完全初始化所需的任何依赖项。如果配置键强制不需要自定义 ConversionService,并且仅依赖于使用 @ConfigurationPropertiesBinding 限定的自定义转换器,则可能需要重命名该自定义 ConversionService。
转换时间
Spring Boot 对表示持续时间提供了专门的支持。如果你暴露 java.time.Duration 属性时,应用程序属性中的以下格式可用:
- 常规的
long表示法(使用毫秒作为默认单位,除非已指定@DurationUnit) java.time.Duration使用的标准 ISO-8601 格式- 一种更易读的格式,其中值和单位是耦合的(
10s表示 10 秒)
请考虑以下示例:
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);
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 覆盖,如上面的示例所示。
如果你更喜欢使用构造函数绑定,可以暴露相同的属性,如下例所示:
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.ConstructorBinding;
import org.springframework.boot.context.properties.bind.DefaultValue;
import org.springframework.boot.convert.DurationUnit;
@ConfigurationProperties("my")
@ConstructorBinding
public class MyProperties {
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;
}
public Duration getSessionTimeout() {
return this.sessionTimeout;
}
public Duration getReadTimeout() {
return this.readTimeout;
}
}
TIP:
如果要升级 Long 属性,如果单位不是毫秒,请确保定义单位(使用 @DurationUnit)。这样做提供了一条透明的升级路径,同时支持更丰富的格式。
转换时间
除了持续时间,Spring Boot 还可以使用 java.time.Period 类型。以下格式可以在应用程序属性中使用:
- 常规
int表示(使用天数作为默认单位,除非指定了@PeriodUnit) java.time.Period使用的标准 ISO-8601 格式- 更简单的格式,其中值和单位对是耦合的(
1y3d表示 1 年和 3 天)
简单格式支持以下单位:
- y 年份
- m 月份
- w 周
- d 天
注意:
java.time.Period 类型实际上从未存储周数,它是一个表示 “7天” 的快捷方式。
转换数据大小
Spring Framework 有一个 DataSize 值类型,它以字节为单位表示大小。如果暴露 DataSize 属性,则应用程序属性中的以下格式可用:
- 常规
long表示(使用字节作为默认单位,除非指定了@DataSizeUnit) - 一种更可读的格式,其中值和单位是耦合的(
10MB表示 10 兆字节)
考虑以下示例:
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);
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 重写,如上面的示例所示。
如果你更喜欢使用构造函数绑定,可以暴露相同的属性,如下例所示:
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.ConstructorBinding;
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")
@ConstructorBinding
public class MyProperties {
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;
}
public DataSize getBufferSize() {
return this.bufferSize;
}
public DataSize getSizeThreshold() {
return this.sizeThreshold;
}
}
TIP:
如果要升级 Long 属性,如果不是字节,请确保定义单位(使用 @DataSizeUnit)。这样做可以提供透明的升级路径,同时支持更丰富的格式。
@ConfigurationProperties 验证
当 @ConfigurationProperties 类被 Spring 的 @Validated 注解注释时,Spring Boot 会尝试验证它们。你可以直接在配置类上使用 JSR-303 javax.validation 约束注解。要做到这一点,请确保类路径上有一个兼容的 JSR-303 实现,然后向字段添加约束注释,如下例所示:
import java.net.InetAddress;
import javax.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;
public InetAddress getRemoteAddress() {
return this.remoteAddress;
}
public void setRemoteAddress(InetAddress remoteAddress) {
this.remoteAddress = remoteAddress;
}
}
TIP:
还可以通过用 @Validated 注解创建配置属性的 @Bean 方法来触发验证。
为了确保总是触发嵌套属性的验证,即使没有找到属性,关联的字段也必须用 @Valid 标注。下面的示例建立在前面的 MyProperties 示例之上:
import java.net.InetAddress;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.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();
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;
public String getUsername() {
return this.username;
}
public void setUsername(String username) {
this.username = username;
}
}
}
你还可以通过创建一个名为 ConfigurationPropertiesValidator 的 Bean 定义来添加一个定制的 Spring Validator。@Bean 方法应该声明为静态的。配置属性验证器是在应用程序生命周期的早期创建的,如果将 @Bean 方法声明为静态方法,则无需实例化 @Configuration 类即可创建 Bean。这样做可以避免早期实例化可能导致的任何问题。
TIP:
spring-boot-actuator 模块包括一个端点,它暴露所有 @ConfigurationProperties bean。将 web 浏览器指向 /actuator/configprops 或使用等效的 JMX 端点。
@ConfigurationProperties 和 @Value
@Value 注解是核心容器功能,它不提供与类型安全配置属性相同的功能。下表总结了 @ConfigurationProperties 和 @Value 支持的功能:
| 特性 | @ConfigurationProperties | @Value |
|---|---|---|
| 宽松绑定 | √ | 限制 |
| 元数据支持 | √ | × |
| SpEL | × | √ |
注意:
如果你确实想使用 @Value,我们建议你使用规范形式引用属性名称(串行大小写仅使用小写字母)。这将允许 Spring Boot 使用与放松绑定 @ConfigurationProperties 时相同的逻辑。
例如,@Value("${demo.Item-Price}") 将从 application.properties 文件以及从系统环境 DEMO_ITEMPRICE 中获取 demo.item-price 和 demo.itemPrice 数据。如果你使用 @Value("${demo.itemPrice}"),则不会考虑 demo.item-price 和 DEMO_ITEMPRICE。
如果为自己的组件定义了一组配置键,我们建议将它们分组到带有 @ConfigurationProperties 注释的 POJO 中。这样做将为你提供结构化的类型安全对象,你可以将其注入到自己的 bean 中。
在解析这些文件并填充环境时,不会处理应用程序属性文件中的 SpEL 表达式。但是,可以在 @Value 中编写 SpEL 表达式。如果应用程序属性文件中的属性值是 SpEL 表达式,则在通过 @Value 使用时将对其求值。
