外部化配置
Spring Boot 允许您外部化配置,这样您就可以在不同的环境中使用相同的应用程序代码。 您可以使用各种外部配置源,包括 Java properties 文件、YAML 文件、环境变量和命令行参数。
属性值可以通过使用 @Value
注解直接注入到您的 bean 中,通过 Spring 的 Environment
抽象访问,或者通过 @ConfigurationProperties
绑定到结构化对象。
Spring Boot 使用一个非常特殊的 PropertySource
顺序,该顺序旨在允许合理地覆盖值。
后面的属性源可以覆盖前面定义的值。
按以下顺序考虑源:
-
默认属性(通过设置
SpringApplication.setDefaultProperties(Map)
指定)。 -
@PropertySource
注解在您的@Configuration
类上。 请注意,在应用程序上下文被刷新之前,这些属性源不会被添加到Environment
中。 这对于配置某些属性来说太晚了,比如logging.*
和spring.main.*
,这些属性在刷新开始之前就被读取了。 -
配置数据(如
application.properties
文件)。 -
一个
RandomValuePropertySource
,它只在random.*
中有属性。 -
操作系统环境变量。
-
Java 系统属性(
System.getProperties()
)。 -
来自
java:comp/env
的 JNDI 属性。 -
ServletContext
初始化参数。 -
ServletConfig
初始化参数。 -
来自
SPRING_APPLICATION_JSON
的属性(嵌入在环境变量或系统属性中的内联 JSON)。 -
命令行参数。
-
测试中的
properties
属性。 可用于@SpringBootTest
和用于测试应用程序特定部分的 测试注解。 -
测试中的
@DynamicPropertySource
注解。 -
测试中的
@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
和 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"
)。
提示:env
和 configprops
端点对于确定属性具有特定值的原因很有用。
您可以使用这两个端点来诊断意外的属性值。
有关详细信息,请参见 生产就绪功能部分。
访问命令行属性
默认情况下,SpringApplication
将任何命令行选项参数(即以 --
开头的参数,如 --server.port=9000
)转换为 property
并将其添加到 Spring Environment
中。
如前所述,命令行属性始终优先于基于文件的属性源。
如果您不希望将命令行属性添加到 Environment
中,可以通过使用 SpringApplication.setAddCommandLineProperties(false)
来禁用它们。
JSON 应用程序属性
环境变量和系统属性通常有限制,这意味着某些属性名称不能使用。 为了帮助解决这个问题,Spring Boot 允许您将一组属性编码到单个 JSON 结构中。
当您的应用程序启动时,任何 spring.application.json
或 SPRING_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
值将被添加到结果属性源中,但 PropertySourcesPropertyResolver
将 null
属性视为缺失值。
这意味着 JSON 不能使用 null
值覆盖较低优先级属性源中的属性。
外部应用程序属性
当您的应用程序启动时,Spring Boot 将自动从以下位置查找并加载 application.properties
和 application.yaml
文件:
-
从类路径
-
类路径根目录
-
类路径
/config
包
-
-
从当前目录
-
当前目录
-
当前目录中的
config/
子目录 -
config/
子目录的直接子目录
-
该列表按优先级排序(较低项的值覆盖较早的值)。
加载的文件中的文档作为 PropertySource
实例添加到 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
提示:如果 位置是可选的 并且您不介意它们不存在,请使用前缀 optional:
。
警告:spring.config.name
、spring.config.location
和 spring.config.additional-location
在早期用于确定必须加载哪些文件。
它们必须定义为环境属性(通常是操作系统环境变量、系统属性或命令行参数)。
如果 spring.config.location
包含目录(而不是文件),它们应该以 /
结尾。
在运行时,它们将在加载之前附加从 spring.config.name
生成的名称。
spring.config.location
中指定的文件将直接导入。
注意:目录和文件位置值也会被扩展以检查 特定于配置文件的文件。
例如,如果您有 spring.config.location
为 classpath: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/
,则考虑的完整位置集为:
-
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 Boot 将抛出 ConfigDataLocationNotFoundException
并且您的应用程序将不会启动。
如果您想指定一个位置,但不介意它是否总是存在,可以使用 optional:
前缀。
您可以将此前缀与 spring.config.location
和 spring.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.location
和 spring.config.additional-location
属性自己使用通配符位置。
注意:通配符位置必须只包含一个 *
,并且对于目录的搜索位置以 */
结尾,对于文件的搜索位置以 */<filename>
结尾。
带有通配符的位置根据文件名的绝对路径按字母顺序排序。
提示:通配符位置仅适用于外部目录。
您不能在 classpath:
位置中使用通配符。
特定于配置文件的文件
除了 application
属性文件外,Spring Boot 还将尝试使用命名约定 application-{profile}
加载特定于配置文件的文件。
例如,如果您的应用程序激活了名为 prod
的配置文件并使用 YAML 文件,则将考虑 application.yaml
和 application-prod.yaml
。
特定于配置文件的属性从与标准 application.properties
相同的位置加载,特定于配置文件的文件始终覆盖非特定文件。
如果指定了多个配置文件,则应用最后获胜策略。
例如,如果通过 spring.profiles.active
属性指定了配置文件 prod,live
,则 application-prod.properties
中的值可以被 application-live.properties
中的值覆盖。
最后获胜策略在 位置组级别应用。
例如,继续上面的 /cfg application-live.properties /ext application-live.properties application-prod.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.properties
。
optional: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。 如果您想支持自己的位置,请参见 |
导入无扩展名文件
某些云平台无法为卷挂载的文件添加文件扩展名。 要导入这些无扩展名的文件,您需要给 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)上运行应用程序时,您通常需要读取平台提供的配置值。 为此使用环境变量并不罕见,但这可能有缺点,特别是如果该值应该是保密的。
作为环境变量的替代方案,许多云平台现在允许您将配置映射到挂载的数据卷中。
例如,Kubernetes 可以卷挂载 ConfigMaps
和 Secrets
。
可以使用两种常见的卷挂载模式:
-
单个文件包含完整的属性集(通常以 YAML 编写)。
-
多个文件写入目录树,文件名成为"键",内容成为"值"。
对于第一种情况,您可以使用 spring.config.import
直接导入 YAML 或 Properties 文件,如 上面所述。
对于第二种情况,您需要使用 configtree:
前缀,以便 Spring Boot 知道它需要将所有文件作为属性公开。
例如,假设 Kubernetes 已挂载以下卷:
etc/
config/
myapp/
username
password
username
文件的内容将是一个配置值,password
文件的内容将是一个密钥。
要导入这些属性,您可以在 application.properties
或 application.yaml
文件中添加以下内容:
-
Properties
-
YAML
spring.config.import=optional:configtree:/etc/config/
spring:
config:
import: "optional:configtree:/etc/config/"
然后,您可以像往常一样从 Environment
访问或注入 myapp.username
和 myapp.password
属性。
提示:配置树下文件夹和文件的名称形成属性名称。
在上面的示例中,要将属性作为 username
和 password
访问,您可以将 spring.config.import
设置为 optional:configtree:/etc/config/myapp
。
注意:带点表示法的文件名也会被正确映射。
例如,在上面的示例中,/etc/config
中名为 myapp.username
的文件将导致 Environment
中的 myapp.username
属性。
提示:配置树值可以根据预期的内容绑定到 String
和 byte[]
类型。
如果您有多个配置树要从同一父文件夹导入,您可以使用通配符快捷方式。
任何以 /*/
结尾的 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.username
、db.password
、mq.username
和 mq.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.properties
和 application.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 在 宽松绑定 例如, |
你也可以用这种技术为现有 Spring Boot 属性创建"短"变体。 详见"如何指南"中的 使用"短"命令行参数。 |
多文档文件的使用
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 注解加载。
|
激活属性
有时你可能希望只在满足某些条件时激活一组属性。 例如,你可能有些属性只在特定 profile 激活时才有意义。
你可以通过 spring.config.activate.*
有条件地激活属性文档。
可用的激活属性如下:
Property | Note |
---|---|
|
文档激活时必须匹配的 profile 表达式,或至少有一个匹配的 profile 表达式列表。 |
|
文档激活时必须检测到的 |
例如,下面指定第二个文档只在 Kubernetes 上运行且 prod
或 staging
profile 激活时才生效:
-
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 的超集,因此是指定层次化配置数据的便捷格式。
SpringApplication
类只要类路径中有 SnakeYAML 库,就会自动支持 YAML 作为 properties 的替代方案。
注意:如果你使用 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
YAML 文件不能通过 @PropertySource 或 @TestPropertySource 注解加载。
如果你需要以这种方式加载值,请使用 properties 文件。
|
直接加载 YAML
Spring Framework 提供了两个便捷类用于加载 YAML 文档。
YamlPropertiesFactoryBean
以 Properties
形式加载 YAML,YamlMapFactoryBean
以 Map
形式加载 YAML。
如果你想以 Spring PropertySource
形式加载 YAML,也可以使用 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 应用共享且配置需求不同,这很有用。
可以在运行应用前通过调用 SpringApplication
的 setEnvironmentPrefix(…)
方法直接设置系统环境属性前缀。
例如,如果你将前缀设置为 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
,一个默认值为USER
的String
集合。
如果属性名中有保留关键字,如 my.service.import ,可在属性字段上使用 @Name 注解。
|
注意:映射到 Spring Boot 中 @ConfigurationProperties
类的属性(通过 properties 文件、YAML 文件、环境变量等机制配置)是公共 API,但类本身的访问器(getter/setter)不建议直接使用。
这种方式依赖于默认的无参构造函数,getter 和 setter 通常是必须的,因为绑定是通过标准 Java Beans 属性描述符完成的,就像在 Spring MVC 中一样。 以下情况可以省略 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>)
}
在这种设置下,存在单个带参数构造函数意味着应使用构造器绑定。
这意味着 binder 会找到你希望绑定参数的构造函数。
如果你的类有多个构造函数,可以用 @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
}
构造器绑定可用于 record。除非 record 有多个构造函数,否则无需使用 @ConstructorBinding
。
构造器绑定类的嵌套成员(如上例中的 Security
)也会通过其构造函数绑定。
可用 @DefaultValue
注解在构造函数参数和 record 组件上指定默认值。转换服务会将注解的 String
值转换为缺失属性的目标类型。
参考前例,如果没有属性绑定到 Security
,MyProperties
实例中的 security
会是 null
。要让其即使没有属性绑定也包含非 null 的 Security
实例(Kotlin 下这要求 Security
的 username
和 password
参数声明为可空且无默认值),可用空的 @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 或 @Import
加载的 bean)使用构造器绑定。
注意:要使用构造器绑定,类必须用 -parameters
编译。如果你用 Spring Boot 的 Gradle 插件或 Maven 的 spring-boot-starter-parent
,会自动这样做。
注意:不推荐在 @ConfigurationProperties
中使用 Optional
,它主要用于返回类型,不适合配置属性注入。为与其他类型属性一致,如果你声明了 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
当通过配置属性扫描或 假设在 |
我们建议 @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 一样注入它们,如下例所示:
-
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 还可以生成元数据文件,IDE 可用其为你的自定义键提供自动补全。详见 附录。
|
第三方配置
除了使用 @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(prefix = "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(prefix = "another")
fun anotherComponent(): AnotherComponent = AnotherComponent()
}
任何使用 another
前缀定义的 JavaBean 属性都会以类似于前面的 SomeProperties
示例的方式映射到该 AnotherComponent
bean 上。
宽松绑定
Spring Boot 使用一些宽松的规则来将 Environment
属性绑定到 @ConfigurationProperties
bean,因此 Environment
属性名称和 bean 属性名称之间不需要完全匹配。
这种宽松绑定的常见例子包括使用短横线分隔的环境属性(例如,context-path
绑定到 contextPath
)和大写的环境属性(例如,PORT
绑定到 port
)。
例如,考虑以下 @ConfigurationProperties
类:
-
Java
-
Kotlin
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;
}
}
import org.springframework.boot.context.properties.ConfigurationProperties
@ConfigurationProperties(prefix = "my.main-project.person")
class MyPersonProperties {
var firstName: String? = null
}
使用上述代码,可以使用以下所有属性名称:
属性 | 说明 |
---|---|
|
短横线命名法,推荐在 |
|
标准驼峰命名法语法。 |
|
下划线表示法,是 |
|
大写格式,推荐在使用系统环境变量时使用。 |
注意:注解的 prefix
值 必须 使用短横线命名法(小写并用 -
分隔,如 my.main-project.person
)。
属性源 | 简单属性 | 列表 |
---|---|---|
Properties 文件 |
驼峰命名法、短横线命名法或下划线表示法 |
使用 |
YAML 文件 |
驼峰命名法、短横线命名法或下划线表示法 |
标准 YAML 列表语法或逗号分隔的值 |
环境变量 |
使用下划线作为分隔符的大写格式(参见 从环境变量绑定)。 |
用下划线包围的数值(参见 从环境变量绑定) |
系统属性 |
驼峰命名法、短横线命名法或下划线表示法 |
使用 |
提示:我们建议,在可能的情况下,属性以短横线命名法的小写格式存储,例如 my.person.first-name=Rod
。
绑定 Map
当绑定到 Map
属性时,你可能需要使用特殊的括号表示法,以便保留原始的 key
值。
如果 key 没有被 []
包围,任何非字母数字、-
或 .
的字符都会被删除。
例如,考虑将以下属性绑定到 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 文件,key 需要用引号包围括号才能正确解析。
上述属性将绑定到一个 Map
,其中 /key1
、/key2
和 key3
作为 map 中的键。
key3
中的斜杠被删除是因为它没有被方括号包围。
当绑定到标量值时,包含 .
的键不需要用 []
包围。
标量值包括枚举和 java.lang
包中除 Object
外的所有类型。
将 a.b=c
绑定到 Map<String, String>
将保留键中的 .
并返回包含 {"a.b"="c"}
条目的 Map。
对于任何其他类型,如果 key
包含 .
,你需要使用括号表示法。
例如,将 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
的环境变量。
从环境变量绑定的支持应用于 systemEnvironment
属性源和任何名称以 -systemEnvironment
结尾的附加属性源。
从环境变量绑定 Map
当 Spring Boot 将环境变量绑定到属性类时,它会在绑定之前将环境变量名称转换为小写。
大多数情况下这个细节并不重要,但在绑定到 Map
属性时除外。
Map
中的键始终是小写的,如下例所示:
-
Java
-
Kotlin
import java.util.HashMap;
import java.util.Map;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "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(prefix = "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
对象,其 name
和 description
属性默认为 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
profile 未激活,MyProperties.list
包含一个 MyPojo
条目,如前所述。
但是,如果启用了 dev
profile,list
仍然 只包含一个条目(名称为 my another name
,description 为 null
)。
此配置_不会_向列表添加第二个 MyPojo
实例,也不会合并项目。
当 List
在多个 profile 中指定时,使用优先级最高的那个(且仅使用那个)。
考虑以下示例:
-
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
profile 处于活动状态,MyProperties.list
包含_一个_ MyPojo
条目(名称为 my another name
,description 为 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
profile 未激活,MyProperties.map
包含一个键为 key1
的条目(名称为 my name 1
,description 为 my description 1
)。
但是,如果启用了 dev
profile,map
包含两个条目,键为 key1
(名称为 dev name 1
,description 为 my description 1
)和 key2
(名称为 dev name 2
,description 为 dev description 2
)。
注意:前面的合并规则适用于所有属性源的属性,而不仅仅是文件。
属性转换
Spring Boot 在绑定到 @ConfigurationProperties
bean 时,会尝试将外部应用程序属性强制转换为正确的类型。
如果你需要自定义类型转换,你可以提供一个 ConversionService
bean(名为 conversionService
)或自定义属性编辑器(通过 CustomEditorConfigurer
bean)或自定义转换器(使用 @ConfigurationPropertiesBinding
注解的 bean 定义)。
用于属性转换的 bean 在应用程序生命周期的早期就被请求,因此请确保限制你的 ConversionService
使用的依赖项。
通常,你需要的任何依赖项在创建时可能尚未完全初始化。
提示:如果你的自定义 ConversionService
不是配置键强制转换所必需的,你可能想要重命名它,并且只依赖使用 @ConfigurationPropertiesBinding
限定的自定义转换器。
当使用 @ConfigurationPropertiesBinding
限定 @Bean
方法时,该方法应该是 static
的,以避免 “bean is not eligible for getting processed by all BeanPostProcessors” 警告。
转换持续时间
Spring Boot 对表达持续时间有专门的支持。
如果你暴露一个 Duration
属性,在应用程序属性中可以使用以下格式:
-
常规的
long
表示(除非指定了@DurationUnit
,否则使用毫秒作为默认单位) -
Duration
使用的标准 ISO-8601 格式 docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html#parse(java.lang.CharSequence) -
更易读的格式,其中值和单位是耦合的(
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 秒的会话超时,30
、PT30S
和 30s
都是等效的。
500 毫秒的读取超时可以指定为以下任何形式:500
、PT0.5S
和 500ms
。
你还可以使用任何支持的单位。 这些是:
-
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
,否则使用天作为默认单位) -
Period
使用的标准 ISO-8601 格式 docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Period.html#parse(java.lang.CharSequence) -
更简单的格式,其中值和单位对是耦合的(
1y3d
表示 1 年和 3 天)
简单格式支持以下单位:
-
y
表示年 -
m
表示月 -
w
表示周 -
d
表示天
注意:Period
类型实际上从不存储周数,它是一个表示 “7 天” 的快捷方式。
转换数据大小
-
常规的
long
表示(除非指定了@DataSizeUnit
,否则使用字节作为默认单位) -
更易读的格式,其中值和单位是耦合的(
10MB
表示 10 兆字节)
考虑以下示例:
-
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 兆字节的缓冲区大小,10
和 10MB
是等效的。
256 字节的大小阈值可以指定为 256
或 256B
。
你还可以使用任何支持的单位。 这些是:
-
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
),如果不是字节的话。
这样做可以在支持更丰富格式的同时提供透明的升级路径。
@ConfigurationProperties 验证
每当 @ConfigurationProperties
类被 Spring 的 @Validated
注解时,Spring Boot 都会尝试验证它们。
你可以直接在配置类上使用 JSR-303 jakarta.validation
约束注解。
为此,确保你的类路径上有兼容的 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
}
提示:你还可以通过用 @Validated
注解创建配置属性的 @Bean
方法来触发验证。
要将验证级联到嵌套属性,必须用 @Valid
注解关联字段。
以下示例基于前面的 MyProperties
示例:
-
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
bean 的端点。
将你的 Web 浏览器指向 /actuator/configprops
或使用等效的 JMX 端点。
有关详细信息,请参见 生产就绪功能 部分。
@ConfigurationProperties 与 @Value
@Value
注解是核心容器功能,它不提供与类型安全的配置属性相同的功能。
下表总结了 @ConfigurationProperties
和 @Value
支持的功能:
功能 | @ConfigurationProperties |
@Value |
---|---|---|
是 |
有限(参见 下面的注释) |
|
是 |
否 |
|
|
否 |
是 |
如果你确实想使用 @Value
,我们建议你使用规范形式(仅使用小写字母的短横线命名法)引用属性名称。
这将允许 Spring Boot 使用与 宽松绑定 @ConfigurationProperties
时相同的逻辑。
例如,@Value("${demo.item-price}")
将从 application.properties
文件中获取 demo.item-price
和 demo.itemPrice
形式,以及从系统环境中获取 DEMO_ITEMPRICE
。
如果你使用 @Value("${demo.itemPrice}")
代替,demo.item-price
和 DEMO_ITEMPRICE
将不会被考虑。
如果你为自己的组件定义了一组配置键,我们建议你将它们分组在一个用 @ConfigurationProperties
注解的 POJO 中。
这样做将为你提供一个结构化的、类型安全的对象,你可以将其注入到自己的 bean 中。