跳到主要内容

外部化配置

DeepSeek V3 中英对照 Externalized Configuration

Spring Boot 允许你将配置外部化,以便在不同的环境中使用相同的应用程序代码。你可以使用多种外部配置源,包括 Java 属性文件、YAML 文件、环境变量和命令行参数。

属性值可以通过使用 @Value 注解直接注入到你的 bean 中,通过 Spring 的 Environment 抽象访问,或者通过 @ConfigurationProperties 绑定到结构化对象

Spring Boot 使用了一个非常特定的 PropertySource 顺序,该顺序旨在允许合理地覆盖值。后面的属性源可以覆盖前面定义的属性值。属性源的顺序如下:

  1. 默认属性(通过设置 SpringApplication.setDefaultProperties(Map) 指定)。

  2. 在你的 @Configuration 类上的 @PropertySource 注解。请注意,此类属性源在应用程序上下文被刷新之前不会添加到 Environment 中。这对于配置某些属性(如 logging.*spring.main.*)来说为时已晚,因为这些属性在刷新开始之前就已经被读取。

  3. 配置数据(如 application.properties 文件)。

  4. 仅在 random.* 中包含属性的 RandomValuePropertySource

  5. 操作系统环境变量。

  6. Java 系统属性(System.getProperties())。

  7. 来自 java:comp/env 的 JNDI 属性。

  8. ServletContext 初始化参数。

  9. ServletConfig 初始化参数。

  10. 来自 SPRING_APPLICATION_JSON 的属性(嵌入在环境变量或系统属性中的内联 JSON)。

  11. 命令行参数。

  12. 测试中的 properties 属性。可用于 @SpringBootTest用于测试应用程序特定部分的测试注解

  13. 测试中的 @DynamicPropertySource 注解。

  14. 测试中的 @TestPropertySource 注解。

  15. 当 devtools 激活时,位于 $HOME/.config/spring-boot 目录中的 Devtools 全局设置属性

配置文件按以下顺序被考虑:

  1. 打包在 jar 包内的应用属性application.properties 和 YAML 变体)。

  2. 打包在 jar 包内的特定环境的应用属性application-{profile}.properties 和 YAML 变体)。

  3. 打包 jar 包外部的应用属性application.properties 和 YAML 变体)。

  4. 打包 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;

// ...

}
java

在你的应用程序类路径中(例如,在 jar 文件内部),你可以有一个 application.properties 文件,它为 name 属性提供合理的默认值。当在新环境中运行时,可以在 jar 文件外部提供一个 application.properties 文件来覆盖 name 属性。对于一次性测试,你可以使用特定的命令行开关启动应用程序(例如,java -jar app.jar --name="Spring")。

提示

envconfigprops 端点可以帮助你确定某个属性为何具有特定值。你可以使用这两个端点来诊断意外的属性值。详情请参阅 生产就绪特性 部分。

访问命令行属性

默认情况下,SpringApplication 会将任何命令行选项参数(即以 -- 开头的参数,例如 --server.port=9000)转换为 property,并将其添加到 Spring 的 Environment 中。如前所述,命令行属性始终优先于基于文件的属性源。

如果您不希望将命令行属性添加到 Environment 中,可以通过使用 SpringApplication.setAddCommandLineProperties(false) 来禁用它们。

JSON 应用属性

环境变量和系统属性通常有一些限制,这意味着某些属性名称无法使用。为了解决这个问题,Spring Boot 允许你将一组属性编码为单个 JSON 结构。

当你的应用程序启动时,任何 spring.application.jsonSPRING_APPLICATION_JSON 属性都会被解析并添加到 Environment 中。

例如,SPRING_APPLICATION_JSON 属性可以通过命令行的方式在 UN*X shell 中作为环境变量提供:

$ SPRING_APPLICATION_JSON='{"my":{"name":"test"}}' java -jar myapp.jar
shell

在前面的例子中,你最终会在 Spring 的 Environment 中得到 my.name=test

相同的 JSON 也可以作为系统属性提供:

$ java -Dspring.application.json='{"my":{"name":"test"}}' -jar myapp.jar
shell

或者,你可以通过使用命令行参数来提供 JSON 数据:

$ java -jar myapp.jar --spring.application.json='{"my":{"name":"test"}}'
shell

如果你正在部署到传统的应用服务器,你也可以使用名为 java:comp/env/spring.application.json 的 JNDI 变量。

备注

