类型安全配置属性
使用@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 |
|
当
@ConfigurationProperties
bean以这种方式注册时,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 |
要使用@ConfigurationProperties
bean,可以使用与任何其他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 | "acme.my-project.person") (prefix= |
在前面的示例中,可以使用以下属性名称:
属性 | 注意 |
---|---|
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: |
上面的属性将绑定到Map
with /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尝试在绑定到@ConfigurationProperties
bean 时将外部应用程序属性强制转换为正确的类型。如果需要自定义类型转换,则可以提供ConversionService
bean(带有bean命名conversionService
)或自定义属性编辑器(通过CustomEditorConfigurer
bean)或自定义Converters
(带有注释为bean的bean定义@ConfigurationPropertiesBinding
)。
由于在应用程序生命周期中很早就请求此bean,因此请确保限制您
ConversionService
正在使用的依赖项。通常,您在创建时可能无法完全初始化所需的任何依赖项。ConversionService
如果配置密钥强制不需要,您可能需要重命名自定义,并且只依赖于合格的自定义转换器@ConfigurationPropertiesBinding
。
转换持续时间
Spring Boot专门支持表达持续时间。如果公开 java.time.Duration
属性,则可以使用应用程序属性中的以下格式:
- 常规
long
表示(除非@DurationUnit
已指定,否则使用毫秒作为默认单位 ) - 标准的ISO-8601格式
java.util.Duration
的使用 - 一种更易读的格式,其中值和单位耦合(例如
10s
10秒)
请考虑以下示例:
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 | "acme") (prefix= |
您还可以通过注释
@Bean
创建配置属性的方法来触发验证@Validated
。
虽然嵌套属性也会在绑定时进行验证,但最好还是将关联字段注释为@Valid
。这确保即使没有找到嵌套属性也会触发验证。以下示例基于前面的AcmeProperties
示例构建 :
1 | "acme") (prefix= |
您还可以Validator
通过创建名为的bean定义 来添加自定义Spring configurationPropertiesValidator
。@Bean
应声明该方法static
。配置属性验证器是在应用程序生命周期的早期创建的,并且将该@Bean
方法声明为static可以创建bean而无需实例化@Configuration
该类。这样做可以避免早期实例化可能导致的任何问题。有一个 属性验证示例,显示如何设置。
该
spring-boot-actuator
模块包括一个暴露所有@ConfigurationProperties
bean 的端点 。将Web浏览器指向/actuator/configprops
或使用等效的JMX端点。有关详细信息,请参阅“ 生产就绪功能 ”部分。
@ConfigurationProperties与@Value
@Value
的注释是核心容器的功能,和它不提供相同的功能,类型安全配置属性。下表汇总了支持的功能@ConfigurationProperties
和@Value
:
特征 | @ConfigurationProperties |
@Value |
---|---|---|
轻松绑定 | 是 | 没有 |
元数据支持 | 是 | 没有 |
SpEL 评测 |
没有 | 是 |
如果为自己的组件定义一组配置键,我们建议您将它们分组到带注释的POJO中@ConfigurationProperties
。您还应该知道,因为@Value
不支持宽松绑定,所以如果您需要使用环境变量来提供值,则它不是一个好的候选者。
最后,虽然您可以编写SpEL
表达式@Value
,但不会从应用程序属性文件中处理此类表达式。