Spring Boot 配置外部化中文文档
本文为官方文档直译版本。原文链接
前言
Spring Boot 允许您将配置外部化,这样您就可以在不同的环境中使用相同的应用程序代码。您可以使用各种外部配置源,包括 Java 属性文件、YAML 文件、环境变量和命令行参数。
属性值可以通过 @Value
注解直接注入到 Bean 中,也可以通过 Spring 的Environment
抽象访问,还可以通过 @ConfigurationProperties
绑定到结构化对象中。
Spring Boot 使用一种非常特殊的 PropertySource
顺序,旨在允许对值进行合理的覆盖。后面的属性源可以覆盖前面属性源中定义的值。属性源按以下顺序考虑:
- 默认属性(通过设置
SpringApplication.setDefaultProperties
指定)。 - 配置类上的
@PropertySource
注解。请注意,在应用程序上下文刷新之前,此类属性源不会添加到环境中。这对于配置某些属性(如logging.*
和spring.main.*
)来说为时已晚,因为这些属性是在刷新开始前读取的。 - 配置数据(如
application.properties
文件)。 RandomValuePropertySource
只包含random.*
中的属性。- 操作系统环境变量。
- Java 系统属性(
System.getProperties()
)。 - 来自
java:comp/env.NET
的 JNDI 属性。 ServletContext
初始参数。ServletConfig
初始参数。SPRING_APPLICATION_JSON
中的属性(嵌入到环境变量或系统属性中的内联 JSON)。- 命令行参数。
- 测试属性 在
@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
,如下例所示:
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class MyBean {
@Value("${name}")
private String name;
// ...
}
在应用程序的 classpath 中(例如,在 jar 中),可以有一个 application.properties
文件,为 name
提供一个合理的默认属性值。在新环境中运行时,可以在 jar 之外提供一个 application.properties
文件来覆盖 name
。对于一次性测试,可以使用特定的命令行开关启动(例如,java -jar app.jar --name="Spring"
)。
env
和configprops
端点有助于确定属性具有特定值的原因。你可以使用这两个端点来诊断意外的属性值。有关详情,请参阅 "生产就绪功能 "部分。
访问命令行属性
默认情况下,SpringApplication
会将任何命令行选项参数(即以 --
开头的参数,如 --server.port=9000
)转换为属性,并将其添加到 Spring Environment
中。如前所述,命令行属性总是优先于基于文件的属性源。
如果不想将命令行属性添加到环境中,可以使用 SpringApplication.setAddCommandLineProperties(false)
将其禁用。
JSON 应用程序属性
环境变量和系统属性通常有一些限制,意味着某些属性名称不能使用。为了帮助解决这个问题,Spring Boot 允许您将一组属性编码为单一的 JSON 结构。
当应用程序启动时,spring.application.json
或 SPRING_APPLICATION_JSON
属性将被解析并添加到环境中。
例如,SPRING_APPLICATION_JSON
属性可以作为环境变量在 UNIX 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 中的空值会被添加到生成的属性源中,但
PropertySourcesPropertyResolver
会将空属性视为缺失值。这意味着 JSON 无法用空值覆盖低阶属性源中的属性。
外部应用程序属性
Spring Boot 会在应用程序启动时自动从以下位置查找并加载 application.properties
和 application.yaml
文件:
- 来自类路径
- 类路径根目录
- 类路径
/config
包
- 从当前目录
- 当前目录
- 当前目录中的
config/
子目录 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
如果位置是可选的,并且您不介意它们不存在,请使用前缀
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 知道应该如何对它们进行分组。位置组是在同一级别考虑的位置的集合。例如,您可能想分组所有 classpath
位置,然后是所有外部位置。位置组内的项目应以 ;
分隔。更多详情,请参阅 "配置文件特定文件 "部分的示例。
使用 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(...)
或系统/环境变量设置忽略值。
通配符位置
如果配置文件位置的最后一个路径段包含 *
字符,则视为通配符位置。通配符会在加载配置时展开,因此直接子目录也会被检查。在 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:
位置中使用通配符。
特定配置文件
除了应用程序属性文件外,Spring Boot 还会尝试使用命名约定 application-{profile}
加载特定于配置文件的文件。例如,如果您的应用程序激活了名为 prod
的配置文件并使用 YAML 文件,那么 application.yaml
和 application-prod.yaml
都将被考虑。
特定配置文件属性的加载位置与标准 application.properties
相同,特定配置文件总是优先于非特定文件。如果指定了多个配置文件,则采用后胜策略。例如,如果通过 spring.profiles.active
属性指定了配置文件 prod、live
,application-prod.properties
中的值就会被 application-live.properties
中的值覆盖。
后胜策略适用于位置组级别。
spring.config.location
的classpath:/cfg/,classpath:/ext/
与classpath:/cfg/;classpath:/ext/
的覆盖规则不同。
例如,继续上面的prod,live
实例,我们可能会有以下文件:
- /cfg
- application-live.properties
- /ext
- application-live.properties
- application-prod.properties
当我们的 spring.config.location
为 classpath:/cfg/,classpath:/ext/
时,我们会先处理所有 /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
环境有一组默认配置文件(默认为 [default]
),如果没有设置激活的配置文件,就会使用这些配置文件。换句话说,如果没有明确激活任何配置文件,则会考虑application-default
配置文件中的属性。
属性文件只加载一次。如果您已经直接导入了某个配置文件的特定属性文件,则不会再导入第二次。
导入附加数据
应用程序属性可使用 spring.config.import
属性从其他位置导入更多配置数据。导入会在被发现时进行处理,并被视为紧接在声明导入的文件下方插入的附加文件。
例如,在你的类路径下 application.properties
文件中可能有以下内容:
spring:
application:
name: "myapp"
config:
import: "optional:file:./dev.properties"
这将触发导入当前目录下的 dev.properties
文件(如果存在此类文件)。导入的 dev.properties
中的值将优先于触发导入的文件。在上例中,dev.properties
可以将 spring.application.name
重新定义为不同的值。
无论导入声明多少次,都只能导入一次。导入在 properties/yaml 文件的单个文档中定义的顺序并不重要。例如,下面两个示例产生的结果是一样的:
spring:
config:
import: "my.properties"
my:
property: "value"
my:
property: "value"
spring:
config:
import: "my.properties"
在上述两个示例中,my.properties
文件中的值优先于触发导入的文件。
可在单个 spring.config.import
关键字下指定多个位置。位置将按照定义的顺序进行处理,后导入的位置优先。
在适当情况下,也会考虑导入特定配置文件的变量。上例将同时导入
my.properties
和任何my-<profile>.properties
变体。
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
中导入该文件:
spring:
config:
import: "file:/etc/config/myconfig[.yaml]"
使用配置树
在云平台(如 Kubernetes)上运行应用程序时,您经常需要读取平台提供的配置值。为此目的使用环境变量的情况并不少见,但这样做也有缺点,尤其是当配置值需要保密时。
作为环境变量的替代方案,许多云平台现在都允许将配置映射到挂载的数据卷中。例如,Kubernetes 可以将[ConfigMaps](https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/#populate-a-volume-with-data-stored-in-a-configmap)
和 [Secrets](https://kubernetes.io/docs/concepts/configuration/secret/#using-secrets-as-files-from-a-pod)
卷挂载。
有两种常见的卷挂载模式可供选择:
- 单个文件包含一整套属性(通常写成 YAML)。
- 多个文件被写入一个目录树,文件名成为 “key”,内容成为 “value”。
对于第一种情况,可以如上所述使用 spring.config.import
直接导入 YAML 或 Properties 文件。对于第二种情况,您需要使用 configtree:
前缀,以便 Spring Boot 知道它需要将所有文件作为属性公开。
举个例子,假设 Kubernetes 挂载了以下卷:
etc/
config/
myapp/
username
password
username
文件的内容将是一个配置值,password
的内容将是一个秘密。
要导入这些属性,可以在 application.properties
或 application.yaml
文件中添加以下内容:
spring:
config:
import: "optional:configtree:/etc/config/"
然后,您就可以通过常规方式从环境中访问或注入 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/*/
作为导入位置:
spring:
config:
import: "optional:configtree:/etc/config/*/"
这将添加 db.username
、db.password
、mq.username
和 mq.password
属性。
使用通配符加载的目录按字母顺序排序。如果需要不同的顺序,则应将每个位置作为单独的导入列出
配置树也可用于 Docker 秘密。当 Docker swarm 服务获准访问某个秘密时,该秘密就会被挂载到容器中。例如,如果名为 db.password
的秘密被挂载到 /run/secrets/
位置,你就可以使用下面的方法让 Spring 环境访问 db.password
:
spring:
config:
import: "optional:configtree:/run/secrets/"
属性占位符
在使用 application.properties
和 application.yaml
中的值时,会通过现有的环境进行过滤,因此您可以引用以前定义的值(例如,从系统属性或环境变量中)。标准的 ${name}
属性占位符语法可用于值的任何位置。属性占位符还可以指定默认值,使用 :
分隔默认值和属性名称,例如 ${name:default}
。
有默认值和无默认值占位符的使用示例如下:
app:
name: "MyApp"
description: "${app.name} is a Spring Boot application written by ${username:Unknown}"
假设username
属性未在其他地方设置,app.description
的值将是MyApp is a Spring Boot application written by Unknown
。
您应始终使用其规范形式(仅使用小写字母的kebab-case)来引用占位符中的属性名称。这将允许 Spring Boot 使用与放松绑定
@ConfigurationProperties
时相同的逻辑。
例如,${demo.item-price}
将从application.properties
文件中获取demo.item-price
和demo.itemPrice
表单,并从系统环境中获取DEMO_ITEMPRICE
。如果使用${demo.itemPrice}
,则不会考虑demo.item-price
和DEMO_ITEMPRICE
。
您还可以使用此技术创建现有 Spring Boot 属性的 "简短 "变体。详情请参阅 howto.html 方法。
使用多文档文件
Spring Boot 允许您将单个物理文件分割成多个逻辑文档,每个文档都是独立添加的。文档按从上到下的顺序处理。后面的文档可以覆盖前面文档中定义的属性。
对于 application.yaml
文件,使用标准的 YAML 多文档语法。三个连续的-
字符代表一个文档的结束和下一个文档的开始。
例如,以下文件有两个逻辑文档:
spring:
application:
name: "MyApp"
---
spring:
application:
name: "MyCloudApp"
config:
activate:
on-cloud-platform: "kubernetes"
对于 application.properties
文件,使用特殊的 #---
或 !---
注释来标记文档分割:
spring.application.name=MyApp
#---
spring.application.name=MyCloudApp
spring.config.activate.on-cloud-platform=kubernetes
属性文件分隔符不能有任何前导空白,必须有三个
-
字符。分隔符前后的行不能是相同的注释前缀。
多文档属性文件通常与激活属性(如
spring.config.activate.on-profile
)结合使用。详见下一节。
使用
@PropertySource
或@TestPropertySource
注解无法加载多文档属性文件。
激活属性
有时,只有在满足特定条件时才激活特定的属性集是非常有用的。例如,你可能有一些属性只有在特定配置文件处于激活状态时才相关。
你可以使用 spring.config.activate.*
有条件地激活属性文档。
以下激活属性可用:
Property | Note |
---|---|
on-profile | 配置文件表达式,必须匹配该表达式,文档才会激活。 |
on-cloud-platform | 文档激活时必须检测到的云平台。 |
例如,下面指定第二个文档只有在 Kubernetes 上运行时才会激活,而且只有在 "prod "或 "staging "配置文件激活时才会激活:
myprop:
"always-set"
---
spring:
config:
activate:
on-cloud-platform: "kubernetes"
on-profile: "prod | staging"
myotherprop: "sometimes-set"
加密属性
Spring Boot 不提供任何用于加密属性值的内置支持,但它提供了修改 Spring Environment
中所含值所需的钩点。EnvironmentPostProcessor
接口允许您在应用程序启动前操作环境。详见 howto.htm。
如果您需要一种安全的方式来存储凭证和密码,Spring Cloud Vault 项目支持在 HashiCorp Vault 中存储外部化配置。
使用 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
使用
[index]
符号的属性可以使用 Spring Boot 的Binder
类绑定到 JavaList
或Set
对象。有关详细信息,请参阅下面的 "类型安全配置属性 "部分。
使用
@PropertySource
或@TestPropertySource
注解无法加载 YAML 文件。因此,如果需要以这种方式加载值,则需要使用属性文件。
直接加载 YAML
Spring Framework 提供了两个方便的类,可用于加载 YAML 文档。YamlPropertiesFactoryBean
将 YAML 作为属性加载,而 YamlMapFactoryBean
将 YAML 作为Map
加载。
如果您想将 YAML 作为 Spring PropertySource
加载,还可以使用 YamlPropertySourceLoader
类。
配置随机值
RandomValuePropertySource
用于注入随机值(例如,注入到秘密或测试用例中)。它可以产生整数、longs、uuids 或字符串,如下例所示:
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
上设置。
例如,如果将前缀设置为 input
,remote.timeout
等属性也将在系统环境中解析为 input.remote.timeout
。
类型安全的配置属性
使用 @Value("${property}")
注解注入配置属性有时会很麻烦,尤其是在处理多个属性或数据具有层次性的情况下。Spring Boot 提供了另一种处理属性的方法,让强类型 Bean 管理和验证应用程序的配置。
另请参阅
@Value
与类型安全配置属性之间的区别。
JavaBean 属性绑定
如下例所示,可以绑定一个声明了标准 JavaBean 属性的 Bean:
@ConfigurationProperties("my.service")
public class MyProperties {
private boolean enabled;
private InetAddress remoteAddress;
private final Security security = new Security();
// getters / setters...
public static class Security {
private String username;
private String password;
private List<String> roles = new ArrayList<>(Collections.singleton("USER"));
// getters / setters...
}
}
上面的 POJO 定义了以下属性:
my.service.enabled
,默认值为false
。my.service.remote-address
(远程地址),其类型可从字符串中提取。my.service.security.username
,嵌套 "security "对象,其名称由属性名称决定。特别要指出的是,这里根本没有使用类型,可能是SecurityProperties
。my.service.security.password
。my.service.security.roles
包含一个默认为USER
的字符串集合。
映射到 Spring Boot 中可用的
@ConfigurationProperties
类的属性(通过属性文件、YAML 文件、环境变量和其他机制进行配置)是公共 API,但类本身的访问器(getters/setters)不能直接使用。
这种安排依赖于默认的空构造函数,
getter
和setter
通常是强制性的,因为绑定是通过标准 Java Beans 属性描述符进行的,就像在 Spring MVC 中一样。在以下情况下,可以省略setter
:
Map
(只要已初始化)需要getter
,但不一定需要setter
,因为绑定器可以更改它们。- 可以通过索引(通常使用 YAML)或使用单个逗号分隔的值(属性)来访问集合和数组。在后一种情况下,必须使用
setter
。我们建议始终为此类类型添加一个设置器。如果要初始化一个集合,请确保它不是不可变的(如前面的示例)。- 如果嵌套的 POJO 属性被初始化(如上例中的
Security
字段),则不需要setter
。如果希望Binder
使用默认构造函数即时创建实例,则需要使用setter
。
有些人使用 Project Lombok 自动添加getter
和setter
。请确保 Lombok 不会为这种类型生成任何特定的构造函数,因为容器会自动使用它来实例化对象。
最后,只考虑标准的 Java Bean 属性,不支持绑定静态属性。
构造函数绑定
上一节的示例可以用不可变的方式重写,如下例所示:
@ConfigurationProperties("my.service")
public class MyProperties {
// fields...
public MyProperties(boolean enabled, InetAddress remoteAddress, Security security) {
this.enabled = enabled;
this.remoteAddress = remoteAddress;
this.security = security;
}
// getters...
public static class Security {
// fields...
public Security(String username, String password, @DefaultValue("USER") List<String> roles) {
this.username = username;
this.password = password;
this.roles = roles;
}
// getters...
}
}
在此设置中,单个参数化构造函数的存在意味着应使用构造函数绑定。这意味着绑定器将找到一个带有您希望绑定的参数的构造函数。如果你的类有多个构造函数,可以使用 @ConstructorBinding
注解来指定使用哪个构造函数进行构造函数绑定。如果类只有一个参数化的构造函数,则必须使用 @Autowired
注解才能退出构造函数绑定。构造函数绑定可用于记录。除非您的记录有多个构造函数,否则没有必要使用 @ConstructorBinding
。
构造函数绑定类(如上例中的 Security
)的嵌套成员也将通过其构造函数绑定。
可以在构造函数参数和记录组件上使用 @DefaultValue
指定默认值。转换服务将用于将注解的字符串值强制转换为缺失属性的目标类型。
参考前面的示例,如果没有任何属性绑定到 Security
,MyProperties
实例将包含一个null
值的 Security
。要使 MyProperties
实例在没有绑定任何属性的情况下也包含一个非空的 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),您不能使用构造函数绑定。
要在本地镜像中使用构造函数绑定,必须使用
-parameters
对类进行编译。如果使用 Spring Boot 的 Gradle 插件,或者使用 Maven 和spring-boot-starter-parent
,编译会自动进行。
不建议将
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 {
}
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("some.properties")
public class SomeProperties {
}
要使用配置属性扫描,请在应用程序中添加 @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 的完整限定名称。
假设它位于com.example.app
包中,则上述SomeProperties
示例的 Bean 名称为some.properties-com.example.app.SomeProperties
。
我们建议 @ConfigurationProperties
只处理环境,尤其不要注入上下文中的其他 Bean。在某些情况下,可以使用setter
注入或框架提供的任何 *Aware
接口(如需要访问环境时使用 EnvironmentAware
)。如果您仍想使用构造函数注入其他 Bean,则必须使用 @Component
对配置属性 Bean 进行注解,并使用基于 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();
// ...
}
// ...
}
使用 @ConfigurationProperties
还可以生成元数据文件,供集成开发环境使用,为自己的键提供自动完成功能。详情请参见附录。
第三方配置
除了使用 @ConfigurationProperties
对类进行注解外,您还可以在公共 @Bean
方法中使用它。当您想将属性绑定到您无法控制的第三方组件时,这样做会特别有用。
要从环境属性配置 Bean,请在其 Bean 注册中添加 @ConfigurationProperties
,如下例所示:
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;
}
}
通过前面的代码,可以使用以下所有属性名称:
Property | Note |
---|---|
my.main-project.person.first-name | Kebab-case,建议在 .properties 和 YAML 文件中使用。 |
my.main-project.person.firstName | 标准驼峰式语法 |
my.main-project.person.first_name | 下划线符号,是 .properties 和 YAML 文件的另一种格式。 |
MY_MAINPROJECT_PERSON_FIRSTNAME | 大写格式,建议在使用系统环境变量时使用。 |
注解的前缀值必须使用 kebab-case 写(小写并用
-
分隔,如my.main-project.person
)。
Property Source | Simple | List |
---|---|---|
Properties Files | 驼峰格式、破折号或下划线连接 | 使用 [ ] 或逗号分隔值的标准列表语法 |
YAML Files | 驼峰格式、破折号或下划线连接 | 标准 YAML 列表语法或用逗号分隔的值 |
Environment Variables | 大写格式,下划线为分隔符(请参阅 “从环境变量绑定”)。 | 用下划线连起来的数值(请参阅从环境变量绑定)。 |
System properties | 驼峰格式、破折号或下划线连接 | 使用 [ ] 或逗号分隔值的标准列表语法 |
我们建议,在可能的情况下,属性以减号连接的格式存储,例如
my.person.first-name=Rod
。
绑定Map
绑定属性到 Map
时,可能需要使用特殊的括号符号,以便保留原始key
值。如果键值没有用[]
包围,任何非字母数字、-
或.
的字符都会被删除。
例如,将以下属性绑定到 Map<String,String>
中:
my:
map:
"[/key1]": "value1"
"[/key2]": "value2"
"/key3": "value3"
对于 YAML 文件,括号需要用引号包围,这样才能正确解析键值。
上述属性将绑定到一个Map
,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
的环境变量。
在绑定对象列表时,也可以使用环境变量。要绑定到列表,应在变量名中用下划线括起元素编号。
例如,配置属性 my.service[0].other
将使用名为 MY_SERVICE_0_OTHER
的环境变量。
缓存
宽松绑定使用缓存来提高性能。默认情况下,这种缓存只适用于不可变属性源。要自定义此行为,例如为可变属性源启用缓存,请使用 ConfigurationPropertyCaching
。
合并复杂类型
当列表配置在多个地方时,覆盖的作用是替换整个列表。
例如,假设 MyPojo
对象的 name
和 description
属性默认为空。下面的示例从 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;
}
}
请考虑以下配置:
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
实例,也不会合并项目。
如果在多个配置文件中指定了一个列表,则会使用优先级最高的配置文件(也只有该配置文件)。请看下面的例子:
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;
}
}
请考虑以下配置:
my:
map:
key1:
name: "my name 1"
description: "my description 1"
---
spring:
config:
activate:
on-profile: "dev"
my:
map:
key1:
name: "dev name 1"
key2:
name: "dev name 2"
description: "dev description 2"
如果dev
配置文件未激活,MyProperties.map
会包含一个 key1
条目(名称为my name 1
,描述为my description 1
)。但如果启用了dev
配置文件,map
会包含两个条目,键值分别为 key1
(名称为dev name 1
,描述为my description 1
)和 key2
(名称为dev name 2
,描述为dev description 2
)。
上述合并规则适用于所有属性源的属性,而不仅仅是文件。
属性转换
Spring Boot 在绑定 @ConfigurationProperties
Bean 时,会尝试将外部应用程序属性强制转换为正确的类型。如果需要自定义类型转换,可以提供 ConversionService
Bean(使用名为 conversionService
的 Bean)或自定义属性编辑器(通过 CustomEditorConfigurer
Bean)或自定义转换器(使用注释为 @ConfigurationPropertiesBinding
的 Bean 定义)。
由于此 Bean 在应用程序生命周期的早期就会被请求,因此请确保限制 ConversionService
使用的依赖关系。通常情况下,您所需要的任何依赖关系在创建时可能尚未完全初始化。如果配置键强制不需要自定义 ConversionService
,您可能需要将其重命名,并且只依赖于使用 @ConfigurationPropertiesBinding
限定的自定义转换器。
转换Duration
Spring Boot 为表达Duration
提供了专门支持。如果暴露了 java.time.Duration
属性,应用程序属性中的以下格式就可用:
- 常规长表示法(使用毫秒作为默认单位,除非指定了
@DurationUnit
) java.time.Duration
使用的标准 ISO-8601 格式- 一种更易读的格式,其中值和单位是耦合的(
10s
表示 10 秒)
请看下面这个例子:
@ConfigurationProperties("my")
public class MyProperties {
@DurationUnit(ChronoUnit.SECONDS)
private Duration sessionTimeout = Duration.ofSeconds(30);
private Duration readTimeout = Duration.ofMillis(1000);
// getters / setters...
}
要指定会话超时 30 秒,30
、PT30S
和 30s
都是等价的。读取超时 500 毫秒可以用以下任何一种形式指定: 500
、PT0.5S
和 500ms
。
您也可以使用任何支持的单位。它们是:
ns
代表纳秒us
代表微秒ms
代表毫秒s
代表秒m
代表分钟h
代表小时d
代表天
默认单位是毫秒,可使用 @DurationUnit
进行重载,如上例所示。
如果您喜欢使用构造函数绑定,也可以公开相同的属性,如下例所示:
@ConfigurationProperties("my")
public class MyProperties {
// fields...
public MyProperties(@DurationUnit(ChronoUnit.SECONDS) @DefaultValue("30s") Duration sessionTimeout,
@DefaultValue("1000ms") Duration readTimeout) {
this.sessionTimeout = sessionTimeout;
this.readTimeout = readTimeout;
}
// getters...
}
如果要升级长属性,请确保定义单位(使用
@DurationUnit
)(如果不是毫秒)。这样可以提供一个透明的升级路径,同时支持更丰富的格式。
转换Period
除了Duration,Spring Boot 还可以使用 java.time.Period
类型。应用程序属性中可使用以下格式:
- 常规
int
表示法(使用天数作为默认单位,除非指定了@PeriodUnit
) java.time.Period
使用的标准 ISO-8601 格式- 一种更简单的格式,其中值和单位对是耦合的(
1y3d
表示 1 年零 3 天)
使用简单格式可支持以下单位:
y
代表年m
代表月w
代表周d
代表天数
java.time.Period
类型实际上并不存储周数,它只是一个快捷方式,表示 “7 天”。
转换DataSize
Spring Framework 有一种 DataSize
值类型,以字节为单位表示大小。如果公开 DataSize
属性,应用程序属性中的以下格式将可用:
- 常规的长表示法(使用字节作为默认单位,除非指定了
@DataSizeUnit
) - 可读性更强的格式,其中值和单位是耦合的(
10MB
表示 10 兆字节)
请看下面的示例:
@ConfigurationProperties("my")
public class MyProperties {
@DataSizeUnit(DataUnit.MEGABYTES)
private DataSize bufferSize = DataSize.ofMegabytes(2);
private DataSize sizeThreshold = DataSize.ofBytes(512);
// getters/setters...
}
要指定 10 兆字节的缓冲区大小,10
和 10MB
是等价的。256 字节的大小阈值可以指定为 256
或 256B
。
您也可以使用任何支持的单位。这些单位是:
B
代表字节KB
代表千字节MB
代表兆字节GB
代表千兆字节TB
代表太字节
默认单位是字节,可使用 @DataSizeUnit
进行重载,如上例所示。
如果您喜欢使用构造函数绑定,也可以公开相同的属性,如下例所示:
@ConfigurationProperties("my")
public class MyProperties {
// fields...
public MyProperties(@DataSizeUnit(DataUnit.MEGABYTES) @DefaultValue("2MB") DataSize bufferSize,
@DefaultValue("512B") DataSize sizeThreshold) {
this.bufferSize = bufferSize;
this.sizeThreshold = sizeThreshold;
}
// getters...
}
如果要升级长属性,请确保定义单位(使用
@DataSizeUnit
)(如果不是字节)。这样可以提供透明的升级路径,同时支持更丰富的格式。
@ConfigurationProperties 验证
只要 @ConfigurationProperties
类使用了 Spring 的 @Validated
注解,Spring Boot 就会尝试对其进行验证。您可以直接在配置类上使用 JSR-303 jakarta.validation
约束注解。为此,请确保在类路径上有符合要求的 JSR-303 实现,然后在字段中添加约束注解,如下例所示:
@ConfigurationProperties("my.service")
@Validated
public class MyProperties {
@NotNull
private InetAddress remoteAddress;
// getters/setters...
}
您还可以通过在创建配置属性的
@Bean
方法中注释@Validated
来触发验证。
为确保嵌套属性始终触发验证,即使没有找到任何属性,也必须用 @Valid
对相关字段进行注解。下面的示例以前面的 MyProperties
示例为基础:
@ConfigurationProperties("my.service")
@Validated
public class MyProperties {
@NotNull
private InetAddress remoteAddress;
@Valid
private final Security security = new Security();
// getters/setters...
public static class Security {
@NotEmpty
private String username;
// getters/setters...
}
}
您还可以通过创建名为 configurationPropertiesValidator
的 Bean 定义来添加自定义 Spring 验证器。@Bean
方法应声明为静态。配置属性验证器是在应用程序生命周期的早期创建的,将 @Bean
方法声明为静态方法可让 Bean 在创建时无需实例化 @Configuration
类。这样做可以避免提前实例化可能导致的任何问题。
spring-boot-actuator
模块包含一个可公开所有@ConfigurationProperties
Bean 的端点。将网络浏览器指向/actuator/configprops
,或使用相应的 JMX 端点。有关详情,请参阅 "生产就绪功能 "部分。
@ConfigurationProperties 对比 @Value
@Value
注解是容器的核心功能,它不提供与类型安全配置属性相同的功能。下表总结了 @ConfigurationProperties
和 @Value
支持的功能:
Feature | @ConfigurationProperties | @Value |
---|---|---|
宽松绑定 | Yes | 支持有限(见下方说明) |
元数据支持 | Yes | No |
SpEL 评估 | No | Yes |
如果您确实想使用
@Value
,我们建议您使用其规范形式(仅使用小写字母的kebab-case)来引用属性名称。这将允许 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 中。
在解析应用程序属性文件和填充环境时,不会处理这些文件中的 SpEL
表达式。不过,可以在 @Value
中写入 SpEL
表达式。如果应用程序属性文件中的属性值是 SpEL
表达式,则在通过 @Value
消耗时将对其进行评估。