尽管来自 JSON 的 null 值会被添加到最终的属性源中,但 PropertySourcesPropertyResolver 会将 null 属性视为缺失值。这意味着 JSON 无法通过 null 值来覆盖低优先级属性源中的属性。

外部应用程序属性

Spring Boot 在应用启动时会自动从以下位置查找并加载 application.propertiesapplication.yaml 文件:

  1. 从类路径中

    1. 类路径的根目录

    2. 类路径中的 /config

  2. 从当前目录中

    1. 当前目录

    2. 当前目录中的 config/ 子目录

    3. config/ 子目录的直系子目录

该列表按优先级排序(后面项的值会覆盖前面的值)。从加载的文件中获取的文档将作为 PropertySource 实例添加到 Spring Environment 中。

如果您不喜欢使用 application 作为配置文件的名称,可以通过指定 spring.config.name 环境属性来切换到另一个文件名。例如,要查找 myproject.propertiesmyproject.yaml 文件,您可以按如下方式运行您的应用程序:

$ java -jar myproject.jar --spring.config.name=myproject
shell

你也可以通过使用 spring.config.location 环境属性来引用一个明确的路径。该属性接受一个以逗号分隔的列表,列表中包含一个或多个需要检查的路径。

以下示例展示了如何指定两个不同的文件:

$ java -jar myproject.jar --spring.config.location=\
optional:classpath:/default.properties,\
optional:classpath:/override.properties
shell
提示

如果位置是可选的,并且你不介意它们不存在,请使用前缀 optional:

注意

spring.config.namespring.config.locationspring.config.additional-location 在早期阶段用于确定需要加载哪些文件。它们必须定义为环境属性(通常是操作系统环境变量、系统属性或命令行参数)。

如果 spring.config.location 包含的是目录(而不是文件),它们应该以 / 结尾。在运行时,这些目录将会被附加由 spring.config.name 生成的名称,然后被加载。而在 spring.config.location 中指定的文件则会直接导入。

备注

目录和文件位置的值也会被展开以检查特定于配置文件的文件。例如,如果你的 spring.config.locationclasspath: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/,则考虑的全部位置集合为:

  1. optional:classpath:custom-config/

  2. optional:file:./custom-config/

如果您希望添加额外的配置文件位置,而不是替换默认位置,可以使用 spring.config.additional-location。从额外位置加载的属性可以覆盖默认位置中的属性。例如,如果将 spring.config.additional-location 配置为值 optional:classpath:/custom-config/,optional:file:./custom-config/,则考虑的完整位置集合为:

  1. optional:classpath:/;optional:classpath:/config/

  2. optional:file:./;optional:file:./config/;optional:file:./config/*/

  3. optional:classpath:custom-config/

  4. optional:file:./custom-config/

这种搜索顺序允许您在一个配置文件中指定默认值,然后在另一个配置文件中选择性地覆盖这些值。您可以在默认位置的 application.properties 文件(或使用 spring.config.name 指定的其他基本名称)中为应用程序提供默认值。然后,在运行时可以通过位于自定义位置的不同文件覆盖这些默认值。

可选位置

默认情况下,当指定的配置数据位置不存在时,Spring Boot 会抛出 ConfigDataLocationNotFoundException,并且你的应用程序将无法启动。

如果你想指定一个位置,但不在意它是否总是存在,可以使用 optional: 前缀。你可以在 spring.config.locationspring.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.locationspring.config.additional-location 属性来实现。

备注

通配符位置必须仅包含一个 *,并且对于目录的搜索位置以 */ 结尾,对于文件的搜索位置以 */<文件名> 结尾。带有通配符的位置会根据文件名的绝对路径按字母顺序排序。

提示

通配符位置仅适用于外部目录。你不能在 classpath: 位置使用通配符。

配置文件特定文件

除了 application 属性文件外,Spring Boot 还会尝试使用命名约定 application-{profile} 加载特定于配置文件的文件。例如,如果你的应用程序激活了一个名为 prod 的配置文件,并且使用了 YAML 文件,那么 application.yamlapplication-prod.yaml 都会被考虑加载。

特定于配置文件的属性从与标准 application.properties 相同的位置加载,且特定于配置文件的文件总是会覆盖非特定的文件。如果指定了多个配置文件,则采用“最后胜出”策略。例如,如果通过 spring.profiles.active 属性指定了 prod,live 配置文件,application-prod.properties 中的值可以被 application-live.properties 中的值覆盖。

备注

