类型安全配置属性
使用@Value("${property}")注释注入配置属性有时会很麻烦,尤其是在使用多个属性或数据本质上是分层的情况下。Spring Boot提供了一种使用属性的替代方法,该方法允许强类型bean管理和验证应用程序的配置,如以下示例所示:
1 | package com.example; |
前面的POJO定义了以下属性:
acme.enabled,默认值为false。acme.remote-address,具有可以强制的类型String。acme.security.username,使用嵌套的“安全”对象,其名称由属性名称决定。特别是,那里根本没有使用返回类型SecurityProperties。acme.security.password。acme.security.roles,收集String。
getter和setter通常是必需的,因为绑定是通过标准的Java Beans属性描述符,就像在Spring MVC中一样。在下列情况下可以省略setter:
- 映射,只要它们被初始化,就需要一个getter但不一定是setter,因为它们可以被绑定器变异。
- 可以通过索引(通常使用YAML)或使用单个逗号分隔值(属性)访问集合和数组。在后一种情况下,必须设置一个setter。我们建议始终为这些类型添加setter。如果初始化集合,请确保它不是不可变的(如上例所示)。
- 如果初始化嵌套的POJO属性(如
Security前面示例中的字段),则不需要setter。如果您希望绑定器使用其默认构造函数动态创建实例,则需要一个setter。有些人使用Project Lombok自动添加getter和setter。确保Lombok不为此类型生成任何特定构造函数,因为容器会自动使用它来实例化对象。
最后,仅考虑标准Java Bean属性,并且不支持对静态属性的绑定。
您还需要列出要在@EnableConfigurationProperties注释中注册的属性类 ,如以下示例所示:
1 |
|
当
@ConfigurationPropertiesbean以这种方式注册时,bean具有常规名称:<prefix>-<fqn>,其中<prefix>是@ConfigurationProperties注释中指定的环境键前缀,并且<fqn>是bean的完全限定名称。如果注释未提供任何前缀,则仅使用bean的完全限定名称。上例中的bean名称是
acme-com.example.AcmeProperties。
即使前面的配置创建了常规bean AcmeProperties,我们也建议@ConfigurationProperties只处理环境,特别是不从上下文中注入其他bean。话虽如此,@EnableConfigurationProperties注释也会自动应用于您的项目,以便从中配置任何注释的现有 bean 。您可以通过确保 已经是bean 来快捷方式,如以下示例所示:@ConfigurationProperties
Environment
MyConfiguration
AcmeProperties
1 |
|
这种配置风格特别适用于SpringApplication外部YAML配置,如以下示例所示:
1 | # application.yml |
要使用@ConfigurationPropertiesbean,可以使用与任何其他bean相同的方式注入它们,如以下示例所示:
1 |
|
使用
@ConfigurationProperties还可以生成元数据文件,IDE可以使用这些文件为您自己的密钥提供自动完成功能。有关详细信息,请参阅 附录B,配置元数据附录。
第三方配置
除了@ConfigurationProperties用于注释类之外,您还可以在公共@Bean方法上使用它。当您想要将属性绑定到控件之外的第三方组件时,这样做会特别有用。
要从Environment属性配置bean ,请添加@ConfigurationProperties到其bean注册,如以下示例所示:
1 | (prefix =“another”) |
使用another前缀定义的任何属性都AnotherComponent以与前面AcmeProperties示例类似的方式映射到该bean 。
松弛结合
Spring Boot使用一些宽松的规则来绑定bean的Environment属性 @ConfigurationProperties,因此不需要在Environment属性名和bean属性名之间进行精确匹配 。这有用的常见示例包括破折号分隔的环境属性(例如,context-path绑定到contextPath)和大写环境属性(例如,PORT绑定到 port)。
例如,请考虑以下@ConfigurationProperties类:
1 | (prefix="acme.my-project.person") |
在前面的示例中,可以使用以下属性名称:
| 属性 | 注意 |
|---|---|
acme.my-project.person.first-name |
建议用于.properties和.yml文件。 |
acme.myProject.person.firstName |
标准的驼峰案例语法。 |
acme.my_project.person.first_name |
下划线符号,这是在用另一种格式.properties和.yml 文件。 |
ACME_MYPROJECT_PERSON_FIRSTNAME |
大写格式,使用系统环境变量时建议使用。 |
prefix注释的值必须是kebab大小写(小写并且用-,例如acme.my-project.person)。
每个属性源放宽绑定规则:
| Property Source | Simple | List |
|---|---|---|
| 属性文件 | 骆驼案,kebab案例或下划线表示法 | 标准列表语法使用[ ]或逗号分隔值 |
| YAML文件 | 骆驼案,kebab案例或下划线表示法 | 标准YAML列表语法或逗号分隔值 |
| 环境变量 | 大写格式,下划线作为分隔符。_不应在属性名称中使用 |
由下划线包围的数字值,例如 MY_ACME_1_OTHER = my.acme[1].other |
| 系统属性 | 骆驼案,kebab案例或下划线表示法 | 标准列表语法使用[ ]或逗号分隔值 |
我们建议,在可能的情况下,属性以小写的kebab格式存储,例如
my.property-name=acme。
绑定到Map属性时,如果key包含除小写字母数字字符以外的任何内容,则-需要使用括号表示法以保留原始值。如果密钥未被包围[],则任何非字母数字或-删除的字符。例如,考虑将以下属性绑定到Map:
1 | acme: |
上面的属性将绑定到Mapwith /key1,/key2并key3作为地图中的键。
合并复杂类型
当列表在多个位置配置时,覆盖通过替换整个列表来工作。
例如,假设一个MyPojo具有对象name和description那些属性 null默认。以下示例公开了以下MyPojo对象 列表AcmeProperties:
1 | ("acme") |
请考虑以下配置:
1 | acme: |
如果dev配置文件未激活,则AcmeProperties.list包含一个MyPojo条目,如先前定义的那样。dev但是,如果启用了配置文件,则list 仍然 只包含一个条目(名称my another name和描述null)。此配置不会MyPojo向列表添加第二个实例,也不会合并项目。
当List在多个配置文件中指定a时,将使用具有最高优先级(并且仅具有该优先级)的配置文件。请考虑以下示例:
1 | acme: |
在前面的示例中,如果dev配置文件处于活动状态,则AcmeProperties.list包含 一个 MyPojo条目(名称my another name和描述null)。对于YAML,逗号分隔列表和YAML列表都可用于完全覆盖列表的内容。
对于Map属性,您可以绑定从多个源中提取的属性值。但是,对于多个源中的相同属性,使用具有最高优先级的属性。以下示例公开了一个Map<String, MyPojo>from AcmeProperties:
1 | ("acme") |
请考虑以下配置:
1 | acme: |
如果dev配置文件未处于活动状态,则AcmeProperties.map包含一个带密钥的条目key1 (名称my name 1和描述my description 1)。dev但是,如果启用了配置文件,则map包含两个带有键的条目key1 (名称dev name 1和描述my description 1)和 key2(名称dev name 2和描述dev description 2)。
前面的合并规则适用于所有属性源的属性,而不仅仅适用于YAML文件。
属性转换
Spring Boot尝试在绑定到@ConfigurationPropertiesbean 时将外部应用程序属性强制转换为正确的类型。如果需要自定义类型转换,则可以提供ConversionServicebean(带有bean命名conversionService)或自定义属性编辑器(通过CustomEditorConfigurerbean)或自定义Converters(带有注释为bean的bean定义@ConfigurationPropertiesBinding)。
由于在应用程序生命周期中很早就请求此bean,因此请确保限制您
ConversionService正在使用的依赖项。通常,您在创建时可能无法完全初始化所需的任何依赖项。ConversionService如果配置密钥强制不需要,您可能需要重命名自定义,并且只依赖于合格的自定义转换器@ConfigurationPropertiesBinding。
转换持续时间
Spring Boot专门支持表达持续时间。如果公开 java.time.Duration属性,则可以使用应用程序属性中的以下格式:
- 常规
long表示(除非@DurationUnit已指定,否则使用毫秒作为默认单位 ) - 标准的ISO-8601格式
java.util.Duration的使用 - 一种更易读的格式,其中值和单位耦合(例如
10s10秒)
请考虑以下示例:
1 | ("app.system") |
要指定30秒的会话超时30,PT30S并且30s都是等效的。的500ms的读超时可以以任何形式如下指定:500,PT0.5S和 500ms。
您也可以使用任何支持的单位。这些是:
ns为纳秒us微秒ms毫秒s几秒钟m几分钟h用了几个小时d持续数天
默认单位是毫秒,可以使用@DurationUnit上面的示例中所示进行覆盖。
如果要从仅
Long用于表示持续时间的先前版本升级,请确保定义单位(使用@DurationUnit),如果它不是交换机旁边的毫秒数Duration。这样做可以提供透明的升级路径,同时支持更丰富的格式。
转换数据大小
Spring Framework有一个DataSize值类型,允许以字节为单位表示大小。如果公开DataSize属性,则可以使用应用程序属性中的以下格式:
- 常规
long表示(使用字节作为默认单位,除非@DataSizeUnit已指定) - 更可读的格式,其中值和单元耦合(例如,
10MB意味着10兆字节)
请考虑以下示例:
1 | ("app.io") |
指定10兆字节的缓冲区大小,10并且10MB是等效的。可以将大小阈值256个字节指定为256或256B。
您也可以使用任何支持的单位。这些是:
B用于字节KB为千字节MB对于兆字节GB为千兆字节TB对于太字节
默认单位是字节,可以使用@DataSizeUnit上面的示例中所示进行覆盖。
如果要从仅
Long用于表示大小的先前版本升级,请确保定义单位(使用@DataSizeUnit),如果它不是交换机旁边的字节DataSize。这样做可以提供透明的升级路径,同时支持更丰富的格式。
@ConfigurationProperties验证
Spring Boot尝试在@ConfigurationProperties使用Spring的@Validated注释注释时验证类。您可以javax.validation 直接在配置类上使用JSR-303 约束注释。为此,请确保符合条件的JSR-303实现位于类路径上,然后将约束注释添加到字段中,如以下示例所示:
1 | (prefix="acme") |
您还可以通过注释
@Bean创建配置属性的方法来触发验证@Validated。
虽然嵌套属性也会在绑定时进行验证,但最好还是将关联字段注释为@Valid。这确保即使没有找到嵌套属性也会触发验证。以下示例基于前面的AcmeProperties示例构建 :
1 | (prefix="acme") |
您还可以Validator通过创建名为的bean定义 来添加自定义Spring configurationPropertiesValidator。@Bean应声明该方法static。配置属性验证器是在应用程序生命周期的早期创建的,并且将该@Bean方法声明为static可以创建bean而无需实例化@Configuration该类。这样做可以避免早期实例化可能导致的任何问题。有一个 属性验证示例,显示如何设置。
该
spring-boot-actuator模块包括一个暴露所有@ConfigurationPropertiesbean 的端点 。将Web浏览器指向/actuator/configprops或使用等效的JMX端点。有关详细信息,请参阅“ 生产就绪功能 ”部分。
@ConfigurationProperties与@Value
@Value的注释是核心容器的功能,和它不提供相同的功能,类型安全配置属性。下表汇总了支持的功能@ConfigurationProperties和@Value:
| 特征 | @ConfigurationProperties |
@Value |
|---|---|---|
| 轻松绑定 | 是 | 没有 |
| 元数据支持 | 是 | 没有 |
SpEL 评测 |
没有 | 是 |
如果为自己的组件定义一组配置键,我们建议您将它们分组到带注释的POJO中@ConfigurationProperties。您还应该知道,因为@Value不支持宽松绑定,所以如果您需要使用环境变量来提供值,则它不是一个好的候选者。
最后,虽然您可以编写SpEL表达式@Value,但不会从应用程序属性文件中处理此类表达式。