官网地址:
Spring Boot外部配置加载顺序
- Spring Boot使用一种非常特定的PropertySource顺序设计,以便合理地覆盖值。后面的属性源可以覆盖前面定义的值(后面的优先级高于前面的)。属性源的考虑顺序如下:
-
- 默认属性(通过设置SpringApplication.setDefaultProperties指定)。
- 在你的@Configuration类上的@PropertySource注释。请注意,这些属性源在应用上下文刷新之前不会添加到Environment中。因此,这对于配置某些属性(如logging.和spring.main.)来说为时已晚,因为这些属性在应用上下文刷新之前被读取。
- 配置数据(例如application.properties文件)。
- 随机值属性源(RandomValuePropertySource),其属性仅在random.*。
- 操作系统环境变量。
- Java系统属性(System.getProperties())。
- 来自java:comp/env的JNDI属性。
- ServletContext init参数。
- ServletConfig init参数。
- 来自SPRING_APPLICATION_JSON的属性(内联JSON嵌入在环境变量或系统属性中)。
- 命令行参数。
- 测试中的属性属性。在@SpringBootTest和用于测试应用特定切片的测试注释中可用。
- 测试中的@DynamicPropertySource注释。
- 测试中的@TestPropertySource注释。
- 当devtools激活时,在$HOME/.config/spring-boot目录中的Devtools全局设置属性。
- 配置数据文件按照以下顺序考虑(后面的属性源可以覆盖前面定义的值,即后面的优先级高于前面的):
-
- 打包在jar中的应用属性(application.properties及其YAML变体)。
- 打包在jar中的特定profile的应用属性(application-{profile}.properties及其YAML变体)。
- 打包在jar外的应用属性(application.properties及其YAML变体)。
- 打包在jar外的特定profile的应用属性(application-{profile}.properties及其YAML变体)。
- 建议为整个应用坚持使用一种格式。如果在同一位置有.properties和YAML格式的配置文件,.properties优先。如果你使用环境变量而不是系统属性,大多数操作系统不允许使用以句点(.)分隔的键名,但你可以改用下划线(例如,SPRING_CONFIG_NAME代替spring.config.name)。如果你的应用在一个Servlet容器或应用服务器中运行,那么可以使用JNDI属性(在java:comp/env中)或servlet context初始化参数,代替或添加环境变量或系统属性。
- 示例:
-
-
以下是一个具体的例子,假设你开发了一个使用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
端点在确定为什么某个属性具有特定值时是有用的。你可以使用这两个端点来诊断意外的属性值。有关详细信息,请参阅Production ready features部分。
访问命令行属性
- 默认情况下,SpringApplication会将任何命令行选项参数(即以--开头的参数,如--server.port=9000)转换为一个属性,并将其添加到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
-
在上面的例子中,你最终会得到my.name=test在Spring Environment中。同样的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-.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/
这种搜索顺序让您可以在一个配置文件中指定默认值,然后在另一个文件中选择性地覆盖这些值。您可以在 default locations 的 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 属性。设置该属性的值为 ignore 可以通过 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 属性自己使用通配符位置。
通配符位置必须仅包含一个 * 并且以 */ 结尾以用于搜索目录位置,或以 */ 结尾以用于搜索文件位置。包含通配符的位置基于文件名的绝对路径按字母顺序排序。
通配符位置仅适用于外部目录。您不能在 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
如果没有激活任何配置文件,Environment 有一组默认配置文件(默认情况下为 [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-.properties 变体。
Spring Boot 包含可插入的 API 允许支持各种不同的位置地址。默认情况下,您可以导入 Java Properties、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 和 Secrets。
有两种常见的卷挂载模式:
- 一个文件包含一组完整属性(通常写为 YAML)。
- 多个文件写入到目录树中,文件名成为"键",内容成为"值"。
对于第一种情况,如上所述,您可以直接使用 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 文件将导致在环境中有一个 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 的 secret。当 Docker swarm 服务被授予对一个 secret 的访问权限时,该 secret 会被挂载到容器中。例如,如果名为 db.password 的 secret 挂载在 /run/secrets/ 位置,您可以使用以下内容使 db.password 在 Spring Environment 中可用:
spring:
config:
import: "optional:configtree:/run/secrets/"
属性占位符
application.properties 和 application.yaml 中的值在使用时通过现有 Environment 进行了过滤,因此您可以引用之前定义的值(例如,来自 System 属性或环境变量)。标准 𝑛𝑎𝑚𝑒属性占位符语法可以在值的任何地方使用。属性占位符也可以使用:分隔属性名和默认值来指定默认值,例如name属性占位符语法可以在值的任何地方使用。属性占位符也可以使用:分隔属性名和默认值来指定默认值,例如{name:default}.
使用默认值和不使用默认值的占位符如下示例所示:
app:
name: "MyApp"
description: "${app.name} 是由 ${username:Unknown} 编写的 Spring Boot 应用程序"
假设 username 属性未在其他地方设置,app.description 将具有值 MyApp 是由 Unknown 编写的 Spring Boot 应用程序。
您应该始终使用其规范形式(使用小写字母的 kebab-case)引用占位符中的属性名。这样将允许 Spring Boot 使用与松散绑定 @ConfigurationProperties 时相同的逻辑。
例如,𝑑𝑒𝑚𝑜.𝑖𝑡𝑒𝑚−𝑝𝑟𝑖𝑐𝑒将会从𝑎𝑝𝑝𝑙𝑖𝑐𝑎𝑡𝑖𝑜𝑛.𝑝𝑟𝑜𝑝𝑒𝑟𝑡𝑖𝑒𝑠文件中提取𝑑𝑒𝑚𝑜.𝑖𝑡𝑒𝑚−𝑝𝑟𝑖𝑐𝑒和𝑑𝑒𝑚𝑜.𝑖𝑡𝑒𝑚𝑃𝑟𝑖𝑐𝑒形式的配置,还会从系统环境提取𝐷𝐸𝑀𝑂𝐼𝑇𝐸𝑀𝑃𝑅𝐼𝐶𝐸。如果您使用demo.item−price将会从application.properties文件中提取demo.item−price和demo.itemPrice形式的配置,还会从系统环境提取DEMOITEMPRICE。如果您使用{demo.itemPrice},demo.item-price 和 DEMO_ITEMPRICE 则不会被考虑。
您还可以使用此技术来创建现有 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 注解加载。
激活属性
有时仅在满足某些条件时激活某些属性集很有用。例如,您可能有仅在特定配置文件激活时才相关的属性。
您可以使用 spring.config.activate.* 有条件地激活一个属性文档。
可用的激活属性如下:
表 1. 激活属性
|-------------------|------------------------------|
| 属性 | 备注 |
| on-profile | 必须匹配的配置文件表达式,以使文档生效。 |
| on-cloud-platform | 必须检测到的 CloudPlatform,以使文档生效。 |
例如,以下内容指定第二个文档仅在 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 接口允许您在应用程序启动之前操作 Environment。有关详细信息,请参阅"在启动前自定义 Environment 或 ApplicationContext"。
如果您需要一种安全的方式来存储凭证和密码,可以使用 Spring Cloud Vault 项目,它提供支持在 HashiCorp Vault 中存储外部化配置。
使用 YAML
YAML 是 JSON 的超集,因此是一种方便的格式,可以用于指定层次结构的配置数据。只要类路径中有 SnakeYAML 库,SpringApplication 类就能自动支持 YAML 作为 properties 的替代。
如果您使用 starters,spring-boot-starter 会自动提供 SnakeYAML。
将 YAML 映射到 Properties
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 类绑定到 Java List 或 Set 对象。有关更多详细信息,请参阅下面的"类型安全的配置属性"部分。
YAML 文件不能通过 @PropertySource 或 @TestPropertySource 注解加载。因此,如果需要通过这种方式加载值,您需要使用 properties 文件。
直接加载 YAML
Spring Framework 提供了两个方便的类,可以用于加载 YAML 文档。YamlPropertiesFactoryBean 将 YAML 作为 Properties 加载,而 YamlMapFactoryBean 将 YAML 作为 Map 加载。
如果您希望将 YAML 作为 Spring PropertySource 加载,也可以使用 YamlPropertySourceLoader 类。
配置随机值
RandomValuePropertySource 对于注入随机值(例如到 secrets 或测试用例中)非常有用。它可以生成整数、长整数、UUID 或字符串,如下例所示:
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 来管理和验证应用程序的配置。
另请参见 @ConfigurationProperties 与 @Value。
JavaBean 属性绑定
可以绑定声明标准 JavaBean 属性的 bean,如以下示例所示:
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;
}
}
}
上述 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 的字符串集合。
映射到 @ConfigurationProperties 类的属性通过属性文件、YAML 文件、环境变量等机制进行配置,这些属性类是公共 API,但类本身的访问器(getter/setter)并不打算直接使用。
这种配置依赖于默认的空构造函数,并且 getter 和 setter 通常是必要的,因为绑定是通过标准的 Java Beans 属性描述符完成的,就像在 Spring MVC 中一样。以下几种情况下可以省略 setter:
- Map,只要初始化了,需要 getter 而不一定需要 setter,因为可以通过绑定器来操作。
- 集合和数组可以通过索引访问(通常用 YAML)或使用逗号分隔的值(properties)。后者情况下,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.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;
}
}
}
在此设置中,单个参数化构造函数的存在意味着应使用构造绑定。这意味着绑定器将找到一个具有希望绑定的参数的构造函数 。如果类有多个构造函数,可以使用 @ConstructorBinding 注解来指定用于构造绑定的构造函数。要选择退出具有单个参数化构造函数的类的构造绑定,必须使用 @Autowired 或将构造函数设置为 private。构造绑定可以与记录一起使用。除非记录有多个构造函数,否则无需使用 @ConstructorBinding。
构造绑定类的嵌套成员(如上述示例中的 Security)也将通过其构造函数绑定。
可以使用 @DefaultValue 注解构造函数参数和记录组件来指定默认值。转换服务将应用于将注解的 String 值强制转换为丢失属性的目标类型。
参照上述示例,如果没有属性绑定到 Security,MyProperties 实例将包含一个 null 值的 security。要使其在没有属性绑定时包含一个非 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 机制(例如 @Component bean,通过 @Bean 方法创建的 bean 或通过 @Import 加载的 bean)创建的 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 injection)或框架提供的任何 *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();
// ...
}
// ...
}
使用 @ConfigurationProperties 还可以生成元数据文件,这些文件可供 IDE 提供自动完成功能以供您自己的键使用。有关详细信息,请参阅附录。
第三方配置
除了使用 @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;
}
}
通过上述代码,可以使用以下属性名称:
|-----------------------------------|-----------------------------------------|
| 属性 | 备注 |
| 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 | 大写格式,建议使用系统环境变量时使用 |
注解的 prefix 值必须为 kebab case(小写字母并用 - 分隔,例如 my.main-project.person)。
各属性源的宽松绑定规则表:
|---------------|----------------------------|--------------------------|
| 属性源 | 简单 | 列表 |
| Properties 文件 | 驼峰命名法、kebab case 或下划线表示法 | 使用 [ ] 的标准列表语法,或逗号分隔的值 |
| YAML 文件 | 驼峰命名法、kebab case 或下划线表示法 | YAML 标准列表语法或逗号分隔的值 |
| 环境变量 | 使用下划线作为分隔符的大写格式(参见从环境变量绑定) | 数值被下划线包围(参见从环境变量绑定) |
| 系统属性 | 驼峰命名法、kebab case 或下划线表示法 | 使用 [ ] 的标准列表语法,或逗号分隔的值 |
我们建议在可能的情况下将属性存储在小写 kebab 格式中,例如 my.person.first-name=Rod。
绑定映射(Map)
当我们把配置属性绑定到 Map 类型的属性时,通常希望保留键值的原始形式,特别是当键包含特殊字符时。在常规绑定过程中,任何不在方括号 ([]) 内的非字母数字字符、连字符 (-) 或点 (.) 都会被移除。为了保留这些特殊字符,需要使用方括号来包裹这些键。
例让我们通过一个具体的例子进行解释。
示例配置
假如我们在配置文件中有如下内容:
my:
map:
"[/key1]": "value1"
"[/key2]": "value2"
"/key3": "value3"
在上述示例中,键 /key1
和 /key2
被方括号包裹,而 /key3
没有。
解析如何进行
Spring Boot 在加载这些配置时,会分别处理每个键:
- 对于
[/key1]
和[/key2]
,因为它们被方括号包裹,键会被原样保留。 - 对于
/key3
,因为没有方括号包裹,解析时斜杠 (/
) 会被移除,所以结果键会是key3
。
最终解析绑定到 Map<String,String>
时,对应的Map内容如下所示:
Map<String, String> map = new HashMap<>();
map.put("/key1", "value1"); // 保留斜杠
map.put("/key2", "value2"); // 保留斜杠
map.put("key3", "value3"); // 斜杠被移除
标量值绑定
对于标量值(简单的 Java 对象类型,例如 String
、Integer
、Double
等),键名包含点 (.
) 时,不需要使用方括号包裹。这些键的点会被保留。
例如,有如下配置:
a.b=c
当绑定到 Map<String, String>
时,结果Map内容如下:
Map<String, String> map = new HashMap<>();
map.put("a.b", "c");
键 a.b
保持原样。
更复杂的类型
但是,当绑定到更复杂的类型时,例如 Map<String, Object>
,并且键包含点 (.
) 时,需要使用方括号包裹键名,以确保点得以保留并作为键的一部分。
例如,有如下配置:
a.b=c
当绑定到 Map<String, Object>
时,如果不使用方括号,Spring 会认为 a
是包含 b=c
的一个嵌套对象。因此返回的 Map 将如下所示:
Map<String, Object> map = new HashMap<>();
map.put("a", Map.of("b", "c"));
而使用方括号后的配置:
[a.b]=c
绑定效果是:
Map<String, Object> map = new HashMap<>();
map.put("a.b", "c");
在这个例子中,键 a.b
被完整保留,成为单独的一个键值对。
总结一下:
- 非方括号包裹的键名中,任何非字母数字字符、连字符 (
-
) 或点 (.
) 会被移除。 - 为了保留特殊字符(例如
/
),需要用方括号包裹键名。 - 对于标量值,点 (
.
) 保留不变,无需方括号。 - 对于更复杂的类型,如
Map<String, Object>
,需要用方括号包裹包含点 (.
) 的键名,以确保点被作为键的一部分保留。
从环境变量绑定
大多数操作系统对环境变量名有严格的规定。例如,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。
缓存
宽松绑定使用缓存来提升性能。默认情况下,此缓存仅应用于不可变属性源。要自定义此行为,例如为可变属性源启用缓存,可以使用 ConfigurationPropertyCaching。
合并复杂类型
当列表在多个地方配置时,覆盖操作会替换整个列表。
例如,假设一个 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;
}
}
考虑以下配置:
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 配置文件,列表仍然只包含一个条目(name 为 my another name,description 为 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 条目(name 为 my another name,description 为 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 的条目(name 为 my name 1,description 为 my description 1)。但是,如果启用了 dev 配置文件,map 包含两个键为 key1 和 key2 的条目(key1 的 name 为 dev name 1,description 为 my description 1;key2 的 name 为 dev name 2,description 为 dev description 2)。
上述合并规则适用于来自所有属性源的属性,而不仅仅是文件。
属性转换
Spring Boot 试图在与 @ConfigurationProperties bean 绑定时将外部应用程序属性转换为正确的类型。如果您需要自定义类型转换,可以提供一个 ConversionService bean(名称为 conversionService),或通过 CustomEditorConfigurer bean 提供自定义属性编辑器,或通过 @ConfigurationPropertiesBinding 注解定义的 bean 提供自定义转换器。
由于这个 bean 在应用程序生命周期的早期被请求,确保限制 ConversionService 使用的依赖项。通常,您需要的任何依赖项在创建时可能尚未完全初始化。如果自定义 ConversionService 不需要用于配置键的强制转换,可以将其重命名,仅依赖于带有 @ConfigurationPropertiesBinding 注解的自定义转换器。
转换持续时间
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);
// 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;
}
}
要指定一个 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.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;
}
}
如果您正在升级一个 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);
// 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;
}
}
要指定 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.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;
}
}
如果您正在升级一个 Long 属性,如果不是以字节为单位,请确保定义单位(使用 @DataSizeUnit)。这样可以提供透明的升级路径,同时支持更丰富的格式。
@ConfigurationProperties 验证
@ConfigurationProperties 注解用于将外部配置文件中的属性绑定到一个类上,以便在 Spring Boot 应用程序中使用。当在 @ConfigurationProperties
类上使用了 Spring 的 @Validated
注解时,Spring Boot 将尝试验证这些属性。下面详细解释如何使用和验证这些配置属性。
基本用法
假设有一个配置类 MyProperties
,用于绑定以 my.service
开头的配置属性:
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;
}
}
在这个例子中,remoteAddress
属性使用了 @NotNull
注解,表示它不能为空。当 Spring Boot 加载这个配置类时,如果 remoteAddress
为空,将会抛出验证异常。
嵌套属性的验证
如果配置类中包含嵌套的属性,需要确保嵌套的类也被验证。例如,扩展上面的例子,加入一个 Security
类来处理安全相关的配置:
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;
}
}
}
在这个例子中,Security
类的 username
属性使用了 @NotEmpty
注解,确保用户名不为空。通过在 MyProperties
类中使用 @Valid
注解,确保 security
属性中的嵌套属性也会被验证。
自定义验证器
如果需要自定义验证逻辑,可以通过创建一个名为 configurationPropertiesValidator
的 Bean 来实现。这个 Bean 方法应该声明为静态方法,以便在 Spring Boot 启动时进行早期创建,避免因实例化顺序问题而引起的验证失败。以下是一个示例:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
@Configuration
public class MyValidationConfig {
@Bean
public static LocalValidatorFactoryBean configurationPropertiesValidator() {
return new LocalValidatorFactoryBean();
}
}
这里的 configurationPropertiesValidator
方法返回了一个 LocalValidatorFactoryBean
,它是 Spring 提供的用于 JSR-303 验证的工厂类。
查看配置属性信息
Spring Boot 的 actuator 模块提供了一个端点 /actuator/configprops
,用于公开所有 @ConfigurationProperties
Bean 的详细信息。有关详细信息,请参阅Endpoints :: Spring Boot。
@ConfigurationProperties 与 @Value
对比表格
|-----------|----------------------------------------------------------------|-----------------------------------------------|
| 特性 | @ConfigurationProperties | @Value |
| 定义 | 用于将配置属性绑定到结构化对象 | 用于将单个配置属性注入到字段或方法参数 |
| 使用位置 | 类级别、@Bean 方法、@Component 类 | 字段、方法或构造函数参数级别 |
| 宽松绑定 | 完全支持。可以自动将不同命名风格(如camelCase、kebab-case、snake_case)的属性映射到Java字段 | 有限支持。使用kebab-case时可以匹配camelCase和UPPER_CASE |
| 元数据支持 | 支持。可以生成配置元数据,用于IDE自动完成和文档生成 | 不支持 |
| SpEL表达式支持 | 不支持 | 支持。可以在@Value注解中使用SpEL表达式 |
| 类型转换 | 自动进行复杂的类型转换,包括Duration、DataSize等 | 基本的类型转换,复杂类型需要自定义转换器 |
| 嵌套属性 | 支持。可以轻松映射复杂的嵌套结构 | 有限支持。需要使用点号表示法,如"parent.child.property" |
| 数组和集合绑定 | 完全支持。可以轻松绑定到List、Set、Map等 | 有限支持。可以绑定到数组,但复杂集合需要额外配置 |
| 验证 | 支持。可以与@Validated注解结合使用,应用JSR-303验证 | 不直接支持。需要额外的验证逻辑 |
| 默认值 | 通过字段初始化、@DefaultValue注解或在@ConfigurationProperties中指定 | 在@Value注解中直接指定,如@Value("{property:default}") |
| 前缀支持 | 支持。可以使用@ConfigurationProperties(prefix="myapp")指定前缀 | 不直接支持。每次都需要完整的属性名 |
| 批量属性注入 | 支持。可以一次性注入多个相关属性 | 不支持。每个属性需要单独的@Value注解 |
| 重载属性 | 支持。可以在不同配置文件中覆盖属性,遵循Spring Boot的属性优先级 | 支持。同样遵循Spring Boot的属性优先级 |
| 环境变量支持 | 自动支持。可以从环境变量中读取值,支持规范化命名 | 支持。可以使用{ENV_VAR}语法直接引用环境变量 |
| 配置文件位置 | 通常在application.properties或application.yml中集中定义,但也支持自定义配置文件 | 可以从任何Spring环境中可用的属性源读取 |
| 重构友好性 | 高。由于是类型安全的,IDE可以更好地支持重构 | 低。基于字符串,重构时可能会遗漏 |
| 测试友好性 | 高。可以轻松创建测试配置类或使用@TestPropertySource | 中等。可以使用@TestPropertySource,但每个属性可能需要单独设置 |
| 性能 | 较好。属性会被一次性绑定到对象,缓存结果 | 相对较差。每次访问都可能需要解析,但Spring有一定的缓存机制 |
| IDE支持 | 优秀。提供自动完成、类型检查等 | 有限。主要是字符串基础的支持 |
| 适用场景 | 大型配置集合,模块化配置,类型安全至关重要的场景 | 简单配置,单个值注入,需要SpEL表达式支持的场景 |
| 可维护性 | 高。集中管理相关配置,结构清晰 | 中等。分散的属性可能导致难以维护 |
| 文档生成 | 支持。可以生成配置属性文档,特别是结合@ConfigurationProperties使用 | 不直接支持。需要手动文档 |
| 条件装配 | 支持与@ConditionalOnProperty等注解结合使用 | 可以与@ConditionalOnExpression结合使用 |
| 动态更新 | 不直接支持。通常需要应用重启 | 可以通过@RefreshScope支持动态更新 |
| 国际化支持 | 可以与Spring的国际化机制结合使用 | 直接支持,可以使用#{...}语法引用消息源 |
特性:定义和使用
@ConfigurationProperties:
-
类级别注解,用于将配置属性绑定到结构化对象。
-
示例:
@Configuration
@ConfigurationProperties(prefix = "mail")
public class MailProperties {
private String host;
private int port;
// getters and setters
}
@Value:
-
字段、方法或构造函数参数级别的注解,用于注入单个属性。
-
示例:
@Component
public class MailService {
@Value("{mail.host}") private String host; @Value("{mail.port}")
private int port;
}
特性:宽松绑定
@ConfigurationProperties:
完全支持。可以自动将不同命名风格的属性映射到Java字段。
示例:
配置文件:
mail:
smtp-host: smtp.example.com
smtp-port: 25
Java类:
@ConfigurationProperties(prefix = "mail")
public class MailProperties {
private String smtpHost; // 自动映射smtp-host
private int smtpPort; // 自动映射smtp-port
}
@Value:
有限支持。主要支持kebab-case到camelCase的转换。
示例:
配置文件:
mail:
smtp-host: smtp.example.com
smtp-port: 25
Java类:
@Component
public class MailService {
@Value("${mail.smtp-host}")
private String smtpHost;
@Value("${mail.smtp-port}")
private int smtpPort;
}
特性:元数据支持
@ConfigurationProperties:
支持。可以生成配置元数据,用于IDE自动完成和文档生成。
示例:
@ConfigurationProperties(prefix = "acme")
public class AcmeProperties {
/**
* 服务器IP地址
*/
private String remoteAddress;
// IDE可以显示此注释作为提示
}
@Value:
不支持。无法为IDE提供自动完成或属性说明。
示例:
@Component
public class AcmeService {
@Value("${acme.remote-address}")
private String remoteAddress;
// IDE无法提供关于此属性的额外信息
}
特性:SpEL表达式支持
@ConfigurationProperties:
- 不支持SpEL表达式。
- 示例:不适用
@Value:
-
支持SpEL表达式。
-
示例:
@Component
public class SpelExample {
@Value("#{systemProperties['user.region']}")
private String region;
@Value("#{T(java.lang.Math).random() * 100.0}")
private double randomNumber;
}
特性:类型转换
@ConfigurationProperties:
自动进行复杂的类型转换。
示例:
配置文件:
app:
timeout: 5s
colors: red,green,blue
Java类:
@ConfigurationProperties(prefix = "app")
public class AppProperties {
private Duration timeout; // 自动转换为Duration
private List<String> colors; // 自动转换为List<String>
}
@Value:
基本的类型转换,复杂类型需要自定义转换器。
示例:
@Component
public class AppConfig {
@Value("${app.timeout}")
private String timeout; // 仍然是String,需手动转换
@Value("${app.colors}")
private String[] colors; // 可以自动转换为数组
}
嵌套属性:
@ConfigurationProperties:
@Component
@ConfigurationProperties(prefix = "app")
public class AppConfig {
private MailConfig mail;
public MailConfig getMail() {
return mail;
}
public void setMail(MailConfig mail) {
this.mail = mail;
}
public static class MailConfig {
private String host;
private int port;
// getters and setters
}
}
配置文件(application.properties):
app.mail.host=smtp.example.com
app.mail.port=25
@Value:
@Component
public class MailService {
@Value("${app.mail.host}")
private String mailHost;
@Value("${app.mail.port}")
private int mailPort;
// getters and setters
}
数组和集合绑定:
@ConfigurationProperties:
@Component
@ConfigurationProperties(prefix = "app")
public class AppConfig {
private List<String> servers;
// getters and setters
}
配置文件(application.properties):
app.servers[0]=server1
app.servers[1]=server2
@Value:
java
复制代码
@Component
public class ServerConfig {
@Value("${app.servers}")
private String[] servers;
// getters and setters
}
验证:
@ConfigurationProperties:
@Component
@ConfigurationProperties(prefix = "app")
@Validated
public class AppConfig {
@NotBlank
private String name;
// getters and setters
}
@Value:需要手动验证,例如使用JSR-303的注解或自定义逻辑来验证属性。
默认值:
@ConfigurationProperties:
@Component
@ConfigurationProperties(prefix = "app")
public class AppConfig {
@DefaultValue("defaultName")
private String name;
// getters and setters
}
@Value:
@Component
public class MessageService {
@Value("${app.message:defaultMessage}")
private String defaultMessage;
// getters and setters
}
批量属性注入:
@ConfigurationProperties:
@Component
@ConfigurationProperties(prefix = "app")
public class AppConfig {
private Map<String, String> properties;
// getters and setters
}
配置文件(application.properties):
app.properties.key1=value1
app.properties.key2=value2
@Value:每个属性需要单独的注解来注入。
环境变量支持:
@ConfigurationProperties:
@Component
@ConfigurationProperties(prefix = "app")
public class AppConfig {
private String envVar;
public String getEnvVar() {
return envVar;
}
public void setEnvVar(String envVar) {
this.envVar = envVar;
}
}
环境变量设置:
arduino
复制代码
export APP_ENV_VAR=myenvvalue
@Value:
@Component
public class EnvironmentService {
@Value("${APP_ENV_VAR}")
private String envVar;
// getters and setters
}
前缀支持、重载属性、配置文件位置
@ConfigurationProperties:
@ConfigurationProperties(prefix = "app")
@Component
public class AppConfig {
private String name;
private String environment;
private String version;
// getters and setters
}
配置文件(application.properties):
app.name=MyApp
app.environment=dev
配置文件(application-prod.properties):
app.environment=prod
app.version=1.0.0
@Value:
@Component
public class MessageService {
@Value("${app.name}")
private String appName;
@Value("${app.environment}")
private String environment;
@Value("${app.version:1.0.0}")
private String version;
// getters and setters
}
重构友好性
@ConfigurationProperties: 由于是类型安全的,重构时可以直接在Java类中重命名字段,IDE可以帮助检测和更新所有相关使用该属性的地方,如:
@ConfigurationProperties(prefix = "app")
@Component
public class AppConfig {
private String applicationName;
// getters and setters
}
@Value: 在使用字符串的情况下,重构时需要手动查找和更新所有使用该属性的地方,容易遗漏:
@Component
public class MessageService {
@Value("${app.name}")
private String applicationName;
// getters and setters
}
测试友好性
@ConfigurationProperties: 使用@TestPropertySource
注解可以轻松模拟配置属性,使得单元测试编写更加方便:
java
复制代码
@RunWith(SpringRunner.class)
@SpringBootTest
@TestPropertySource(locations = "classpath:test.properties")
public class AppConfigTests {
@Autowired
private AppConfig appConfig;
// test cases
}
@Value: 每个属性通常需要单独的模拟,测试配置相对繁琐:
@RunWith(SpringRunner.class)
@SpringBootTest
public class MessageServiceTests {
@Value("${app.name}")
private String appName;
// test cases
}
性能
@ConfigurationProperties: 属性一次性绑定到对象,性能较好:
@ConfigurationProperties(prefix = "app")
@Component
public class AppConfig {
private String name;
// getters and setters
}
@Value: 每次访问时需要解析字符串,性能相对较差:
@Component
public class MessageService {
@Value("${app.name}")
private String appName;
// getters and setters
}
IDE支持
@ConfigurationProperties: 提供良好的IDE支持,包括自动完成和类型检查:
@ConfigurationProperties(prefix = "app")
@Component
public class AppConfig {
private String name;
// getters and setters
}
@Value: 基于字符串的支持,IDE功能受限:
@Component
public class MessageService {
@Value("${app.name}")
private String appName;
// getters and setters
}
适用场景
@ConfigurationProperties: 适用于大型配置集合、模块化配置和类型安全要求较高的场景:
@ConfigurationProperties(prefix = "app")
@Component
public class AppConfig {
private String name;
// getters and setters
}
@Value: 适用于简单的配置注入、单个值的读取和需要SpEL表达式支持的简单场景:
@Component
public class MessageService {
@Value("${app.name}")
private String appName;
// getters and setters
}
可维护性
@ConfigurationProperties: 高,能够集中管理相关配置,结构清晰:
@ConfigurationProperties(prefix = "app")
@Component
public class AppConfig {
private String name;
// getters and setters
}
@Value: 中等,分散的属性可能导致配置不易维护:
@Component
public class MessageService {
@Value("${app.name}")
private String appName;
// getters and setters
}
文档生成
@ConfigurationProperties: 支持生成配置属性的文档,有助于团队成员理解和使用配置:
@ConfigurationProperties(prefix = "app")
@Component
public class AppConfig {
private String name;
// getters and setters
}
@Value: 不直接支持文档生成,需要额外的文档工作来说明每个属性的用途和配置。
注意
本文大多翻译自原文,进行了适当补充与扩展。