“最后胜出”策略在位置组级别适用。spring.config.locationclasspath:/cfg/,classpath:/ext/ 时,其覆盖规则与 classpath:/cfg/;classpath:/ext/ 不同。

例如,继续我们上面的 prod,live 示例,可能会有以下文件:

/cfg
application-live.properties
/ext
application-live.properties
application-prod.properties

spring.config.locationclasspath:/cfg/,classpath:/ext/ 时,我们会先处理所有 /cfg 文件,再处理所有 /ext 文件:

  1. /cfg/application-live.properties

  2. /ext/application-prod.properties

  3. /ext/application-live.properties

而当 spring.config.locationclasspath:/cfg/;classpath:/ext/ 时(使用 ; 分隔符),我们会在同一级别处理 /cfg/ext

  1. /ext/application-prod.properties

  2. /cfg/application-live.properties

  3. /ext/application-live.properties

Environment 有一组默认的配置文件(默认情况下为 [default]),这些配置文件在没有设置任何激活的配置文件时会被使用。换句话说,如果没有显式激活任何配置文件,那么来自 application-default 的属性会被考虑。

备注

属性文件只会被加载一次。如果你已经直接导入了某个特定配置文件的属性文件,那么它不会再次被导入。

导入额外数据

应用程序属性可以通过 spring.config.import 属性从其他位置导入更多的配置数据。导入操作会在发现时立即处理,并且会被视为插入在声明导入的文档下方的额外文档。

例如,你可能会在类路径的 application.properties 文件中包含以下内容:

spring.application.name=myapp
spring.config.import=optional:file:./dev.properties
properties

这将触发在当前目录中导入一个 dev.properties 文件(如果该文件存在)。从导入的 dev.properties 文件中获取的值将优先于触发导入的文件中的值。在上面的示例中,dev.properties 可以将 spring.application.name 重新定义为不同的值。

无论声明多少次,导入只会进行一次。在属性/YAML 文件的单个文档中,导入定义的顺序并不重要。例如,下面的两个示例会产生相同的结果:

spring.config.import=my.properties
my.property=value
properties
my.property=value
spring.config.import=my.properties
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 包中的 ConfigDataLocationResolverConfigDataLoader 类。

导入无扩展名文件

某些云平台无法为挂载卷中的文件添加文件扩展名。为了导入这些没有扩展名的文件,你需要给 Spring Boot 一个提示,以便它知道如何加载这些文件。你可以在方括号中添加扩展名提示来实现这一点。

例如,假设你有一个 /etc/config/myconfig 文件,你希望将其作为 YAML 导入。你可以使用以下方式从 application.properties 中导入它:

spring.config.import=file:/etc/config/myconfig[.yaml]
properties

使用配置树

在云平台(如 Kubernetes)上运行应用程序时,您通常需要读取平台提供的配置值。为此,使用环境变量并不罕见,但这可能会有一些缺点,尤其是当值需要保密时。

作为环境变量的替代方案,许多云平台现在允许你将配置映射到挂载的数据卷中。例如,Kubernetes 可以挂载 ConfigMapsSecrets

以下是两种常见的卷挂载模式:

  1. 单个文件包含一整套属性(通常以 YAML 格式编写)。

  2. 多个文件被写入目录树中,文件名成为“键”,文件内容成为“值”。

对于第一种情况,你可以直接使用 spring.config.import 导入 YAML 或 Properties 文件,如上文 所述。对于第二种情况,你需要使用 configtree: 前缀,以便 Spring Boot 知道它需要将所有文件作为属性暴露出来。

例如,我们假设 Kubernetes 已经挂载了以下卷:

etc/
config/
myapp/
username
password
none

username 文件的内容将是一个配置值,而 password 文件的内容将是一个密钥。

要导入这些属性,你可以在 application.propertiesapplication.yaml 文件中添加以下内容:

spring.config.import=optional:configtree:/etc/config/
properties

然后,你可以通过常规方式从Environment访问或注入 myapp.usernamemyapp.password 属性。

提示

配置树下的文件夹和文件名称构成了属性名。在上面的示例中,要访问 usernamepassword 属性,你可以将 spring.config.import 设置为 optional:configtree:/etc/config/myapp

备注

带有点符号的文件名也会被正确映射。例如,在上面的例子中,位于 /etc/config 路径下名为 myapp.username 的文件将会在 Environment 中生成一个 myapp.username 属性。

提示

