Skip to content

配置外部化

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

属性值可以通过以下方式注入到 Bean 中:

• 使用 @Value 注解直接注入。

• 通过 Spring 的 Environment 抽象访问。

• 使用 @ConfigurationProperties 绑定到结构化对象

Spring Boot 使用特定的 PropertySource 顺序,设计为允许合理地覆盖值。后加载的属性源会覆盖之前定义的值。以下是属性源的考虑顺序:

  1. 默认属性(通过调用 SpringApplication.setDefaultProperties 指定)。

  2. @PropertySource 注解(位于 @Configuration 类中)。注意,这些属性源直到应用上下文刷新时才添加到环境中,因此无法用于配置刷新前读取的属性(如 logging.*spring.main.* )。

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

  4. RandomValuePropertySource (仅包含 random.* 属性)。

  5. 操作系统环境变量。

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

  7. JNDI 属性( java:comp/env

  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 和 YAML 文件, .properties 文件优先

环境变量与系统属性 :操作系统通常不支持用点分隔的键名,但可以使用下划线替代(例如 SPRING_CONFIG_NAME 代替 spring.config.name )。详情参见从环境变量绑定

Servlet 容器中的配置 :如果应用程序运行在 Servlet 容器或应用服务器中,可以使用 JNDI 属性( java:comp/env )或 Servlet 上下文初始化参数,替代或补充环境变量或系统属性

假设开发了一个 @Component ,并使用了名为 name 的属性,示例如下:

java
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class MyBean {

	@Value("${name}")
	private String name;

	// ...

}

在你的应用程序类路径中(例如,在 JAR 文件内),可以包含一个 application.properties 文件,为属性 name 提供一个合理的默认值

当在新环境中运行时,可以在 JAR 文件外部提供一个 application.properties 文件,用于覆盖默认的 name

对于一次性测试,你可以使用特定的命令行参数启动应用程序,例如: java -jar app.jar --name="Spring"

envconfigprops 端点对于确定某个属性为什么具有特定的值非常有用。你可以使用这两个端点来诊断意外的属性值。详细信息请参阅 生产就绪功能 章节

访问命令行属性

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

如果你不希望命令行属性被添加到环境中,可以通过使用 SpringApplication.setAddCommandLineProperties(false) 来禁用它们

Json 应用属性

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

当应用程序启动时,任何 spring.application.jsonSPRING_APPLICATION_JSON 属性将会被解析并添加到环境中

例如, SPRING_APPLICATION_JSON 属性可以在 UN*X shell 中作为环境变量提供:

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

在上述例子中,最终你会得到 my.name=test 这个属性值被添加到 Spring Environment

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

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

或者你也可以通过命令行参数提供 JSON:

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

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

尽管来自 JSON 的 null 值会被添加到结果属性源中, PropertySourcesPropertyResolver 会将 null 属性视为缺失的值。这意味着 JSON 不能用 null 值覆盖较低顺序的属性源中的属性

外部应用配置文件

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

  • 从 classpath
  • 从当前目录

这些位置按优先级排序(后面的文件会覆盖前面的配置)。从这些文件加载的内容会作为 PropertySources 添加到 Spring Environment

如果不想使用默认的 application 作为配置文件名,可以通过 spring.config.name 环境属性切换到其他文件名。例如,要查找 myproject.propertiesmyproject.yaml ,可以这样运行应用:

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

可以通过 spring.config.location 环境属性显式指定配置文件的位置。该属性接受一个用逗号分隔的文件或目录列表。以下示例指定了两个文件:

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

使用 optional: 前缀可以表示这些位置是可选的,如果不存在也不会报错

spring.config.namespring.config.locationspring.config.additional-location 会在应用启动的非常早期阶段使用,以确定需要加载的文件。因此,必须以环境属性的形式定义(例如操作系统环境变量、系统属性或命令行参数)。

如果 spring.config.location 包含目录,必须以 / 结尾。运行时,Spring Boot 会将其与 spring.config.name 生成的文件名结合后加载

目录和文件位置的值也会扩展用以检查与配置文件(Profile)相关的文件。例如,如果设置了以下 spring.config.location 值: classpath:myconfig.properties , 那么 Spring Boot 也会加载相应的配置文件,例如: classpath:myconfig-<profile>.properties

在大多数情况下,每个 spring.config.location 项通常会引用一个单独的文件或目录。配置位置按照定义的顺序进行处理,后面的位置可以覆盖前面的值

如果您的位置设置较为复杂,并且使用了与配置文件(Profile)相关的配置文件,则可能需要提供更多的提示,让 Spring Boot 知道这些位置应该如何分组。 位置组(Location Group) 是将多个位置视为同一级别的一种方式。例如,您可能希望将所有 classpath 位置分组在一起,然后再分组所有外部位置。同一位置组内的条目应使用分号 ; 分隔。有关更多详细信息,请参阅 Profile Specific Files 部分中的示例。

通过 spring.config.location 配置的路径将替换默认位置。例如,如果使用以下值配置 spring.config.locationoptional:classpath:/custom-config/,optional:file:./custom-config/

则考虑的完整位置集为:

  • optional:classpath:custom-config/
  • optional:file:./custom-config/

如果您更倾向于在默认位置之外添加额外的位置,而不是替换默认位置,则可以使用 spring.config.additional-location 。从额外位置加载的属性可以覆盖默认位置中的值。例如,如果使用以下值配置 spring.config.additional-locationoptional: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 指定的其他 basename 文件)中提供应用的默认值,然后在运行时通过位于自定义位置的不同文件覆盖这些默认值

