|
此版本仍在开发中,尚未被视为稳定版本。如需最新稳定版本,请使用 Spring Boot 4.0.4! |
外部化配置
Spring Boot 允许您将配置外部化,以便您可以在不同环境中使用相同的应用程序代码。 您可以使用多种外部配置源,包括 Java 属性文件、YAML 文件、环境变量以及命令行参数。
属性值可通过使用 @Value 注解直接注入到您的 Bean 中,也可通过 Spring 的 Environment 抽象进行访问,或通过 @ConfigurationProperties 绑定到结构化对象。
Spring Boot 采用一种非常特定的 PropertySource 加载顺序,该顺序旨在支持对属性值进行合理的覆盖。
后加载的属性源可以覆盖先前加载的属性源中定义的值。
源按以下顺序进行考虑:
-
默认属性(通过设置
SpringApplication.setDefaultProperties(Map)指定)。 -
@PropertySource个注解应用于您的@Configuration类。 请注意,此类属性源不会被添加到Environment中,直至应用上下文正在刷新时才添加。 此时已为时过晚,无法配置某些在刷新开始前即已被读取的属性,例如logging.*和spring.main.*。 -
配置数据(例如
application.properties文件)。 -
一个仅在
random.*中具有属性的RandomValuePropertySource。 -
操作系统环境变量。
-
Java 系统属性(
System.getProperties())。 -
来自
java:comp/env的 JNDI 属性。 -
ServletContext个初始化参数。 -
ServletConfig个初始化参数。 -
来自
SPRING_APPLICATION_JSON的属性(以内联 JSON 格式嵌入在环境变量或系统属性中)。 -
命令行参数。
-
properties属性应用于您的测试。 该属性可用于@SpringBootTest以及用于测试应用程序特定切片的 测试注解。 -
@DynamicPropertySource个注解在您的测试中。 -
@TestPropertySource个注解应用于您的测试。 -
Devtools 全局设置属性位于 devtools 启用时的
$HOME/.config/spring-boot目录中。
配置数据文件按以下顺序进行考虑:
-
应用程序属性 打包在您的 JAR 文件中(
application.properties及 YAML 变体)。 -
面向特定配置文件的应用程序属性,打包在您的 JAR 文件中(
application-{profile}.properties格式及 YAML 变体)。 -
应用程序属性 位于您打包的 JAR 文件之外(
application.properties及 YAML 变体)。 -
面向特定配置文件的应用程序属性(位于打包的 JAR 文件之外)(
application-{profile}.properties及其 YAML 变体)。
建议在整个应用程序中统一使用一种格式。
如果在同一位置存在同时采用 .properties 和 YAML 格式的配置文件,则 .properties 格式具有更高优先级。 |
如果使用环境变量而非系统属性,则大多数操作系统不允许键名中包含英文句点,但可以改用下划线(例如,使用 SPRING_CONFIG_NAME 代替 spring.config.name)。
详情请参阅 从环境变量绑定。 |
如果您的应用程序运行在 Servlet 容器或应用服务器中,则可以使用 JNDI 属性(位于 java:comp/env 中)或 Servlet 上下文初始化参数,来替代或补充环境变量及系统属性。 |
为提供一个具体示例,假设您开发了一个@Component,该类使用了name属性,如下例所示:
-
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")。
env 和 configprops 端点有助于确定某个属性为何具有特定值。
您可以使用这两个端点来诊断意外的属性值。
有关详细信息,请参阅 生产就绪功能 章节。 |
访问命令行属性
默认情况下,SpringApplication 会将所有命令行选项参数(即以 -- 开头的参数,例如 --server.port=9000)转换为 property,并将其添加到 Spring 的 Environment 中。
如前所述,命令行属性始终优先于基于文件的属性源。
如果您不希望将命令行属性添加到 Environment 中,则可以通过使用 SpringApplication.setAddCommandLineProperties(false) 来禁用它们。
JSON 应用属性
环境变量和系统属性通常存在一些限制,导致某些属性名称无法使用。 为解决此问题,Spring Boot 允许您将一组属性编码为单个 JSON 结构。
当您的应用程序启动时,所有值为 spring.application.json 或 SPRING_APPLICATION_JSON 的属性将被解析并添加到 Environment 中。
例如,可以在 UNIX 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 值会被添加到最终的属性源中,但 PropertySourcesPropertyResolver 将 null 属性视为缺失值。
这意味着 JSON 无法使用 null 值覆盖低优先级属性源中的属性。 |
外部应用程序属性
Spring Boot 在应用程序启动时会自动查找并加载以下位置中的 application.properties 和 application.yaml 文件:
-
从类路径
-
类路径根目录
-
类路径
/config包
-
-
从当前目录
-
当前目录
-
当前目录下的
config/子目录 -
config/子目录的直接子目录
-
该列表按优先级排序(后加载项的值会覆盖先前项的值)。
已加载文件中的文档将作为 PropertySource 实例添加到 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-<profile>.properties 文件。 |
在大多数情况下,您添加的每个 spring.config.location 项都将引用单个文件或目录。
位置按其定义的顺序进行处理,后定义的位置可覆盖先定义位置的值。
如果您具有复杂的配置文件位置设置,并且使用了特定于配置文件的配置文件,则可能需要提供进一步的提示,以便 Spring Boot 能够识别这些配置文件应如何分组。
位置组是一组被视作同一层级的位置集合。
例如,您可能希望将所有类路径(classpath)位置归为一组,再将所有外部位置归为另一组。
位置组内的各项应使用 ; 分隔。
有关更多详细信息,请参阅特定于配置文件的文件章节中的示例。
使用 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/
此搜索顺序允许您在一个配置文件中指定默认值,然后在另一个配置文件中选择性地覆盖这些默认值。
您可以在某个默认位置的 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 属性。
通过 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.location 和 spring.config.additional-location 属性来实现。
通配符位置必须仅包含一个 *,且对于搜索目录位置,须以 */ 结尾;对于搜索文件位置,则须以 */<filename> 结尾。
含通配符的位置将根据文件名的绝对路径按字母顺序排序。 |
通配符路径仅对外部目录有效。
您不能在 classpath: 路径中使用通配符。 |
特定配置文件的文件
除了 application 属性文件外,Spring Boot 还会尝试使用命名约定 application-{profile} 加载特定于配置文件的文件。
例如,如果您的应用程序启用了名为 prod 的配置文件并使用 YAML 文件,则将同时考虑 application.yaml 和 application-prod.yaml。
面向特定配置文件的属性从与标准 application.properties 相同的位置加载,且面向特定配置文件的文件始终会覆盖非特定配置文件的文件。
如果指定了多个配置文件,则采用“后加载者优先”的策略。
例如,若通过 spring.profiles.active 属性指定了配置文件 prod,live,则 application-prod.properties 中的值可被 application-live.properties 中的值覆盖。
|
“后写入者胜出”策略应用于位置组级别。
例如,延续上面的 /cfg application-live.properties /ext application-live.properties application-prod.properties 当
当我们使用
|
Environment 具有一组默认配置文件(默认为 [default]),当未设置任何激活的配置文件时,将使用这些默认配置文件。
换言之,若未显式激活任何配置文件,则会考虑来自 application-default 的属性。
| 属性文件仅加载一次。 如果您已直接导入了特定配置文件的属性文件,则该文件不会被再次导入。 |
导入其他数据
应用程序属性可通过 spring.config.import 属性从其他位置导入更多配置数据。
导入操作在发现时即被处理,并被视为附加文档,插入到声明该导入的文档正下方。
例如,您的类路径中可能包含以下 application.properties 文件:
-
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 重新定义为另一个值。
| 无论声明多少次,导入项都只会被导入一次。 |
默认情况下,属性文件使用 ISO-8859-1 字符集进行导入。要更改此设置,可以使用 encoding 属性:
-
Properties
-
YAML
spring.config.import=classpath:import.properties[encoding=utf-8]
spring:
config:
import: "classpath:import.properties[encoding=utf-8]"
该 import.properties 文件将使用 UTF-8 编码进行读取。
使用“固定”和“导入相对”位置
导入路径可以指定为固定路径或相对于导入文件的路径。
固定路径始终解析为同一底层资源,与spring.config.import属性在何处声明无关。
相对于导入文件的路径则相对于声明spring.config.import属性的文件进行解析。
以正斜杠(/)开头的位置,或以 URL 风格前缀(如 file:、classpath: 等)开头的位置,被视为固定位置。
所有其他位置均被视为导入相对位置。
optional: 前缀在判断位置是固定位置还是导入相对位置时不予考虑。 |
例如,假设我们有一个名为 /demo 的目录,其中包含我们的 application.jar 文件。
我们可能添加一个名为 /demo/application.properties 的文件,其内容如下:
spring.config.import=optional:core/core.properties
这是一个相对导入路径,因此如果文件 /demo/core/core.properties 存在,将尝试加载该文件。
如果 /demo/core/core.properties 包含以下内容:
spring.config.import=optional:extra/extra.properties
它将尝试加载 /demo/core/extra/extra.properties。
optional:extra/extra.properties 相对于 /demo/core/core.properties,因此完整目录为 /demo/core/ + extra/extra.properties。
属性顺序
在 properties/yaml 文件的单个文档内,import 的定义顺序无关紧要。 例如,以下两个示例会产生相同的结果:
-
Properties
-
YAML
spring.config.import=my.properties
my.property=value
spring:
config:
import: "my.properties"
my:
property: "value"
-
Properties
-
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 等外部存储。 如需支持您自定义的配置位置,请参阅 |
导入无扩展名文件
某些云平台无法为卷挂载的文件添加文件扩展名。 要导入这些没有扩展名的文件,您需要向 Spring Boot 提供提示,以便它知道如何加载这些文件。 您可以通过在方括号中提供一个扩展名提示来实现这一点。
例如,假设您有一个希望作为 YAML 文件导入的 /etc/config/myconfig 文件。
您可以使用以下方式从您的 application.properties 中导入它:
-
Properties
-
YAML
spring.config.import=file:/etc/config/myconfig[.yaml]
spring:
config:
import: "file:/etc/config/myconfig[.yaml]"
这是简写形式:
-
Properties
-
YAML
spring.config.import=file:/etc/config/myconfig[extension=.yaml]
spring:
config:
import: "file:/etc/config/myconfig[extension=.yaml]"
文件属性
如果您需要指定多个属性,可以使用此语法:
-
Properties
-
YAML
spring.config.import=file:/etc/config/myconfig[extension=.yaml][encoding=utf-8]
spring:
config:
import: "file:/etc/config/myconfig[extension=.yaml][encoding=utf-8]"
使用环境变量
在云平台(例如 Kubernetes)上运行应用程序时,通常需要读取平台提供的配置值。 您可以使用环境变量来实现此目的,也可以使用配置树。
您甚至可以将完整的配置以属性(properties)或 YAML 格式存储在(多行)环境变量中,并使用 env: 前缀加载它们。
假设存在一个名为 MY_CONFIGURATION 的环境变量,其内容如下:
my.name=Service1
my.cluster=Cluster1
使用 env: 前缀,可以从此变量中导入所有属性:
-
Properties
-
YAML
spring.config.import=env:MY_CONFIGURATION
spring:
config:
import: "env:MY_CONFIGURATION"
此功能还支持 指定扩展名。
默认扩展名为 .properties。 |
使用配置树
将配置值存储在环境变量中存在缺点,尤其是当该值需要保密时。
作为环境变量的替代方案,许多云平台现在允许您将配置映射到已挂载的数据卷中。
例如,Kubernetes 可以对 ConfigMaps 和 Secrets 执行卷挂载。
有两种常用的卷挂载模式可供使用:
-
单个文件包含一组完整的属性(通常以 YAML 格式编写)。
-
多个文件被写入目录树中,文件名成为“键”,内容成为“值”。
对于第一种情况,您可以直接使用 spring.config.import 导入 YAML 或 Properties 文件,具体方法如上文所述。
对于第二种情况,您需要使用 configtree: 前缀,以便 Spring Boot 知道它需要将所有文件作为属性公开。
例如,假设 Kubernetes 已挂载了以下卷:
etc/
config/
myapp/
username
password
username 文件的内容将是一个配置值,而 password 文件的内容将是一个密钥。
要导入这些属性,您可以在 application.properties 或 application.yaml 文件中添加以下内容:
-
Properties
-
YAML
spring.config.import=optional:configtree:/etc/config/
spring:
config:
import: "optional:configtree:/etc/config/"
然后,您便可以像往常一样访问或注入 myapp.username 和 myapp.password 属性(这些属性位于 Environment 中)。
配置树下的文件夹和文件名称共同构成属性名。
在上述示例中,若要将属性作为 username 和 password 进行访问,可将 spring.config.import 设置为 optional:configtree:/etc/config/myapp。 |
使用点号表示法的文件名也会被正确映射。
例如,在上述示例中,/etc/config 目录下名为 myapp.username 的文件将在 Environment 中生成一个 myapp.username 属性。 |
配置树值可根据预期的内容绑定到字符串类型 String 和 byte[]。 |
如果需要从同一父文件夹中导入多个配置树,可以使用通配符快捷方式。
任何以 /*/ 结尾的 configtree: 位置,都将导入其所有直接子项作为配置树。
与非通配符导入一样,每个配置树下的文件夹和文件名称将构成属性名。
例如,给定以下卷:
etc/
config/
dbconfig/
db/
username
password
mqconfig/
mq/
username
password
您可以将 configtree:/etc/config/*/ 用作导入位置:
-
Properties
-
YAML
spring.config.import=optional:configtree:/etc/config/*/
spring:
config:
import: "optional:configtree:/etc/config/*/"
这将添加 db.username、db.password、mq.username 和 mq.password 属性。
| 使用通配符加载的目录将按字母顺序排序。 如果您需要不同的顺序,则应将每个位置作为单独的 import 进行列出。 |
配置树还可用于 Docker 密钥。
当 Docker Swarm 服务被授予访问某个密钥的权限时,该密钥将挂载到容器中。
例如,如果一个名为 db.password 的密钥挂载在位置 /run/secrets/,则可使用以下方式将 db.password 提供给 Spring 环境:
-
Properties
-
YAML
spring.config.import=optional:configtree:/run/secrets/
spring:
config:
import: "optional:configtree:/run/secrets/"
属性占位符
application.properties 和 application.yaml 中的值在使用时会通过现有的 Environment 进行过滤,因此您可以引用先前已定义的值(例如,来自系统属性或环境变量的值)。
标准的 ${name} 属性占位符语法可在任意值中使用。
属性占位符还可通过 : 指定默认值,以将默认值与属性名称分隔开,例如 ${name:default}。
以下示例展示了带默认值和不带默认值的占位符的用法:
-
Properties
-
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 能够采用与宽松绑定 例如,使用 |
| 您还可以使用此技术为现有的 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 注解加载多文档属性文件。 |
激活属性
有时,仅在满足特定条件时才激活一组给定的属性会很有用。 例如,您可能拥有一些仅在特定 profile 处于激活状态时才相关的属性。
您可以使用 spring.config.activate.* 来有条件地启用属性文档。
以下激活属性可用:
| 属性 | 注意 |
|---|---|
|
一个配置文件表达式,文档要处于激活状态,该表达式必须匹配;或者是一组配置文件表达式,其中至少有一个表达式匹配,文档才能处于激活状态。 |
|
为使文档处于活动状态,必须检测到的 |
例如,以下配置指定第二个文档仅在 Kubernetes 上运行时生效,且仅当激活了 “prod” 或 “staging” 任一环境配置文件时才生效:
-
Properties
-
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 进行操作。
详情请参阅 在 Environment 或 ApplicationContext 启动前进行自定义。
如果您需要一种安全的方式来存储凭据和密码,Spring Cloud Vault 项目提供了对在 HashiCorp Vault 中存储外部化配置的支持。
使用 YAML
YAML 是 JSON 的超集,因此是一种指定分层配置数据的便捷格式。
当类路径中包含 SnakeYAML 库时,SpringApplication 类会自动支持 YAML 作为属性文件的替代格式。
如果使用Starters(Starters),SnakeYAML 会由 spring-boot-starter 自动提供。 |
将 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
无法使用 @PropertySource 或 @TestPropertySource 注解加载 YAML 文件。
因此,如果您需要以这种方式加载配置值,则必须使用属性文件(properties 文件)。 |
直接加载 YAML
Spring 框架提供了两个便捷的类,可用于加载 YAML 文档。
YamlPropertiesFactoryBean 将 YAML 加载为 Properties,而 YamlMapFactoryBean 将 YAML 加载为 Map。
如果希望将 YAML 加载为 Spring 的 PropertySource,您也可以使用 YamlPropertySourceLoader 类。
配置随机值
RandomValuePropertySource 适用于注入随机值(例如,用于密钥或测试用例)。
它可以生成整数、长整型、UUID 或字符串,如下例所示:
-
Properties
-
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 应用程序共享同一系统环境时,此功能非常有用。
可通过在应用程序运行前调用 setEnvironmentPrefix(…) 方法,直接在 SpringApplication 上设置系统环境属性的前缀。
例如,如果将前缀设置为 input,则属性 remote.timeout 将在系统环境中解析为 INPUT_REMOTE_TIMEOUT。
前缀 仅 适用于系统环境属性。
上述示例在从其他来源读取属性时,仍会继续使用 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"))
}
}
上述普通 Java 对象(POJO)定义了以下属性:
-
my.service.enabled,默认值为false。 -
my.service.remote-address,其类型可从String强制转换而来。 -
my.service.security.username,其中嵌套了一个名为“security”的对象,该对象的名称由属性名决定。 特别需要注意的是,此处完全未使用类型信息,因此该类型本可以是SecurityProperties。 -
my.service.security.password. -
my.service.security.roles,并附带一个默认值为USER的String集合。
若要在属性名称中使用保留关键字(例如 my.service.import),请在该属性的字段上使用 @Name 注解。 |
Spring Boot 中映射到 @ConfigurationProperties 类的属性(可通过属性文件、YAML 文件、环境变量及其他机制进行配置)属于公共 API,但该类自身的访问器方法(getter/setter)并非设计用于直接调用。 |
|
此类配置依赖于默认的无参构造函数,且通常必须提供 getter 和 setter 方法,因为绑定是通过标准 Java Bean 的属性描述符(Property Descriptors)完成的,与 Spring MVC 中的方式相同。 在以下情况下,可以省略 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>)
}
在此配置中,存在单个带参数的构造函数意味着应使用构造函数绑定。
这意味着绑定器将查找一个具有您希望绑定的参数的构造函数。
如果您的类包含多个构造函数,则可使用 @ConstructorBinding 注解来指定用于构造函数绑定的构造函数。
若要为某个类禁用构造函数绑定,该带参数的构造函数必须使用 @Autowired 进行注解,或声明为 private。
Kotlin 开发者可使用空的主构造函数来禁用构造函数绑定。
例如:
-
Java
-
Kotlin
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("my")
public class MyProperties {
// fields...
final MyBean myBean;
private String name;
@Autowired
public MyProperties(MyBean myBean) {
this.myBean = myBean;
}
// getters / setters...
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
import org.springframework.boot.context.properties.ConfigurationProperties
@ConfigurationProperties("my")
class MyProperties() {
constructor(name: String) : this() {
this.name = name
}
// vars...
var name: String? = null
}
可以将构造函数绑定与记录(record)一起使用。
除非您的记录具有多个构造函数,否则无需使用 @ConstructorBinding。
构造函数绑定类的嵌套成员(例如上例中的 Security)也将通过其构造函数进行绑定。
要使用构造函数绑定,必须通过 @EnableConfigurationProperties 或配置属性扫描来启用该类。
您无法对通过常规 Spring 机制创建的 Bean 使用构造函数绑定(例如 @Component 类型的 Bean、通过 @Bean 方法创建的 Bean,或通过 @Import 加载的 Bean)。 |
要使用构造函数绑定,该类必须使用 -parameters 进行编译。
如果使用 Spring Boot 的 Gradle 插件,或在使用 Maven 时配置了 spring-boot-starter-parent,则会自动完成此操作。 |
不建议将 Optional 与 @ConfigurationProperties 一起使用,因为它主要设计为用作返回类型。
因此,它并不适合用于配置属性注入。
为了与其他类型属性保持一致,如果您确实声明了一个 Optional 类型的属性且该属性未赋值,则绑定的值将是 null,而非一个空的 Optional。 |
若要在属性名称中使用保留关键字(例如 my.service.import),请在构造函数参数上使用 @Name 注解。 |
默认值
可使用@DefaultValue在构造函数参数和记录组件上指定默认值。
转换服务将应用于强制转换注解的String值,以匹配缺失属性的目标类型。
在上面的 MyProperties 示例中,你可以看到嵌套的 Security 类使用 @DefaultValue("USER") 作为 roles 参数。
这意味着如果定义了 security 属性,但没有定义 roles,则会绑定 "USER" 的默认值。
例如,以下属性:
-
Properties
-
YAML
my.service.enabled=true
my.service.security.username=admin
my:
service:
enabled: true
security:
username: admin
将作为 new MyProperties(true, null, new Security("admin", null, List.of("USER"))) 绑定
如果 security 属性根本不存在,那么 Security 实例将是 null。
例如,以下属性:
-
Properties
-
YAML
my.service.enabled=true
my:
service:
enabled: true
将作为 new MyProperties(true, null, null) 绑定
|
如果您希望使用完全默认的 对于 YAML,您可以使用以下语法:
使用
将作为 |
如果您希望始终绑定一个非空的 Security 实例,即使属性缺失,可以使用空的 @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>)
}
使用 Kotlin 时,您需要将 username 和 password 参数声明为可为空的,因为它们没有默认值 |
启用使用 @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
|
当使用配置属性扫描或通过 假设该类位于 |
我们建议 @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("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("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("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("my.main-project.person")
class MyPersonProperties {
var firstName: String? = null
}
使用上述代码,以下属性名称均可使用:
| 属性 | 注意 |
|---|---|
|
烤肉串命名法(kebab case),建议在 |
|
标准驼峰命名法语法。 |
|
下划线命名法,是 |
|
大写格式,使用系统环境变量时推荐采用此格式。 |
注解的 prefix 值 必须 采用短横线命名法(即小写字母,单词之间用 - 分隔),例如 my.main-project.person。 |
| 属性源 | 简单 | 列表 |
|---|---|---|
属性文件 |
驼峰命名法、短横线命名法或下划线命名法 |
使用 |
YAML 文件 |
驼峰命名法、短横线命名法或下划线命名法 |
标准 YAML 列表语法或逗号分隔的值 |
环境变量 |
使用下划线作为分隔符的大写格式(参见 从环境变量绑定)。 |
用下划线包围的数值(参见 从环境变量绑定) |
系统属性 |
驼峰命名法、短横线命名法或下划线命名法 |
使用 |
我们建议在可能的情况下,将属性以小写短横线格式(kebab-case)存储,例如 my.person.first-name=Rod。 |
绑定映射
绑定到 Map 属性时,您可能需要使用特殊的方括号表示法,以确保原始的 key 值得以保留。
如果键未被 [] 包围,则所有非字母数字、非 - 和非 . 的字符都将被移除。
例如,考虑将以下属性绑定到一个 Map<String,String>:
-
Properties
-
YAML
my.map[/key1]=value1
my.map[/key2]=value2
my.map./key3=value3
my:
map:
"[/key1]": "value1"
"[/key2]": "value2"
"/key3": "value3"
| 对于 YAML 文件,键名周围的方括号需要用引号包围,才能被正确解析。 |
上述属性将绑定到一个 Map,其映射(Map)中的键分别为 /key1、/key2 和 key3。
由于 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 变量名称只能包含字母(a 到 z 或 A 到 Z)、数字(a 到 9)或下划线字符(_)。
按照惯例,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("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("my.props")
class MyMapsProperties {
val values: Map<String, String> = HashMap()
}
将 MY_PROPS_VALUES_KEY=value 设置为 0 时,values Map 中包含一个 {"key"="value"} 条目。
仅环境变量的名称会被转换为小写,其值则不会。
当设置为MY_PROPS_VALUES_KEY=VALUE时,values Map 中包含一个 {"key"="VALUE"} 条目。
缓存
宽松绑定使用缓存来提升性能。默认情况下,此缓存仅应用于不可变的属性源。
若要自定义此行为(例如为可变的属性源启用缓存),请使用 ConfigurationPropertyCaching。
合并复杂类型
当列表在多个位置配置时,覆盖机制会完全替换整个列表。
例如,假设有一个 MyPojo 对象,其具有 name 和 description 两个属性,默认值均为 null。
以下示例从 MyProperties 中公开一个 MyPojo 对象列表:
-
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()
}
请考虑以下配置:
-
Properties
-
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 时,将使用优先级最高(且仅此一个)的配置。
请参阅以下示例:
-
Properties
-
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 个属性,您可以从多个来源绑定属性值。
但是,当多个来源中存在同名属性时,将采用优先级最高的那个属性值。
以下示例从 MyProperties 中暴露一个 Map<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()
}
请考虑以下配置:
-
Properties
-
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),或自定义转换器(使用带有 @ConfigurationPropertiesBinding 注解的 Bean 定义)。
|
用于属性转换的 Bean 在应用程序生命周期的早期即被请求,因此请确保限制您的 |
如果您的自定义 ConversionService 并非用于配置项键值的类型转换,而仅依赖于使用 @ConfigurationPropertiesBinding 注解限定的自定义转换器,则可考虑为其重命名。
当使用 @ConfigurationPropertiesBinding 注解限定一个 @Bean 方法时,该方法应声明为 static,以避免出现“bean 不适用于所有 BeanPostProcessor 的处理”警告。 |
转换持续时间
Spring Boot 专门支持以时长形式表达配置值。
如果您公开一个 Duration 类型的属性,则可在应用属性文件中使用以下格式:
-
常规的
long表示法(默认以毫秒为单位,除非已指定@DurationUnit) -
一种更易读的格式,其中数值与单位成对出现(
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秒,30、PT30S 和 30s 均等效。
读取超时时间为500毫秒,可通过以下任意一种形式指定:500、PT0.5S 和 500ms。
您还可以使用任意受支持的单位。 这些单位包括:
-
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 还可支持 Period 类型。
以下格式可在应用属性中使用:
-
常规的
int表示法(默认以“天”为单位,除非已指定@PeriodUnit) -
一种更简洁的格式,其中数值与单位成对组合(
1y3d表示 1 年零 3 天)
以下单位支持简单格式:
-
y表示年份 -
m表示月份 -
w周 -
d表示天数
Period 类型实际上从不存储周数,它只是一个表示“7天”的快捷方式。 |
转换数据大小
-
常规的
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 兆字节的缓冲区大小,10 和 10MB 是等效的。
256 字节的大小阈值可指定为 256 或 256B。
您还可以使用任意受支持的单位。 这些单位包括:
-
B表示字节 -
KB表示千字节 -
MB表示兆字节 -
GB表示千兆字节 -
TB表示太字节(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 Boot 会在带有 Spring 的 @Configuration 注解的类上,尝试对其进行验证。
您可直接在配置类上使用 JSR-303 的 @Validated 约束注解。
为此,请确保您的类路径中包含符合 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
}
您还可以通过在创建配置属性的 @Bean 方法上添加 @Validated 注解来触发验证。 |
要将验证级联到嵌套属性,关联字段必须使用 @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 方法声明为静态方法,可使该 Bean 在无需实例化 @Configuration 类的情况下被创建。
这样做可避免因过早实例化而引发的任何问题。
spring-boot-actuator 模块包含一个端点,用于公开所有 @ConfigurationProperties Bean。
请在 Web 浏览器中访问 /actuator/configprops,或使用对应的 JMX 端点。
有关详细信息,请参阅 生产就绪功能 章节。 |
@ConfigurationProperties 与 @Value
@Value 注解是容器的核心特性,它并不提供类型安全配置属性所具备的相同功能。
下表总结了 @ConfigurationProperties 和 @Value 所支持的功能:
| 特性 | @ConfigurationProperties |
@Value |
|---|---|---|
是 |
受限(参见下方说明) |
|
是 |
No |
|
|
No |
是 |
|
如果您确实希望使用 例如,使用 |
如果您为自己的组件定义了一组配置项,我们建议您将它们分组到一个使用 @ConfigurationProperties 注解的 POJO 中。
这样做将为您提供一个结构化、类型安全的对象,您可以将其注入到您自己的 Bean 中。