外部化配置

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

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

Spring Boot 使用非常特殊的PropertySource顺序,旨在允许对值进行合理的覆盖。后面的属性源可以覆盖前面属性源中定义的值。源按以下顺序考虑:

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

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

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

  4. 一个RandomValuePropertySource,其属性仅在random.*中。

  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,如下例所示

  • Java

  • Kotlin

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

@Component
public class MyBean {

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

	// ...

}
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Component

@Component
class MyBean {

	@Value("\${name}")
	private val name: String? = null

	// ...

}

在您的应用程序类路径上(例如,在您的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中。

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

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

在前面的示例中,您最终在Spring Environment中得到my.name=test

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

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

或者您可以使用命令行参数提供JSON

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

如果您部署到经典的应用程序服务器,您还可以使用名为java:comp/env/spring.application.json的JNDI变量。

虽然来自JSON的null值将添加到生成的属性源中,但PropertySourcesPropertyResolvernull属性视为缺失值。这意味着JSON无法使用null值覆盖来自较低顺序属性源的属性。

外部应用程序属性

当您的应用程序启动时,Spring Boot将自动查找并加载以下位置的application.propertiesapplication.yaml文件

  1. 来自类路径

    1. 类路径根目录

    2. 类路径/config

  2. 来自当前目录

    1. 当前目录

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

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

列表按优先级排序(较低项目的数值会覆盖较早的数值)。加载的文件中的文档作为PropertySources添加到Spring Environment中。

如果您不喜欢将application作为配置文件名,可以通过指定spring.config.name环境属性来切换到另一个文件名。例如,要查找myproject.propertiesmyproject.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.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文件丢失。

如果您想忽略所有ConfigDataLocationNotFoundExceptions并始终继续启动您的应用程序,可以使用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属性自己使用通配符位置。

通配符位置必须只包含一个*,并且以*/结尾(对于作为目录的搜索位置)或*/<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.locationclasspath:/cfg/,classpath:/ext/的覆盖规则与spring.config.locationclasspath:/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

当我们使用classpath:/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文件中可能包含以下内容:

  • 属性

  • YAML

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

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

无论声明多少次,导入都只导入一次。在属性/yaml文件中单个文档内定义导入的顺序无关紧要。例如,以下两个示例产生相同的结果:

  • 属性

  • YAML

spring.config.import=my.properties
my.property=value
spring:
  config:
    import: "my.properties"
my:
  property: "value"
  • 属性

  • YAML

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

在以上两个示例中,来自my.properties文件的值将优先于触发其导入的文件。

可以在单个spring.config.import键下指定多个位置。将按定义的顺序处理位置,后面的导入优先。

在适当情况下,还会考虑用于导入的特定于配置文件的变体。上面的示例将同时导入my.properties以及任何my-<profile>.properties变体。

Spring Boot 包含可插拔 API,允许支持各种不同的位置地址。默认情况下,您可以导入 Java 属性、YAML 和配置树

第三方 jar 可以提供对其他技术的支持(不需要文件是本地的)。例如,您可以想象配置数据来自外部存储,例如 Consul、Apache ZooKeeper 或 Netflix Archaius。

如果您想支持自己的位置,请参阅org.springframework.boot.context.config包中的ConfigDataLocationResolverConfigDataLoader类。

导入无扩展名文件

一些云平台无法向卷装载的文件添加文件扩展名。要导入这些无扩展名的文件,您需要给 Spring Boot 一个提示,以便它知道如何加载它们。您可以通过在方括号中添加扩展名提示来实现。

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

  • 属性

  • YAML

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

使用配置树

在云平台(如 Kubernetes)上运行应用程序时,您通常需要读取平台提供的配置值。使用环境变量来实现此目的并不罕见,但这可能有一些缺点,特别是如果该值应保密的情况下。

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

可以使用两种常见的卷装载模式:

  1. 单个文件包含一组完整的属性(通常以 YAML 编写)。

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

对于第一种情况,您可以使用上面描述的spring.config.import直接导入 YAML 或属性文件。对于第二种情况,您需要使用configtree:前缀,以便 Spring Boot 知道它需要将所有文件作为属性公开。

例如,假设 Kubernetes 已挂载以下卷:

etc/
  config/
    myapp/
      username
      password

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

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

  • 属性

  • YAML

spring.config.import=optional:configtree:/etc/config/
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/*/作为导入位置。

  • 属性

  • YAML

spring.config.import=optional: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 环境:

  • 属性

  • YAML

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

属性占位符

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

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

  • 属性

  • YAML

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

假设username属性未在其他地方设置,则app.description的值将为MyApp is a Spring Boot application written by Unknown

您应该始终使用其规范形式(仅使用小写字母的 kebab-case)引用占位符中的属性名称。这将允许 Spring Boot 使用与松散绑定@ConfigurationProperties时相同的逻辑。

例如,${demo.item-price}将从application.properties文件中获取demo.item-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"

对于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”配置文件时才激活第二个文档。

  • 属性

  • YAML

myprop=always-set
#---
spring.config.activate.on-cloud-platform=kubernetes
spring.config.activate.on-profile=prod | staging
myotherprop=sometimes-set
myprop:
  "always-set"
---
spring:
  config:
    activate:
      on-cloud-platform: "kubernetes"
      on-profile: "prod | staging"
myotherprop: "sometimes-set"

加密属性

Spring Boot 不提供任何内置的属性值加密支持,但是,它确实提供了修改 Spring Environment 中包含的值所需的挂钩点。EnvironmentPostProcessor 接口允许您在应用程序启动之前操作 Environment。有关详细信息,请参见启动前自定义环境或应用程序上下文

如果您需要一种安全的方式来存储凭据和密码,Spring Cloud Vault 项目提供对在 HashiCorp Vault 中存储外部化配置的支持。

使用 YAML

YAML 是 JSON 的超集,因此,它是指定分层配置数据的便捷格式。只要您的类路径上有 SnakeYAML 库,SpringApplication 类就会自动支持 YAML 作为属性的替代方案。

如果您使用启动器,则spring-boot-starter会自动提供 SnakeYAML。

将 YAML 映射到属性

YAML 文档需要从其分层格式转换为可与 Spring Environment 一起使用的扁平结构。例如,考虑以下 YAML 文档

environments:
  dev:
    url: "https://dev.example.com"
    name: "Developer Setup"
  prod:
    url: "https://another.example.com"
    name: "My Cool App"

为了从Environment访问这些属性,它们将被展平如下:

environments.dev.url=https://dev.example.com
environments.dev.name=Developer Setup
environments.prod.url=https://another.example.com
environments.prod.name=My Cool App

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

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

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

my.servers[0]=dev.example.com
my.servers[1]=another.example.com
使用[index]表示法的属性可以使用 Spring Boot 的Binder类绑定到 Java ListSet对象。有关详细信息,请参见下面的类型安全的配置属性部分。
不能使用@PropertySource@TestPropertySource注解加载 YAML 文件。因此,如果您需要以这种方式加载值,则需要使用属性文件。

直接加载 YAML

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

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

配置随机值

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

  • 属性

  • YAML

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]}
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 管理和验证应用程序的配置。

JavaBean 属性绑定

可以绑定声明标准 JavaBean 属性的 Bean,如下例所示:

  • Java

  • Kotlin

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

	}

}
import org.springframework.boot.context.properties.ConfigurationProperties
import java.net.InetAddress

@ConfigurationProperties("my.service")
class MyProperties {

	var isEnabled = false

	var remoteAddress: InetAddress? = null

	val security = Security()

	class Security {

		var username: String? = null

		var password: String? = null

		var roles: List<String> = ArrayList(setOf("USER"))

	}

}

前面的 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

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

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

  • 只要初始化了 Map,它就需要一个 getter,但不一定需要 setter,因为 binder 可以对其进行修改。

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

  • 如果初始化了嵌套 POJO 属性(如前面的示例中的Security字段),则不需要 setter。如果您希望 binder 通过使用其默认构造函数动态创建实例,则需要一个 setter。

有些人使用 Project Lombok 自动添加 getter 和 setter。确保 Lombok 没有为此类型生成任何特定的构造函数,因为它会被容器自动用于实例化对象。

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

构造函数绑定

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

  • Java

  • Kotlin

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

	}

}
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.context.properties.bind.DefaultValue
import java.net.InetAddress

@ConfigurationProperties("my.service")
class MyProperties(val enabled: Boolean, val remoteAddress: InetAddress,
		val security: Security) {

	class Security(val username: String, val password: String,
			@param:DefaultValue("USER") val roles: List<String>)

}

在此设置中,单个参数化构造函数的存在意味着应该使用构造函数绑定。这意味着 binder 将找到一个具有您希望绑定的参数的构造函数。如果您的类具有多个构造函数,则可以使用@ConstructorBinding注解指定要用于构造函数绑定的构造函数。要取消具有单个参数化构造函数的类的构造函数绑定,必须使用@Autowired注解构造函数或将其设为private。构造函数绑定可与记录一起使用。除非您的记录具有多个构造函数,否则无需使用@ConstructorBinding

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

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

参考前面的示例,如果没有属性绑定到Security,则MyProperties实例将包含securitynull值。为了使其即使在没有属性绑定到它时也包含Security的非空实例(在使用 Kotlin 时,这将需要将Securityusernamepassword参数声明为可空,因为它们没有默认值),请使用空的@DefaultValue注解

  • Java

  • Kotlin

	public MyProperties(boolean enabled, InetAddress remoteAddress, @DefaultValue Security security) {
		this.enabled = enabled;
		this.remoteAddress = remoteAddress;
		this.security = security;
	}
class MyProperties(val enabled: Boolean, val remoteAddress: InetAddress,
		@DefaultValue val security: Security) {

	class Security(val username: String?, val password: String?,
			@param:DefaultValue("USER") val roles: List<String>)

}
要使用构造函数绑定,必须使用@EnableConfigurationProperties或配置属性扫描启用该类。您不能将构造函数绑定与通过常规 Spring 机制创建的 Bean 一起使用(例如@Component Bean、使用@Bean方法创建的 Bean 或使用@Import加载的 Bean)。
要使用构造函数绑定,必须使用-parameters编译类。如果您使用 Spring Boot 的 Gradle 插件或使用 Maven 和spring-boot-starter-parent,这将自动发生。
不建议将java.util.Optional@ConfigurationProperties一起使用,因为它主要用于返回值类型。因此,它不适合配置属性注入。为了与其他类型属性保持一致,如果您确实声明了一个Optional属性且它没有值,则将绑定null而不是空Optional
要使用保留字作为属性名称,例如my.service.import,请在构造函数参数上使用@Name注解。

启用@ConfigurationProperties 注解的类型

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

有时,使用@ConfigurationProperties注解的类可能不适合扫描,例如,如果您正在开发自己的自动配置或想要有条件地启用它们。在这些情况下,请使用@EnableConfigurationProperties注解指定要处理的类型列表。这可以在任何@Configuration类上完成,如下例所示

  • Java

  • Kotlin

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.EnableConfigurationProperties
import org.springframework.context.annotation.Configuration

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(SomeProperties::class)
class MyConfiguration
  • Java

  • Kotlin

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

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

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

@ConfigurationProperties("some.properties")
class SomeProperties

要使用配置属性扫描,请将@ConfigurationPropertiesScan注解添加到您的应用程序中。通常,它会添加到使用@SpringBootApplication注解的主应用程序类中,但也可以添加到任何@Configuration类中。默认情况下,扫描将从声明注解的类的包开始。如果您想定义要扫描的特定包,可以按如下例所示进行操作

  • Java

  • Kotlin

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;

@SpringBootApplication
@ConfigurationPropertiesScan({ "com.example.app", "com.example.another" })
public class MyApplication {

}
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.context.properties.ConfigurationPropertiesScan

@SpringBootApplication
@ConfigurationPropertiesScan("com.example.app", "com.example.another")
class MyApplication

当使用配置属性扫描或通过@EnableConfigurationProperties注册@ConfigurationProperties Bean 时,Bean 将具有常规名称:<prefix>-<fqn>,其中<prefix>@ConfigurationProperties注解中指定的环境键前缀,<fqn>是 Bean 的完全限定名称。如果注解没有提供任何前缀,则只使用 Bean 的完全限定名称。

假设它位于com.example.app包中,则上述SomeProperties示例的 Bean 名称是some.properties-com.example.app.SomeProperties

我们建议@ConfigurationProperties只处理环境,特别是不要从上下文中注入其他 Bean。对于极端情况,可以使用setter注入或框架提供的任何*Aware接口(例如,如果您需要访问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 一样注入它们,如下例所示

  • Java

  • Kotlin

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();
		// ...
	}

	// ...

}
import org.springframework.stereotype.Service

@Service
class MyService(val properties: MyProperties) {

	fun openConnection() {
		val server = Server(properties.remoteAddress)
		server.start()
		// ...
	}

	// ...

}
使用@ConfigurationProperties还可以生成元数据文件,IDE 可以使用这些文件为您的键提供自动完成功能。有关详细信息,请参阅附录

第三方配置

除了使用@ConfigurationProperties注解类之外,您还可以将其用于公共@Bean方法。当您想要将属性绑定到您无法控制的第三方组件时,这样做特别有用。

要从Environment属性配置 Bean,请在其 Bean 注册中添加@ConfigurationProperties,如下例所示

  • Java

  • Kotlin

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

}
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration(proxyBeanMethods = false)
class ThirdPartyConfiguration {

	@Bean
	@ConfigurationProperties(prefix = "another")
	fun anotherComponent(): AnotherComponent = AnotherComponent()

}

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

宽松绑定

Spring Boot 使用一些宽松的规则将Environment属性绑定到@ConfigurationProperties Bean,因此Environment属性名称和 Bean 属性名称之间不需要完全匹配。这有用的常见示例包括短横线分隔的环境属性(例如,context-path绑定到contextPath)和大写环境属性(例如,PORT绑定到port)。

例如,考虑以下@ConfigurationProperties

  • Java

  • Kotlin

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

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

@ConfigurationProperties(prefix = "my.main-project.person")
class MyPersonProperties {

	var firstName: String? = null

}

使用上述代码,可以使用以下属性名称:

表 2. 宽松绑定
属性 说明

my.main-project.person.first-name

短横线命名法,建议在.properties和 YAML 文件中使用。

my.main-project.person.firstName

标准驼峰命名法。

my.main-project.person.first_name

下划线命名法,这是.properties和 YAML 文件中使用的替代格式。

MY_MAINPROJECT_PERSON_FIRSTNAME

大写格式,建议在使用系统环境变量时使用。

注解的prefix必须使用短横线命名法(小写并用-分隔,例如my.main-project.person)。
表 3. 各属性源的宽松绑定规则
属性源 简单 列表

属性文件

驼峰命名法、短横线命名法或下划线命名法

使用[ ]或逗号分隔值的标准列表语法

YAML 文件

驼峰命名法、短横线命名法或下划线命名法

标准 YAML 列表语法或逗号分隔值

环境变量

使用下划线作为分隔符的大写格式(请参阅从环境变量绑定)。

用下划线包围的数字值(请参阅从环境变量绑定

系统属性

驼峰命名法、短横线命名法或下划线命名法

使用[ ]或逗号分隔值的标准列表语法

我们建议尽可能将属性存储为小写短横线格式,例如my.person.first-name=Rod

绑定映射

绑定到Map属性时,您可能需要使用特殊的括号表示法才能保留原始key值。如果键没有用[]括起来,则会删除任何非字母数字、-.字符。

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

  • 属性

  • YAML

my.map[/key1]=value1
my.map[/key2]=value2
my.map./key3=value3
my:
  map:
    "[/key1]": "value1"
    "[/key2]": "value2"
    "/key3": "value3"
对于 YAML 文件,键需要用引号括起来才能正确解析。

上面的属性将绑定到一个Map,其中/key1/key2key3作为映射中的键。斜杠已从key3中删除,因为它没有用方括号括起来。

绑定到标量值时,其中包含.的键不需要用[]括起来。标量值包括枚举和java.lang包中的所有类型,除了Object。将a.b=c绑定到Map<String, String>将保留键中的.并返回一个包含条目{"a.b"="c"}的 Map。对于任何其他类型,如果您的key包含.,则需要使用括号表示法。例如,将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中的键始终为小写,如下例所示

  • Java

  • Kotlin

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

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

@ConfigurationProperties(prefix = "my.props")
class MyMapsProperties {

	val values: Map<String, String> = HashMap()

}

设置MY_PROPS_VALUES_KEY=value时,valuesMap包含一个{"key"="value"}条目。

只有环境变量的名称是小写的,而不是值。设置MY_PROPS_VALUES_KEY=VALUE时,valuesMap包含一个{"key"="VALUE"}条目。

缓存

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

合并复杂类型

当在多个位置配置列表时,覆盖是通过替换整个列表来完成的。

例如,假设一个MyPojo对象,其namedescription属性默认为null。以下示例显示了来自MyPropertiesMyPojo对象列表

  • Java

  • Kotlin

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

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

@ConfigurationProperties("my")
class MyProperties {

	val list: List<MyPojo> = ArrayList()

}

考虑以下配置

  • 属性

  • YAML

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
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配置文件,则list仍然只包含一个条目(名称为my another name,说明为null)。此配置不会向列表添加第二个MyPojo实例,也不会合并项目。

当在多个配置文件中指定List时,将使用具有最高优先级(且仅使用该一个)的配置文件。考虑以下示例

  • 属性

  • YAML

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
my:
  list:
  - name: "my name"
    description: "my description"
  - name: "another name"
    description: "another description"
---
spring:
  config:
    activate:
      on-profile: "dev"
my:
  list:
  - name: "my another name"

在上述示例中,如果激活了dev配置文件,则MyProperties.list包含一个MyPojo条目(名称为my another name,说明为null)。对于 YAML,逗号分隔列表和 YAML 列表都可以用于完全覆盖列表的内容。

对于Map属性,您可以使用来自多个源的属性值进行绑定。但是,对于多个源中的相同属性,将使用具有最高优先级的属性。以下示例显示了来自MyPropertiesMap<String, MyPojo>

  • Java

  • Kotlin

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

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

@ConfigurationProperties("my")
class MyProperties {

	val map: Map<String, MyPojo> = LinkedHashMap()

}

考虑以下配置

  • 属性

  • YAML

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
my:
  map:
    key1:
      name: "my name 1"
      description: "my description 1"
---
spring:
  config:
    activate:
      on-profile: "dev"
my:
  map:
    key1:
      name: "dev name 1"
    key2:
      name: "dev name 2"
      description: "dev description 2"

如果未激活dev配置文件,则MyProperties.map包含一个键为key1的条目(名称为my name 1,说明为my description 1)。但是,如果启用了dev配置文件,则map包含两个键为key1的条目(名称为dev name 1,说明为my description 1)和key2(名称为dev name 2,说明为dev description 2)。

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

属性转换

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

由于此 bean 在应用程序生命周期的早期被请求,因此请确保限制您的ConversionService使用的依赖项。通常,您需要的任何依赖项在创建时可能尚未完全初始化。如果您的自定义ConversionService不是必需的配置键强制转换,并且仅依赖于使用@ConfigurationPropertiesBinding限定的自定义转换器,则您可能需要重命名您的自定义ConversionService

转换持续时间

Spring Boot 专为表达持续时间提供了支持。如果您公开一个java.time.Duration属性,则可以使用应用程序属性中的以下格式

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

  • 标准 ISO-8601 格式 java.time.Duration使用

  • 更易读的格式,其中值和单位耦合在一起(10s 表示 10 秒)

考虑以下示例

  • Java

  • Kotlin

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

}
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.convert.DurationUnit
import java.time.Duration
import java.time.temporal.ChronoUnit

@ConfigurationProperties("my")
class MyProperties {

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

	var readTimeout = Duration.ofMillis(1000)

}

要指定 30 秒的会话超时,30PT30S30s 都等效。可以以下列任何形式指定 500 毫秒的读取超时:500PT0.5S500ms

您还可以使用任何支持的单位。这些是

  • ns 表示纳秒

  • us 表示微秒

  • ms 表示毫秒

  • s 表示秒

  • m 表示分钟

  • h 表示小时

  • d 表示天

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

如果您更喜欢使用构造函数绑定,则可以使用相同的属性,如下例所示

  • Java

  • Kotlin

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

}
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.context.properties.bind.DefaultValue
import org.springframework.boot.convert.DurationUnit
import java.time.Duration
import java.time.temporal.ChronoUnit

@ConfigurationProperties("my")
class MyProperties(@param:DurationUnit(ChronoUnit.SECONDS) @param:DefaultValue("30s") val sessionTimeout: Duration,
		@param:DefaultValue("1000ms") val readTimeout: Duration)
如果您正在升级Long属性,如果它不是毫秒,请确保定义单位(使用@DurationUnit)。这样做可以提供透明的升级路径,同时支持更丰富的格式。

转换周期

除了持续时间外,Spring Boot 还可以与java.time.Period类型一起使用。可以在应用程序属性中使用以下格式

  • 常规的int表示法(除非指定了@PeriodUnit,否则使用天作为默认单位)

  • 标准 ISO-8601 格式 java.time.Period使用

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

使用简单格式支持以下单位

  • y 表示年

  • m 表示月

  • w 表示周

  • d 表示天

java.time.Period类型实际上从未存储周数,它是一个表示“7 天”的快捷方式。

转换数据大小

Spring Framework 有一个DataSize值类型,它以字节为单位表示大小。如果您公开一个DataSize属性,则可以使用应用程序属性中的以下格式

  • 常规的long表示法(除非指定了@DataSizeUnit,否则使用字节作为默认单位)

  • 更易读的格式,其中值和单位耦合在一起(10MB 表示 10 兆字节)

考虑以下示例

  • Java

  • Kotlin

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

}
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")
class MyProperties {

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

	var sizeThreshold = DataSize.ofBytes(512)

}

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

您还可以使用任何支持的单位。这些是

  • B 表示字节

  • KB 表示千字节

  • MB 表示兆字节

  • GB 表示千兆字节

  • TB 表示太字节

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

如果您更喜欢使用构造函数绑定,则可以使用相同的属性,如下例所示

  • Java

  • Kotlin

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

}
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")
class MyProperties(@param:DataSizeUnit(DataUnit.MEGABYTES) @param:DefaultValue("2MB") val bufferSize: DataSize,
		@param:DefaultValue("512B") val sizeThreshold: DataSize)
如果您正在升级Long属性,如果它不是字节,请确保定义单位(使用@DataSizeUnit)。这样做可以提供透明的升级路径,同时支持更丰富的格式。

@ConfigurationProperties 验证

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

  • Java

  • Kotlin

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

}
import jakarta.validation.constraints.NotNull
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.validation.annotation.Validated
import java.net.InetAddress

@ConfigurationProperties("my.service")
@Validated
class MyProperties {

	var remoteAddress: @NotNull InetAddress? = null

}
您还可以通过使用@Validated注解创建配置属性的@Bean方法来触发验证。

为了确保始终触发嵌套属性的验证,即使找不到任何属性,也必须使用@Valid注解关联的字段。以下示例基于前面的MyProperties示例

  • Java

  • Kotlin

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

	}

}
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
import java.net.InetAddress

@ConfigurationProperties("my.service")
@Validated
class MyProperties {

	var remoteAddress: @NotNull InetAddress? = null

	@Valid
	val security = Security()

	class Security {

		@NotEmpty
		var username: String? = null

	}

}

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

spring-boot-actuator模块包含一个端点,该端点公开了所有@ConfigurationProperties bean。将您的 Web 浏览器指向/actuator/configprops或使用等效的 JMX 端点。有关详细信息,请参阅生产就绪功能部分。

@ConfigurationProperties 与 @Value

@Value注解是一个核心容器功能,它不提供与类型安全配置属性相同的特性。下表总结了@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使用时将对其进行评估。