可选位置 (Optional Locations)

默认情况下,如果指定的配置数据位置不存在,Spring Boot 会抛出 ConfigDataLocationNotFoundException ,并阻止您的应用启动。

如果您希望指定一个位置,但不介意该位置是否始终存在,可以使用 optional: 前缀。该前缀可以与以下属性结合使用:

例如,设置 spring.config.import 的值为 optional:file:./myconfig.properties ,即使 myconfig.properties 文件缺失,您的应用也可以正常启动。

如果您希望忽略所有 ConfigDataLocationNotFoundException 并始终启动应用,可以使用 spring.config.on-not-found 属性。将其值设置为 ignore ,可以通过以下方式配置:

  • 使用 SpringApplication.setDefaultProperties(...)
  • 通过系统或环境变量进行设置

这样可以确保无论任何配置位置是否存在,应用都会正常启动

通配符位置 (Wildcard Locations)

如果配置文件位置的最后一个路径段包含 * 字符,则被视为通配符位置。当加载配置时,通配符会展开,以检查该路径的直接子目录。通配符位置在某些环境(例如 Kubernetes)中特别有用,当存在多个配置属性来源时,可以有效管理这些配置。

假设有一些 Redis 和 MySQL 的配置文件,希望将这些配置分开管理,但同时要求它们都存在于 application.properties 文件中。可能的文件路径如下:

  • /config/redis/application.properties
  • /config/mysql/application.properties

在这种情况下,可以使用通配符位置 config/*/ ,这样两者的配置文件都会被处理

Spring Boot 默认包含 config/*/ 在其搜索路径中。这意味着, /config 目录的所有子目录(在 jar 包外)都会被自动搜索

您可以使用以下属性自定义通配符位置: spring.config.locationspring.config.additional-location

通配符位置必须仅包含一个 * ,如果是目录路径,则必须以 */ 结尾, 如果是文件路径,则必须以 */<filename> 的形式结尾

使用通配符查找的位置会按照文件名的绝对路径字母顺序进行排序

通配符位置仅适用于外部目录。您不能在 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.location 被设置为 classpath:/cfg/,classpath:/ext/ ,其覆盖规则将不同于 classpath:/cfg/;classpath:/ext/

例如,基于前面提到的 prod , live 配置文件,我们可能有以下文件:

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

spring.config.location 被设置为 classpath:/cfg/,classpath:/ext/ 时,Spring Boot 会先处理所有 /cfg 文件,然后再处理所有 /ext 文件:

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

当使用 classpath:/cfg/;classpath:/ext/ (使用分号 ; 作为分隔符)时,Spring Boot 会将 /cfg /ext 视为同一级别进行处理:

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

Environment 提供了一组默认的配置文件(默认是 [default] ),当没有设置活动配置文件时将会使用这些默认配置文件。换句话说,如果没有显式激活任何配置文件,那么会使用 application-default 中的属性值

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

导入额外数据

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

例如,您的 classpath 下的 application.properties 文件可能包含以下内容:

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

上述例子会触发当前目录下的 dev.properties 文件的导入(如果该文件存在)。从 dev.properties 导入的值将优先于触发导入的文件中的值。例如,在上述情况下, dev.properties 可以将 spring.application.name 重定义为一个不同的值。

