Spring Boot外部配置加载顺序

官网地址:

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")。
    • envconfigprops端点在确定为什么某个属性具有特定值时是有用的。你可以使用这两个端点来诊断意外的属性值。有关详细信息,请参阅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 对象类型,例如 StringIntegerDouble 等),键名包含点 (.) 时,不需要使用方括号包裹。这些键的点会被保留。

例如,有如下配置:

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: 不直接支持文档生成,需要额外的文档工作来说明每个属性的用途和配置。

注意

本文大多翻译自原文,进行了适当补充与扩展。

相关推荐

  1. Spring Boot外部配置顺序

    2024-07-21 05:18:02       18 阅读
  2. springboot配置文件(三)外部配置文件

    2024-07-21 05:18:02       47 阅读
  3. 【spring】外部配置文件

    2024-07-21 05:18:02       22 阅读
  4. springBoot配置文件

    2024-07-21 05:18:02       107 阅读
  5. springboot配置过程

    2024-07-21 05:18:02       26 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-21 05:18:02       52 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-21 05:18:02       54 阅读
  3. 在Django里面运行非项目文件

    2024-07-21 05:18:02       45 阅读
  4. Python语言-面向对象

    2024-07-21 05:18:02       55 阅读

热门阅读

  1. 【前后端联调】MethodArgumentNotValidException

    2024-07-21 05:18:02       15 阅读
  2. Vue的自定义事件:组件间通讯的艺术

    2024-07-21 05:18:02       15 阅读
  3. Spring中存储Bean的相关注解及用法

    2024-07-21 05:18:02       17 阅读
  4. Perl中的时间机器:探索文件系统同步机制

    2024-07-21 05:18:02       16 阅读
  5. Perl异步编程新纪元:非阻塞I/O的魔力

    2024-07-21 05:18:02       18 阅读
  6. Perl线程调度优化:掌握线程优先级的艺术

    2024-07-21 05:18:02       13 阅读
  7. 渗透测试过程中如何做好个人防护?

    2024-07-21 05:18:02       20 阅读
  8. [ptrade交易实战] 第十七篇 期货交易类函数!

    2024-07-21 05:18:02       21 阅读
  9. 【C++11】initializer_list、可变参数模板详解

    2024-07-21 05:18:02       21 阅读
  10. 踏进互动科技世界使用Arduino

    2024-07-21 05:18:02       16 阅读
  11. 第五节shell脚本中的运行流程控制(1)(2)

    2024-07-21 05:18:02       18 阅读
  12. Oracle外键约束的三种删除行为

    2024-07-21 05:18:02       18 阅读