配置树的值可以绑定到字符串 Stringbyte[] 类型,具体取决于预期的内容。

如果你需要从同一个父文件夹导入多个配置树,可以使用通配符快捷方式。任何以 /*/ 结尾的 configtree: 位置都会将所有直接子文件夹作为配置树导入。与非通配符导入一样,每个配置树下的文件夹和文件名称将构成属性名称。

例如,给定以下卷:

etc/
config/
dbconfig/
db/
username
password
mqconfig/
mq/
username
password
none

你可以使用 configtree:/etc/config/*/ 作为导入位置:

spring.config.import=optional:configtree:/etc/config/*/
properties

这将添加 db.usernamedb.passwordmq.usernamemq.password 属性。

备注

使用通配符加载的目录会按字母顺序排序。如果需要不同的排序方式,你应该将每个位置单独列出作为导入。

配置树也可以用于 Docker 密钥。当 Docker swarm 服务被授予访问密钥的权限时,密钥会被挂载到容器中。例如,如果一个名为 db.password 的密钥被挂载到 /run/secrets/ 位置,你可以通过以下方式使 db.password 在 Spring 环境中可用:

spring.config.import=optional:configtree:/run/secrets/
properties

属性占位符

application.propertiesapplication.yaml 中的值在使用时会通过现有的 Environment 进行过滤,因此你可以引用先前定义的值(例如,来自系统属性或环境变量)。标准的 ${name} 属性占位符语法可以在值的任何地方使用。属性占位符还可以通过使用 : 将默认值与属性名分开来指定默认值,例如 ${name:default}

以下示例展示了带默认值和不带默认值的占位符的使用:

app.name=MyApp
app.description=${app.name} is a Spring Boot application written by ${username:Unknown}
properties

假设 username 属性未在其他地方设置,app.description 的值将为 MyApp 是由 Unknown 编写的 Spring Boot 应用程序

备注

你应该始终使用规范形式(仅使用小写字母的 kebab-case)来引用占位符中的属性名称。这将允许 Spring Boot 使用与宽松绑定相同的逻辑,就像处理 @ConfigurationProperties 时一样。

例如,${demo.item-price} 将从 application.properties 文件中获取 demo.item-pricedemo.itemPrice 形式,以及从系统环境中获取 DEMO_ITEMPRICE。如果你使用 ${demo.itemPrice} 替代,demo.item-priceDEMO_ITEMPRICE 将不会被考虑。

提示

你也可以使用这种技术来创建现有 Spring Boot 属性的“简短”变体。有关详细信息,请参阅“操作指南”中的使用‘简短’命令行参数部分。

处理多文档文件

Spring Boot 允许你将单个物理文件拆分为多个逻辑文档,每个文档都可以独立添加。文档会按照从上到下的顺序进行处理。后面的文档可以覆盖前面文档中定义的属性。

对于 application.yaml 文件,使用的是标准的 YAML 多文档语法。三个连续的连字符表示一个文档的结束,以及下一个文档的开始。

例如,以下文件包含两个逻辑文档:

spring:
application:
name: "MyApp"
---
spring:
application:
name: "MyCloudApp"
config:
activate:
on-cloud-platform: "kubernetes"
yaml

对于 application.properties 文件,使用特殊的 #---!--- 注释来标记文档分割:

spring.application.name=MyApp
#---
spring.application.name=MyCloudApp
spring.config.activate.on-cloud-platform=kubernetes
properties
备注

属性文件分隔符不能有任何前导空格,并且必须恰好有三个连字符。分隔符前后的行不能使用相同的注释前缀。

提示

多文档属性文件通常与激活属性(如 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
spring.config.activate.on-profile=prod | staging
myotherprop=sometimes-set
properties

加密属性

Spring Boot 并没有提供任何内置的属性值加密支持,然而,它确实提供了必要的钩子点来修改 Spring Environment 中包含的值。EnvironmentPostProcessor 接口允许你在应用程序启动之前操作 Environment。有关详细信息,请参阅在启动之前自定义 Environment 或 ApplicationContext

如果你需要一种安全的方式来存储凭据和密码,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"
yaml

为了从 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
properties

同样地,YAML 列表也需要扁平化处理。它们表示为带有 [index] 解引用符的属性键。例如,考虑以下 YAML:

my:
servers:
- "dev.example.com"
- "another.example.com"
yaml

前面的示例将被转换为以下属性:

my.servers[0]=dev.example.com
my.servers[1]=another.example.com
properties
提示

使用 [index] 符号的属性可以通过 Spring Boot 的 Binder 类绑定到 Java 的 ListSet 对象。更多详细信息请参阅下面的 类型安全配置属性 部分。

注意

YAML 文件无法通过使用 @PropertySource@TestPropertySource 注解来加载。因此,如果你需要通过这种方式加载值,你需要使用 properties 文件。

直接加载 YAML

Spring Framework 提供了两个便捷的类,可用于加载 YAML 文档。YamlPropertiesFactoryBean 将 YAML 加载为 Properties,而 YamlMapFactoryBean 将 YAML 加载为 Map

你也可以使用 YamlPropertySourceLoader 类,如果你想将 YAML 作为 Spring 的 PropertySource 加载。

配置随机值

RandomValuePropertySource 对于注入随机值非常有用(例如,注入到密钥或测试用例中)。它可以生成整数、长整型、UUID 或字符串,如下例所示:

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]}
properties

random.int* 语法为 OPEN 值 (,最大值) CLOSE,其中 OPEN, CLOSE 可以是任意字符,值, 最大值 是整数。如果提供了 最大值,则 是最小值,最大值 是最大值(不包括最大值)。

配置系统环境属性

Spring Boot 支持为环境属性设置前缀。这在系统环境被多个具有不同配置需求的 Spring Boot 应用程序共享时非常有用。系统环境属性的前缀可以直接在 SpringApplication 上进行设置。

例如,如果你将前缀设置为 input,那么像 remote.timeout 这样的属性在系统环境中也会被解析为 input.remote.timeout

类型安全的配置属性

使用 @Value("${property}") 注解来注入配置属性有时会显得繁琐,尤其是当你需要处理多个属性或你的数据结构具有层次性时。Spring Boot 提供了另一种处理属性的方法,它允许通过强类型的 bean 来管理和验证应用程序的配置。

:::提示
另请参阅 @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;
}

}

}
java

前面的 POJO 定义了以下属性:

  • my.service.enabled,默认值为 false

  • my.service.remote-address,其类型可以从 String 强制转换。

  • my.service.security.username,带有一个嵌套的 "security" 对象,其名称由属性的名称决定。特别是,类型在那里完全没有使用,可以是 SecurityProperties

  • my.service.security.password

  • my.service.security.roles,带有一个 String 集合,默认值为 USER

提示

要在属性名称中使用保留关键字,例如 my.service.import,可以在属性的字段上使用 @Name 注解。

备注

映射到 Spring Boot 中 @ConfigurationProperties 类的属性是公开的 API,这些属性通过属性文件、YAML 文件、环境变量等机制进行配置,但类本身的访问器(getters/setters)并不直接使用。

备注

这种安排依赖于默认的空构造函数,并且通常需要 getter 和 setter,因为绑定是通过标准的 Java Beans 属性描述符进行的,就像在 Spring MVC 中一样。在以下情况下可以省略 setter:

  • Map,只要它们被初始化,就需要一个 getter,但不一定需要 setter,因为它们可以通过绑定器进行修改。

  • 集合和数组可以通过索引(通常使用 YAML)或使用单个逗号分隔的值(属性)来访问。在后一种情况下,setter 是必需的。我们建议始终为此类类型添加 setter。如果你初始化了一个集合,请确保它不是不可变的(如前面的示例所示)。

  • 如果嵌套的 POJO 属性被初始化(如前例中的 Security 字段),则不需要 setter。如果你希望绑定器使用其默认构造函数动态创建实例,则需要一个 setter。

一些人使用 Project Lombok 来自动添加 getter 和 setter。请确保 Lombok 不会为此类类型生成任何特定的构造函数,因为容器会自动使用默认构造函数来实例化对象。

最后,仅考虑标准的 Java Bean 属性,不支持对静态属性的绑定。

构造函数绑定

上一节中的示例可以以不可变的方式重写,如下例所示:

import java.net.InetAddress;
import java.util.List;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.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;
}

}

}
java

在这种配置中,单个参数化构造函数的存在意味着应该使用构造函数绑定。这意味着绑定器将找到一个包含你希望绑定的参数的构造函数。如果你的类有多个构造函数,可以使用 @ConstructorBinding 注解来指定用于构造函数绑定的构造函数。对于具有单个参数化构造函数的类,如果要选择不使用构造函数绑定,则必须使用 @Autowired 注解标记构造函数,或者将构造函数设为 private。构造函数绑定可以与记录(records)一起使用。除非你的记录有多个构造函数,否则无需使用 @ConstructorBinding

构造函数绑定类的嵌套成员(例如上例中的 Security)也将通过它们的构造函数进行绑定。

可以使用 @DefaultValue 在构造函数参数和记录组件上指定默认值。转换服务将应用于将注解的 String 值强制转换为缺失属性的目标类型。

参考前面的示例,如果没有属性绑定到 SecurityMyProperties 实例中的 security 将会包含一个 null 值。为了使它在没有属性绑定时也能包含一个非空的 Security 实例(在使用 Kotlin 时,由于 Securityusernamepassword 参数没有默认值,因此需要将它们声明为可空),可以使用一个空的 @DefaultValue 注解:

public MyProperties(boolean enabled, InetAddress remoteAddress, @DefaultValue Security security) {
this.enabled = enabled;
this.remoteAddress = remoteAddress;
this.security = security;
}
java
备注

要使用构造函数绑定,必须通过 @EnableConfigurationProperties 或配置属性扫描来启用类。你不能将构造函数绑定用于通过常规 Spring 机制创建的 Bean(例如 @Component 注解的 Bean、使用 @Bean 方法创建的 Bean 或通过 @Import 加载的 Bean)。

备注

要使用构造函数绑定,类必须使用 -parameters 参数进行编译。如果你使用 Spring Boot 的 Gradle 插件,或者使用 Maven 并依赖 spring-boot-starter-parent,这一步骤会自动完成。

备注

不建议将 Optional@ConfigurationProperties 一起使用,因为它主要设计为返回类型。因此,它不适合用于配置属性注入。为了与其他类型的属性保持一致,如果你确实声明了一个 Optional 属性并且它没有值,绑定的将是 null 而不是空的 Optional

提示

要在属性名称中使用保留关键字,例如 my.service.import,可以在构造函数的参数上使用 @Name 注解。

启用 @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 {

}
java
import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties("some.properties")
public class SomeProperties {

}
java

要使用配置属性扫描功能,请在你的应用程序中添加 @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 {

}
java
备注

当使用配置属性扫描或通过 @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,如果你需要访问 Environment)。如果你仍然希望通过构造函数注入其他 bean,配置属性 bean 必须使用 @Component 进行注解,并使用基于 JavaBean 的属性绑定。

使用 @ConfigurationProperties 注解的类型

这种配置方式与 SpringApplication 的外部 YAML 配置结合得特别好,如下例所示:

my:
service:
remote-address: 192.168.1.1
security:
username: "admin"
roles:
- "USER"
- "ADMIN"
yaml

要使用 @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();
// ...
}

// ...

}
java
提示

使用 @ConfigurationProperties 还可以让你生成元数据文件,这些文件可以被 IDE 用来为你的自定义键提供自动补全功能。详情请参阅附录

第三方配置

除了使用 @ConfigurationProperties 来注解类之外,你也可以在公共的 @Bean 方法上使用它。当你想将属性绑定到不受你控制的第三方组件时,这样做尤其有用。

要从 Environment 属性中配置一个 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();
}

}
java

任何使用 another 前缀定义的 JavaBean 属性都将以类似于前面的 SomeProperties 示例的方式映射到 AnotherComponent bean 上。

宽松绑定

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;
}

}
java

使用前面的代码,以下属性名称都可以使用:

表 2. 宽松绑定

属性备注
my.main-project.person.first-name推荐在 .properties 和 YAML 文件中使用的 kebab case(短横线分隔)格式。
my.main-project.person.firstName标准的 camel case(驼峰式)语法。
my.main-project.person.first_name下划线格式,作为 .properties 和 YAML 文件中的替代格式使用。
MY_MAINPROJECT_PERSON_FIRSTNAME大写字母格式,建议在使用系统环境变量时使用。
备注

注解的 prefix必须采用 kebab case 格式(全部小写并用 - 分隔,例如 my.main-project.person)。

表 3. 每个属性源的宽松绑定规则

属性源简单类型列表类型
属性文件驼峰式、短横线式或下划线式命名法使用 [ ] 的标准列表语法或逗号分隔的值
YAML 文件驼峰式、短横线式或下划线式命名法标准的 YAML 列表语法或逗号分隔的值
环境变量大写字母格式,使用下划线作为分隔符(参见从环境变量绑定)。数值用下划线包围(参见从环境变量绑定
系统属性驼峰式、短横线式或下划线式命名法使用 [ ] 的标准列表语法或逗号分隔的值
提示

我们建议,在可能的情况下,属性应以小写连字符格式存储,例如 my.person.first-name=Rod

绑定映射

当绑定到 Map 属性时,你可能需要使用特殊的括号表示法,以保留原始的 key 值。如果 key 没有被 [] 包围,任何非字母数字、-. 的字符都会被移除。

例如,考虑将以下属性绑定到一个 Map<String,String>

my.map[/key1]=value1
my.map[/key2]=value2
my.map./key3=value3
properties
备注

对于 YAML 文件,为了让键能够正确解析,方括号需要用引号括起来。

上述属性将绑定到一个 Map 上,其中 /key1/key2key3 作为 Map 中的键。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 变量只能包含字母(azAZ)、数字(09)或下划线字符(_)。按照惯例,Unix 的 shell 变量名称通常使用大写字母。

Spring Boot 的宽松绑定规则尽可能地设计为与这些命名限制兼容。

要将规范形式的属性名称转换为环境变量名称,您可以遵循以下规则:

  • 将点号 (.) 替换为下划线 (_)。

  • 删除所有破折号 (-)。

  • 转换为大写字母。

例如,配置属性 spring.main.log-startup-info 对应的环境变量名称为 SPRING_MAIN_LOGSTARTUPINFO

环境变量也可以用于绑定到对象列表。要将环境变量绑定到一个 List,变量名中的元素编号应该用下划线包围。

例如,配置属性 my.service[0].other 将使用名为 MY_SERVICE_0_OTHER 的环境变量。

对环境变量绑定的支持已应用于 systemEnvironment 属性源,以及任何名称以 -systemEnvironment 结尾的附加属性源。

从环境变量绑定映射

当 Spring Boot 将环境变量绑定到属性类时,它会在绑定之前将环境变量名称转换为小写。大多数情况下,这个细节并不重要,除非是绑定到 Map 属性时。

Map 中的键始终为小写,如下例所示:

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;
}

}
java

当设置 MY_PROPS_VALUES_KEY=value 时,values Map 会包含一个 {"key"="value"} 的条目。

只有环境变量的名称会被转换为小写,而值不会。当设置 MY_PROPS_VALUES_KEY=VALUE 时,values Map 会包含一个 {"key"="VALUE"} 的条目。

缓存

Relaxed binding 使用缓存来提高性能。默认情况下,此缓存仅应用于不可变的属性源。要自定义此行为,例如为可变属性源启用缓存,请使用 ConfigurationPropertyCaching

合并复杂类型

当列表在多个地方进行配置时,覆盖操作会通过替换整个列表来实现。

例如,假设有一个 MyPojo 对象,其 namedescription 属性默认均为 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;
}

}
java

请考虑以下配置:

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
properties

如果 dev 配置文件未激活,MyProperties.list 包含一个 MyPojo 条目,如前所述。然而,如果启用了 dev 配置文件,list 仍然 只包含一个条目(名称为 my another name,描述为 null)。此配置 不会 向列表中添加第二个 MyPojo 实例,也不会合并条目。

当一个 List 在多个配置文件中被指定时,只有优先级最高的那个会被使用。考虑以下示例:

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
properties

在前面的例子中,如果 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;
}

}
java

考虑以下配置:

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
properties

如果 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),或者通过 CustomEditorConfigurer bean 提供自定义属性编辑器,或者使用带有 @ConfigurationPropertiesBinding 注解的 bean 定义来提供自定义转换器。

备注

由于这个 bean 在应用生命周期的早期就被请求,因此请确保限制你的 ConversionService 所使用的依赖项。通常情况下,你所需的任何依赖项在创建时可能还没有完全初始化。如果你的自定义 ConversionService 不是用于配置键的强制转换,并且仅依赖于使用 @ConfigurationPropertiesBinding 限定的自定义转换器,那么你可能需要重命名它。

转换持续时间

Spring Boot 提供了专门的支持来表示持续时间。如果你暴露了一个 Duration 属性,那么在应用程序属性中可以使用以下格式:

  • 常规的 long 表示形式(默认使用毫秒作为单位,除非指定了 @DurationUnit

  • 标准的 ISO-8601 格式(由 Duration 使用)

  • 一种更易读的格式,其中值和单位结合在一起(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;
}

}
java

要指定会话超时时间为 30 秒,30PT30S30s 都是等价的。读取超时时间为 500 毫秒可以用以下任意一种形式指定:500PT0.5S500ms

你也可以使用任何受支持的单位。这些单位包括:

  • 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;
}

}
java
提示

如果你在升级一个 Long 属性,请确保如果单位不是毫秒,需要定义单位(使用 @DurationUnit)。这样做可以在支持更丰富格式的同时,提供透明的升级路径。

转换时间段

除了持续时间(durations),Spring Boot 还可以处理 Period 类型。在应用程序属性中可以使用以下格式:

  • 一个常规的 int 表示形式(默认使用天作为单位,除非指定了 @PeriodUnit

  • 标准的 ISO-8601 格式 Period 使用

  • 一种更简单的格式,其中值和单位对是耦合的(1y3d 表示 1 年和 3 天)

以下单位支持简单格式:

  • y 代表年

  • m 代表月

  • w 代表周

  • d 代表天

备注

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;
}

}
java

要指定 10 兆字节的缓冲区大小,1010MB 是等价的。256 字节的大小阈值可以指定为 256256B

你也可以使用任何受支持的单位。这些单位包括:

  • 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;
}

}
java
提示

如果你正在升级一个 Long 类型的属性,请确保在单位不是字节时定义单位(使用 @DataSizeUnit)。这样做可以在支持更丰富格式的同时,提供透明的升级路径。

转换 Base64 数据

Spring Boot 支持解析经过 Base64 编码的二进制数据。如果你暴露了一个 Resource 属性,可以使用 base64: 前缀提供 Base64 编码的文本作为值,如下例所示:

my.property=base64:SGVsbG8gV29ybGQ=
properties
备注

Resource 属性也可用于提供资源的路径,使其更具通用性。

@ConfigurationProperties 验证

Spring Boot 会在 @ConfigurationProperties 类被 Spring 的 @Validated 注解标记时尝试对其进行验证。你可以直接在配置类上使用 JSR-303 jakarta.validation 约束注解。为此,请确保你的类路径上有一个符合 JSR-303 标准的实现,然后在字段上添加约束注解,如下例所示:

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;
}

}
java
提示

你也可以通过在创建配置属性的 @Bean 方法上添加 @Validated 注解来触发验证。

为了将验证级联到嵌套属性,关联字段必须使用 @Valid 注解。以下示例基于前面的 MyProperties 示例进行扩展:

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;
}

}

}
java

你也可以通过创建一个名为 configurationPropertiesValidator 的 bean 定义来添加自定义的 Spring Validator@Bean 方法应该声明为 static。配置属性验证器在应用程序生命周期的早期就被创建,而将 @Bean 方法声明为静态的,可以无需实例化 @Configuration 类就能创建该 bean。这样做可以避免任何可能由早期实例化引起的问题。

提示

spring-boot-actuator 模块包含一个端点,该端点会暴露所有的 @ConfigurationProperties Bean。您可以通过浏览器访问 /actuator/configprops 或使用等效的 JMX 端点。详情请参见 生产就绪特性 部分。

@ConfigurationProperties 与 @Value 的比较

@Value 注解是 Spring 容器的核心特性之一,但它并不提供与类型安全配置属性相同的功能。下表总结了 @ConfigurationProperties@Value 所支持的功能:

特性@ConfigurationProperties@Value
宽松绑定有限(参见下方说明
元数据支持
SpEL 表达式求值
备注

如果你想使用 @Value,我们建议你使用属性的规范形式(kebab-case,仅使用小写字母)来引用属性名称。这将允许 Spring Boot 使用与 宽松绑定 @ConfigurationProperties 相同的逻辑。

例如,@Value("${demo.item-price}") 将会从 application.properties 文件中获取 demo.item-pricedemo.itemPrice 形式的值,以及从系统环境中获取 DEMO_ITEMPRICE。如果你使用 @Value("${demo.itemPrice}") 代替,demo.item-priceDEMO_ITEMPRICE 将不会被考虑。

如果你为自己的组件定义了一组配置键,我们建议你将它们分组到一个用 @ConfigurationProperties 注解的 POJO 中。这样做将为你提供一个结构化的、类型安全的对象,你可以将其注入到自己的 bean 中。

在解析这些文件并填充环境时,来自应用程序属性文件SpEL 表达式不会被处理。然而,可以在 @Value 中编写 SpEL 表达式。如果应用程序属性文件中的属性值是 SpEL 表达式,那么在使用 @Value 时,它将被求值。