无论某个导入被声明多少次,它只会被导入一次。此外,导入在单个属性文件或 YAML 文件中的声明顺序并不重要。例如,以下两个例子产生的结果相同:

spring.config.import=my.properties
my.property=value
my.property=value
spring.config.import=my.properties

在上述两个例子中,从 my.properties 文件导入的值将优先于触发其导入的文件中的值。

可以在单个 spring.config.import 键下指定多个位置。位置会按照定义的顺序依次处理,后面的导入会覆盖前面的值。

在适当情况下,也会考虑配置文件(Profile)特定的变体。例如,上述例子会导入 my.properties 和任何 my-<profile>.properties 变体(例如 my-dev.propertiesmy-prod.properties )。

spring.config.import=classpath:base.properties,classpath:my.properties

此时,my.properties 中的值会覆盖 base.properties 中的值。

支持按配置文件的变体

扩展支持的导入位置

自定义导入位置

这些类允许你定义自己的导入位置逻辑和加载方式,从而满足特定需求。

Spring Boot 提供了可插拔的 API,用于支持各种导入地址。默认情况下,你可以导入 Java PropertiesYAML配置树

此外,第三方 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]

使用配置树

在云平台(如 Kubernetes)上运行应用程序时,通常需要读取平台提供的配置值。尽管环境变量经常用于此目的,但如果值需要保密,使用环境变量可能会存在一些缺点。

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

两种常见的挂载模式

  • 单个文件包含完整的属性集 ( 通常以 YAML 格式编写)
  • 多个文件写入目录树 ( 文件名作为 ‘key’,文件内容作为 ‘value’)。

对于单文件模式, 可以直接使用 spring.config.import 导入 YAML 或 Properties 文件(如前文所述)。 对于目录树模式, 需要使用 configtree: 前缀,这样 Spring Boot 就能识别目录树并将所有文件的内容作为属性公开。

作为示例,我们假设 Kubernetes 已挂载了以下卷:

etc/
  config/
    myapp/
      username
      password

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

要导入这些属性,您可以将以下内容添加到您的 application.propertiesapplication.yaml 文件中:

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

然后,您可以以通常的方式从 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

你可以使用 configtree:/etc/config/*/ 作为导入路径

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

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

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

配置树也可以用于 Docker 密钥。当 Docker Swarm 服务被授予访问密钥的权限时,该密钥会被挂载到容器中。例如,如果一个名为 db.password 的密钥被挂载到 /run/secrets/ 目录下,您可以通过以下配置将 db.password 提供给 Spring 环境:

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

属性占位符

在使用 application.propertiesapplication.yaml 中的值时,这些值会通过现有的 Environment 进行过滤,因此您可以引用之前定义的值(例如,来自系统属性或环境变量的值)。

可以在值的任何地方使用标准的 ${name} 属性占位符语法。

属性占位符还可以通过使用冒号 : 来指定默认值,用于在属性名称之后分隔默认值。例如: ${name:default}

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

app.name=MyApp
app.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-pricedemo.itemPrice 的值,同时也会从系统环境中获取 DEMO_ITEMPRICE 的值。

但如果使用 ${demo.itemPrice} ,则不会考虑 demo.item-priceDEMO_ITEMPRICE

您还可以使用这种技术为现有的 Spring Boot 属性创建“简短”形式的变量。有关详细信息,请参阅“操作指南”中的使用“简短”命令行参数部分。

处理多文档文件

Spring Boot 允许您将一个物理文件拆分为多个逻辑文档,每个文档都会被独立添加。这些文档会按从上到下的顺序依次处理。后面的文档可以覆盖前面文档中定义的属性。

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

例如, 以下文件有两个逻辑文档

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

对于 application.properties 文件 ,可以使用特殊的注释标记 #---!--- 来分隔文档。

yaml
spring.application.name=MyApp
#---
spring.application.name=MyCloudApp
spring.config.activate.on-cloud-platform=kubernetes

属性文件的分隔符前不能有任何空格,且必须正好包含三个连字符 ---。分隔符前后紧邻的行不能使用相同的注释前缀

多文档属性文件通常与激活属性(如 spring.config.activate.on-profile )一起使用。有关详细信息,请参阅下一部分。

多文档属性文件不能通过 @PropertySource@TestPropertySource 注解加载。

激活属性

在某些情况下,仅在满足特定条件时激活一组属性是很有用的。例如,您可能有一些属性仅在某个特定的配置文件(profile)处于活动状态时才相关。

您可以使用 spring.config.activate.* 条件性地激活某个属性文档。

以下是可用的激活属性:

PropertyNote
on-profile一个必须匹配的
on-cloud-platform必须检测到的
myprop=always-set
#---
spring.config.activate.on-cloud-platform=kubernetes
spring.config.activate.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 作为属性文件的替代格式。

如果使用 Spring Boot 的 starters, spring-boot-starter 会自动引入 SnakeYAML

将 YAML 映射为属性

YAML 文档需要从其层次结构转换为平面结构,以便与 Spring Environment 配合使用。例如,以下是一个 YAML 文档:

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 列表也需要被扁平化。它们表示为带有 [索引] 的属性键。例如,以下是一个 YAML 示例:

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

上述示例将被转换为以下属性形式

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

使用 [索引] 表示法的属性可以通过 Spring Boot 的 Binder 类绑定到 Java 的 List 或 Set 对象。有关更多信息,请参阅下文的类型安全的配置属性部分。

YAML 文件不能通过 @PropertySource@TestPropertySource 注解加载。因此,如果需要以这种方式加载值,则需要使用属性文件(properties file)。

直接加载 YAML

Spring 框架提供了两个便捷的类,用于加载 YAML 文档。 YamlPropertiesFactoryBean :将 YAML 加载为 PropertiesYamlMapFactoryBean :将 YAML 加载为 Map

如果您想将 YAML 加载为 Spring 的 PropertySource ,还可以使用 YamlPropertySourceLoader

配置随机值

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

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

配置系统环境属性

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

例如,如果您将前缀设置为 input ,则像 remote.timeout 这样的属性将被解析为系统环境中的 input.remote.timeout

类型安全配置属性

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

JavaBean 属性绑定

确实可以通过声明标准的 JavaBean 属性来绑定一个 bean,如下例所示

java
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();

	public boolean isEnabled() {
		return this.enabled;
	}

	public void setEnabled(boolean enabled) {
		this.enabled = enabled;
	}

	public InetAddress getRemoteAddress() {
		return this.remoteAddress;
	}

	public void setRemoteAddress(InetAddress remoteAddress) {
		this.remoteAddress = remoteAddress;
	}

	public Security getSecurity() {
		return this.security;
	}

	public static class Security {

		private String username;

		private String password;

		private List<String> roles = new ArrayList<>(Collections.singleton("USER"));

		public String getUsername() {
			return 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 (Plain Old Java Object) 定义了以下属性:

  • my.service.enabled :默认值为 false
  • my.service.remote-address :类型强制为 String
  • my.service.security.username :带有嵌套的 “security” 对象,其名称由属性的名称决定。特别地,这里的类型完全没有使用,可以是 SecurityProperties
  • my.service.security.password
  • my.service.security.roles :一个 List<String> ,默认值为 USER

映射到 Spring Boot 中 @ConfigurationProperties 类的属性通过属性文件、YAML 文件、环境变量以及其他机制进行配置,这些属性是公共 API,但类本身的访问器(getter/setter)并不建议直接使用。

这种设计依赖于默认的无参构造函数,并且通常需要 getter 和 setter 方法,因为绑定是通过标准的 Java Beans 属性描述符完成的,与 Spring MVC 类似。以下情况可以省略 setter 方法:

  • Map 类型 :只要已初始化,通常只需要 getter 方法,而不需要 setter 方法,因为绑定器可以直接修改它们。
  • 集合和数组 :可以通过索引(通常用于 YAML)访问,或者通过单个逗号分隔的值(用于属性文件)访问。在后一种情况下,setter 方法是必需的。建议始终为此类类型添加 setter 方法。如果初始化了集合,请确保它不是不可变的(如前例中所示)。
  • 嵌套的 POJO 属性 :如果已初始化(例如前例中的 Security 字段),则不需要 setter 方法。如果希望绑定器在需要时通过默认构造函数创建实例,则需要提供 setter 方法。

一些开发者使用 Project Lombok 自动生成 getter 和 setter 方法。请确保 Lombok 不为此类生成特定的构造函数,因为容器会自动使用默认构造函数来实例化对象。

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

构造器绑定

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

java
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 {

	private final boolean enabled;

	private final InetAddress remoteAddress;

	private final Security security;


	public MyProperties(boolean enabled, InetAddress remoteAddress, Security security) {
		this.enabled = enabled;
		this.remoteAddress = remoteAddress;
		this.security = security;
	}

	public boolean isEnabled() {
		return this.enabled;
	}

	public InetAddress getRemoteAddress() {
		return this.remoteAddress;
	}

	public Security getSecurity() {
		return this.security;
	}

	public static class Security {

		private final String username;

		private final String password;

		private final List<String> roles;


		public Security(String username, String password, @DefaultValue("USER") List<String> roles) {
			this.username = username;
			this.password = password;
			this.roles = roles;
		}

		public String getUsername() {
			return this.username;
		}

		public String getPassword() {
			return this.password;
		}

		public List<String> getRoles() {
			return this.roles;
		}

	}

}

在此设置中,单个带参数的构造函数的存在意味着应使用 构造函数绑定 。这表示绑定器将找到一个具有所需绑定参数的构造函数。如果您的类有多个构造函数,可以使用 @ConstructorBinding 注解来指定用于构造函数绑定的构造函数。若要为带参数的单个构造函数类禁用构造函数绑定,则必须将该构造函数标注为 @Autowired 或将其设置为 private 。构造函数绑定也可以用于 record 。如果 record 没有多个构造函数,则无需使用 @ConstructorBinding

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

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

参考前面的示例,如果没有属性绑定到 Security ,则 MyProperties 实例中的 security 将包含一个 null 值。为了在没有任何属性绑定时(使用 Kotlin 时,由于 Securityusernamepassword 参数没有默认值,它们需要声明为可空类型)让 security 包含一个非空实例,可以在构造函数参数上使用空的 @DefaultValue 注解:

java
	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 ,这将自动完成。

不建议@ConfigurationProperties 中使用 java.util.Optional ,因为它主要用于返回类型。在属性注入中, Optional 并不适用。为了与其他类型的属性保持一致,如果声明了一个 Optional 属性且其没有值,绑定结果将是 null ,而不是一个空的 Optional

如果属性名中使用了保留关键字(例如 my.service.import ),可以在构造函数参数上使用 @Name 注解指定名称

启用 @ConfigurationProperties 注解类型

Spring Boot 提供了绑定 @ConfigurationProperties 类型并将其注册为 Bean 的基础设施。您可以按类启用配置属性,也可以启用类似于组件扫描的配置属性扫描。

有时,使用 @ConfigurationProperties 注解的类可能不适合扫描,例如,当您正在开发自己的自动配置或希望有条件地启用这些类时。在这些情况下,可以通过 @EnableConfigurationProperties 注解指定要处理的类型列表。这可以在任意 @Configuration 类中完成,如以下示例所示:

java
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 {

}

要使用配置属性扫描,可以在应用程序中添加 @ConfigurationPropertiesScan 注解。通常,这个注解会添加到使用 @SpringBootApplication 标注的主应用程序类中,但它也可以添加到任何 @Configuration 类中。默认情况下,扫描会从声明该注解的类所在的包开始。如果需要指定特定的包进行扫描,可以按如下示例进行设置:

java
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 的完全限定名。

假设 SomeProperties 类位于 com.example.app 包中,那么上述示例中该 Bean 的名称为: some.properties-com.example.app.SomeProperties

我们建议 @ConfigurationProperties 仅用于处理环境变量,尤其不要从上下文中注入其他 Bean。在特殊情况下,可以使用 setter 注入 或框架提供的 *Aware 接口(例如,如果需要访问 Environment ,可以使用 EnvironmentAware )。

如果仍希望通过构造函数注入其他 Bean,则配置属性类必须使用 @Component 注解,并采用基于 JavaBean 的属性绑定。

使用 @ConfigurationProperties 注解类型

这种配置方式特别适用于 SpringApplication 的外部 YAML 配置,如以下示例所示

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

要使用 @ConfigurationProperties Bean,可以像其他 Bean 一样将其注入,示例如下:

java
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 方法。当需要将属性绑定到您无法控制的第三方组件时,这种方式尤其有用。

要从 Environment 属性配置一个 Bean,可以在其 Bean 注册中添加 @ConfigurationProperties ,如下示例所示:

java
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 属性都会以类似于前面 SomeProperties 示例的方式映射到该 AnotherComponent Bean 上。

宽松绑定

Spring Boot 对将 Environment 属性绑定到 @ConfigurationProperties Bean 使用了一些宽松的规则,因此环境属性名称和 Bean 属性名称之间不需要完全匹配。这在以下常见情况下非常有用:

  • 使用短横线分隔的环境属性(例如,context-path 会绑定到 contextPath)。
  • 大写形式的环境属性(例如,PORT 会绑定到 port)。

例如,考虑以下 @ConfigurationProperties 类:

java
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-nameKebab case
my.main-project.person.firstNameCamel case
my.main-project.person.first_nameUnderscore notation
MY_MAINPROJECT_PERSON_FIRSTNAMEUpper case format

注解的前缀值必须采用 kebab case 格式(即小写字母并用 - 分隔,例如 my.main-project.person

Property SourceSimpleList
Properties 文件Camel case、Kebab case 或 Underscore notation标准列表语法(使用
YAML 文件Camel case、Kebab case 或 Underscore notation 标准 YAML 列表语法或逗号分隔的值
环境变量全大写格式,使用下划线作为分隔符(参见数值可用下划线包裹(参见
系统属性Camel case、Kebab case 或 Underscore notation 标准列表语法(使用

我们建议尽可能以 小写 kebab 格式 存储属性,例如: my.person.first-name=Rod

绑定到 Map 的属性

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

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

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

在 YAML 文件中,键的括号 [] 需要用引号包围,以确保键能够被正确解析

yaml
my:
  map:
    "[/key1]": "value1"
    "[/key2]": "value2"
    "/key3": "value3"

上述属性会绑定到一个 Map 中,键分别为 /key1/key2key3 。由于 key3 没有用方括号 [] 包围,键中的斜杠 / 被移除。

当绑定标量值时,带有 . 的键不需要用 [] 包围。 标量值 包括枚举以及 java.lang 包中除 Object 以外的所有类型。例如,将 a.b=c 绑定到 Map<String, String> 时,键中的 . 会被保留,结果为 {"a.b"="c"}

对于其他类型,如果键包含 . ,则需要使用方括号表示法。例如:

• 绑定 a.b=cMap<String, Object> 时,结果为 {"a"={"b"="c"}}

• 绑定 [a.b]=c 时,结果为 {"a.b"="c"}

从环境变量绑定

大多数操作系统对环境变量的名称有严格限制。例如,Linux Shell 变量只能包含字母( a - zA - Z )、数字( 0 - 9 )或下划线字符( _ )。按照惯例,Unix Shell 变量的名称通常为全大写形式。

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

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

  • 将点号 ( . ) 替换为下划线 ( _ )
  • 移除任何短横线 ( - )
  • 转换为大写字母

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

环境变量也支持绑定到对象列表。要绑定到 List 类型,元素编号应使用下划线包裹在变量名中。例如,配置属性 my.service[0].other 对应的环境变量名为 MY_SERVICE_0_OTHER

支持从环境变量绑定的范围包括 systemEnvironment 属性源以及名称以 -systemEnvironment 结尾的任何额外属性源

从环境变量绑定到 Map

当 Spring Boot 将环境变量绑定到属性类时,会在绑定之前将环境变量名称转换为小写。大多数情况下,这一细节并不重要,但在绑定到 Map 类型属性时需要注意。

在绑定过程中,Map 中的键始终会被转换为小写,如以下示例所示:

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

}

当设置环境变量 MY_PROPS_VALUES_KEY=value 时, valuesMap 中包含一个 {"key"="value"} 的条目。

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

缓存

松散绑定(Relaxed Binding)使用缓存来提升性能。默认情况下,此缓存仅适用于不可变的属性源(immutable property sources)。若要自定义此行为,例如为可变的属性源(mutable property sources)启用缓存,可以使用 ConfigurationPropertyCaching

合并复杂类型

当列表在多个地方配置时,覆盖操作会替换整个列表。

例如,假设有一个 MyPojo 对象,其 namedescription 属性默认都为 null 。以下示例通过 MyProperties 暴露了一个包含 MyPojo 对象的列表:

java
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[0].name=my name
my.list[0].description=my description
#---
spring.config.activate.on-profile=dev
my.list[0].name=my another name

如果 dev 配置文件未激活, MyProperties.list 包含一个先前定义的 MyPojo 条目。但如果启用了 dev 配置文件,列表仍然只包含一个条目( namemy another namedescriptionnull )。此配置不会向列表中添加第二个 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

在前面的示例中,如果 dev 配置文件被激活, MyProperties.list 包含一个 MyPojo 条目( namemy another namedescriptionnull )。对于 YAML,既可以使用逗号分隔的列表,也可以使用 YAML 列表来完全覆盖列表的内容。

对于 Map 属性,您可以将来自多个来源的属性值绑定在一起。然而,对于多个来源中的同一属性,将使用优先级最高的一个。以下示例暴露了来自 MyPropertiesMap<String, MyPojo>

java
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
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

如果 dev 配置文件未激活,则 MyProperties.map 包含一个条目,键为 key1namemy name 1descriptionmy description 1 )。但是,如果启用 dev 配置文件,则 map 包含两个条目,键为 key1namedev name 1descriptionmy description 1 )和 key2namedev name 2descriptiondev description 2 )。

上述合并规则适用于所有属性源的属性,而不仅仅是文件

属性转换

Spring Boot 在将外部应用程序属性绑定到 @ConfigurationProperties beans 时,会尝试将属性转换为正确的类型。如果你需要自定义类型转换,可以提供一个 ConversionService bean(命名为 conversionService ),或者使用自定义属性编辑器(通过 CustomEditorConfigurer bean),或自定义转换器(通过带有 @ConfigurationPropertiesBinding 注解的 bean 定义)。

由于这个 bean 在应用程序生命周期的非常早期就被请求,因此需要确保限制 ConversionService 所使用的依赖。通常,任何你所依赖的组件在创建时可能尚未完全初始化。如果你的 ConversionService 仅用于配置键的强制转换( coercion ),而不需要其他功能,你可能想要重新命名你的自定义 ConversionService ,并仅依赖带有 @ConfigurationPropertiesBinding 注解的自定义转换器。

转换持续时间

Spring Boot 对持续时间(Duration)有专门的支持。如果你在 @ConfigurationProperties 中暴露了一个 java.time.Duration 类型的属性,以下格式在应用程序属性中是可用的:

  • 常规的 long 表示法 :默认使用毫秒作为单位,除非通过 @DurationUnit 指定了其他单位。
  • 标准的 ISO-8601 格式 :这是 java.time.Duration 使用的标准格式
  • 更可读的格式 :值和单位结合在一起表示,例如 10s 表示 10 秒

看下例子:

java
import java.time.Duration;
import java.time.temporal.ChronoUnit;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.convert.DurationUnit;

@ConfigurationProperties("my")
public class MyProperties {

	@DurationUnit(ChronoUnit.SECONDS)
	private Duration sessionTimeout = Duration.ofSeconds(30);

	private Duration readTimeout = Duration.ofMillis(1000);

	public Duration getSessionTimeout() {
		return this.sessionTimeout;
	}

	public void setSessionTimeout(Duration sessionTimeout) {
		this.sessionTimeout = sessionTimeout;
	}

	public Duration getReadTimeout() {
		return this.readTimeout;
	}

	public void setReadTimeout(Duration readTimeout) {
		this.readTimeout = readTimeout;
	}

}

要指定 30 秒的会话超时,可以使用 30PT30S30s ,三者是等效的。读取超时为 500 毫秒可以使用以下任何一种形式指定: 500PT0.5S500ms

你还可以使用以下任何支持的单位:

  • ns 表示纳秒
  • us 表示微秒
  • ms 表示毫秒
  • s 表示秒
  • m 表示分钟
  • h 表示小时
  • d 表示天

默认单位是毫秒,可以使用 @DurationUnit 来覆盖默认单位,正如上面示例中所示。

如果你更倾向于使用构造函数绑定,可以像下面的示例一样暴露相同的属性。

java
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 {

	private final Duration sessionTimeout;

	private final Duration readTimeout;

	public MyProperties(@DurationUnit(ChronoUnit.SECONDS) @DefaultValue("30s") Duration sessionTimeout,
			@DefaultValue("1000ms") Duration readTimeout) {
		this.sessionTimeout = sessionTimeout;
		this.readTimeout = readTimeout;
	}

	public Duration getSessionTimeout() {
		return this.sessionTimeout;
	}

	public Duration getReadTimeout() {
		return this.readTimeout;
	}

}

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

转换周期

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

  • 一个常规的整数表示(默认单位为天,除非使用 @PeriodUnit 指定了其他单位
  • java.time.Period 使用的标准 ISO-8601 格式
  • 一种更简单的格式,其中值和单位成对表示(例如 1y3d 表示 1 年 3 天)。

简单格式支持以下单位:

  • y 表示年
  • m 表示月
  • w 表示周
  • d 表示天

需要注意的是, java.time.Period 类型实际上并不会存储周的数量, w 只是一个快捷方式,表示 “7 天”

转换数据大小

Spring Framework 提供了一个 DataSize 值类型,用于以字节为单位表示大小。如果你公开了一个 DataSize 属性,在应用程序属性中可以使用以下格式:

  • 一个常规的长整型表示(默认单位为字节,除非使用 @DataSizeUnit 指定了其他单位)。
  • 更可读的格式,其中值与单位成对表示(例如, 10MB 表示 10 兆字节)

以下是一个示例:

java
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.convert.DataSizeUnit;
import org.springframework.util.unit.DataSize;
import org.springframework.util.unit.DataUnit;

@ConfigurationProperties("my")
public class MyProperties {

	@DataSizeUnit(DataUnit.MEGABYTES)
	private DataSize bufferSize = DataSize.ofMegabytes(2);

	private DataSize sizeThreshold = DataSize.ofBytes(512);

	public DataSize getBufferSize() {
		return this.bufferSize;
	}

	public void setBufferSize(DataSize bufferSize) {
		this.bufferSize = bufferSize;
	}

	public DataSize getSizeThreshold() {
		return this.sizeThreshold;
	}

	public void setSizeThreshold(DataSize sizeThreshold) {
		this.sizeThreshold = sizeThreshold;
	}

}

要指定 10 兆字节的缓冲区大小,可以使用 1010MB ,它们是等效的。256 字节的大小阈值可以表示为 256256B

你还可以使用以下支持的单位:

  • B 表示字节(Bytes)
  • KB 表示千字节(Kilobytes)
  • MB 表示兆字节(Megabytes)
  • GB 表示千兆字节(Gigabytes)
  • TB 表示太字节(Terabytes)

默认单位是字节(Bytes),可以使用 @DataSizeUnit 来覆盖默认单位,如上例所示

如果你更倾向于使用构造函数绑定,也可以以相同方式公开这些属性,如以下示例所示:

java
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 {

	private final DataSize bufferSize;

	private final DataSize sizeThreshold;

	public MyProperties(@DataSizeUnit(DataUnit.MEGABYTES) @DefaultValue("2MB") DataSize bufferSize,
			@DefaultValue("512B") DataSize sizeThreshold) {
		this.bufferSize = bufferSize;
		this.sizeThreshold = sizeThreshold;
	}

	public DataSize getBufferSize() {
		return this.bufferSize;
	}

	public DataSize getSizeThreshold() {
		return this.sizeThreshold;
	}

}

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

@ConfigurationProperties 校验

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

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

	public InetAddress getRemoteAddress() {
		return this.remoteAddress;
	}

	public void setRemoteAddress(InetAddress remoteAddress) {
		this.remoteAddress = remoteAddress;
	}

}

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

为了确保嵌套属性的验证始终触发,即使没有找到对应的属性,也必须在相关字段上添加 @Valid 注解。以下示例基于前面的 MyProperties 示例:

java
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();

	public InetAddress getRemoteAddress() {
		return this.remoteAddress;
	}

	public void setRemoteAddress(InetAddress remoteAddress) {
		this.remoteAddress = remoteAddress;
	}

	public Security getSecurity() {
		return this.security;
	}

	public static class Security {

		@NotEmpty
		private String username;

		public String getUsername() {
			return this.username;
		}

		public void setUsername(String username) {
			this.username = username;
		}

	}

}

您还可以通过创建名为 configurationPropertiesValidator 的 Bean 定义来添加自定义的 Spring 验证器。 @Bean 方法应声明为静态方法。配置属性验证器在应用程序生命周期的早期被创建,将 @Bean 方法声明为静态方法可以在不实例化 @Configuration 类的情况下创建该 Bean。这样可以避免由于早期实例化可能导致的问题。

spring-boot-actuator 模块包含一个端点,用于暴露所有 @ConfigurationProperties Bean。您可以通过浏览器访问 /actuator/configprops ,或者使用等效的 JMX 端点查看。有关详细信息,请参阅生产就绪特性部分

@ConfigurationProperties 和 @Value

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

Feature@ConfigurationProperties@Value
松散绑定YesLimited (see 
元数据支持YesNo
SpELNoYes

如果你确实希望使用 @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 使用时,该表达式会被解析并求值