使用@Value("${property}")注解注入配置属性有时会很麻烦,尤其是当您使用多个属性或数据本质上是分层的时。Spring Boot提供了一种使用属性的替代方法,该方法使强类型的Bean可以管理和验证应用程序的配置。
另请参阅
@Value和类型安全配置属性的区别。
2.7.1 JavaBean属性绑定
可以绑定一个声明标准JavaBean属性的bean,如以下示例所示:
package com.example;import java.net.InetAddress;import java.util.ArrayList;import java.util.Collections;import java.util.List;import org.springframework.boot.context.properties.ConfigurationProperties;@ConfigurationProperties("acme")public class AcmeProperties {private boolean enabled;private InetAddress remoteAddress;private final Security security = new Security();public boolean isEnabled() { ... }public void setEnabled(boolean enabled) { ... }public InetAddress getRemoteAddress() { ... }public void setRemoteAddress(InetAddress remoteAddress) { ... }public Security getSecurity() { ... }public static class Security {private String username;private String password;private List<String> roles = new ArrayList<>(Collections.singleton("USER"));public String getUsername() { ... }public void setUsername(String username) { ... }public String getPassword() { ... }public void setPassword(String password) { ... }public List<String> getRoles() { ... }public void setRoles(List<String> roles) { ... }}}
前面的POJO(Plain Ordinary Java Object)定义了以下属性:
acme.enabled,其默认值为false。acme.remote-address,其类型可以从String强制转换。acme.security.username,带有嵌套的“ security(安全)”对象,其名称由属性名称确定。 特别是,返回类型根本不使用,可能是SecurityProperties(安全属性)。acme.security.password,与上面的类似。acme.security.roles,带有默认为USER的一个String类型的集合。
Spring Boot中,映射到
@ConfigurationProperties类的属性(这些属性是通过属性文件,YAML文件,环境变量等配置的)是公共的API,但是类本身的访问器(getters/setters)并不意味着可以直接使用。
这种安排依赖于默认的空构造函数,并且getter和setter通常是强制性的,因为绑定是通过标准Java Beans属性描述符进行的,就像在Spring MVC中一样。在以下情况中,可以省略setter:
- Maps(映射),只要它们被始化,就只需要使用getter而不需要使用setter,因为它们可以通过绑定器(被绑定字段)来改变。
 - 既可以通过索引(通常是YAML),也可以使用单个逗号分隔的值(通常是Properties)来访问集合和数组。在后一种情况下,必须使用setter。我们建议始终为此类类型添加setter。如果初始化集合,请确保它是可变的(如上例所示的是不可变的)。
 - 如果嵌套的POJO属性已被初始化(如前面示例中的
 Security字段),则不需要setter方法。如果希望绑定器(被绑定字段)通过使用其默认构造函数动态创建实例,则需要一个setter。有些人使用Lombok项目自动添加setter和getter。确保Lombok不会为这种类型生成任何特定的构造函数,因为容器会自动使用它来实例化该对象。 最后,仅考虑标准Java Bean属性,不支持对静态属性的绑定。
2.7.2 构造函数绑定
上一节中的示例可以以不可变(字段)的方式重写,如下例所示:
package com.example;import java.net.InetAddress;import java.util.List;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.boot.context.properties.ConstructorBinding;import org.springframework.boot.context.properties.bind.DefaultValue;@ConstructorBinding@ConfigurationProperties("acme")public class AcmeProperties {private final boolean enabled;private final InetAddress remoteAddress;private final Security security;public AcmeProperties(boolean enabled, InetAddress remoteAddress, Security security) {this.enabled = enabled;this.remoteAddress = remoteAddress;this.security = security;}public boolean isEnabled() { ... }public InetAddress getRemoteAddress() { ... }public Security getSecurity() { ... }public static class Security {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;}public String getUsername() { ... }public String getPassword() { ... }public List<String> getRoles() { ... }}}
在此设置中,@ConstructorBinding注解用于指示应使用构造函数绑定。这意味着绑定器(被绑定字段)将期望找到带有您希望绑定的参数的构造函数。@ConstructorBinding类的嵌套成员(例如,上例中的Security)也将通过其构造函数进行绑定。
可以使用@DefaultValue指定默认值,并将应用相同的转换服务将String值强制转化为缺失属性的目标类型。默认情况下,如果未将任何属性绑定到Security,则AcmeProperties实例包含的security字段将为null。如果您希望Security即使没有属性绑定也返回一个非null的实例,则可以使用一个空的@DefaultValue注解达到这种效果:
package com.example;import java.net.InetAddress;import java.util.List;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.boot.context.properties.ConstructorBinding;import org.springframework.boot.context.properties.bind.DefaultValue;@ConstructorBinding@ConfigurationProperties("acme")public class AcmeProperties {private final boolean enabled;private final InetAddress remoteAddress;private final Security security;public AcmeProperties(boolean enabled, InetAddress remoteAddress, @DefaultValue Security security) {this.enabled = enabled;this.remoteAddress = remoteAddress;this.security = security;}}
要使用构造函数绑定,必须使用
@EnableConfigurationProperties或配置属性扫描来启用该类。您不能对通过常规Spring机制创建的bean(例如,@Componentbean、通过@Bean方法创建的bean或使用@Import加载的bean )使用构造函数绑定。
如果您的类具有多个构造函数,则可以直接在应绑定的构造函数上使用
@ConstructorBinding。
不建议
java.util.Optional与@ConfigurationProperties一起使用,因为它主要用于返回类型。因此,它不太适合配置属性注入。为了与其他类型的属性保持一致,如果确实声明了一个Optional属性且该属性没有值,则将会绑定成null,而不是一个空的Optional。
2.7.3 启用@ConfigurationProperties注释的类型
Spring Boot提供了绑定@ConfigurationProperties类型并将其注册为Bean的基础架构。您可以逐类启用配置属性,也可以启用与组件扫描类似的方式进行配置属性扫描。
有时,带@ConfigurationProperties注解的类可能不适合扫描,例如,如果您正在开发自己的自动配置,或者想要有条件地启用它们。在这些情况下,请使用@EnableConfigurationProperties注解指定要处理的类型列表。可以在任何@Configuration类上完成此操作,如以下示例所示:
@Configuration(proxyBeanMethods = false)@EnableConfigurationProperties(AcmeProperties.class)public class MyConfiguration {}
要使用配置属性扫描,请将@ConfigurationPropertiesScan注解添加到您的应用程序。通常,它将添加到带有@SpringBootApplication注解的主应用程序类中,但也可以将其添加到任何@Configuration类中。默认情况下,将从声明注释的类所在的包中进行扫描。如果要定义要扫描的特定程序包,可以按照以下示例所示进行操作:
@SpringBootApplication@ConfigurationPropertiesScan({ "com.example.app", "org.acme.another" })public class MyApplication {}
当使用配置属性扫描或通过
@EnableConfigurationProperties注册@ConfigurationPropertiesbean,这个bean有个常规的名称:<prefix>-<fqn>。其中,<prefix>是在@ConfigurationProperties注解中指定的环境键的前缀,<fqn>是bean的完全限定名。如果@ConfigurationProperties注解不提供任何前缀,则仅使用bean的完全限定名称。 上例中的bean名称为acme-com.example.AcmeProperties。
我们建议@ConfigurationProperties仅用于处理环境,尤其不要从上下文中注入其他bean。对于极端情况,可以使用setter注入,或者使用框架提供的任何*Aware接口(例如,如果需要访问Environment,则使用EnvironmentAware)。如果仍然要使用构造函数注入其他bean,则必须使用@Component注解此配置属性bean,并使用基于JavaBean的属性绑定。
2.7.4 使用@ConfigurationProperties注解的类型
这种配置样式特别适用于SpringApplication的外部YAML配置,如下所示:
acme:remote-address: 192.168.1.1security:username: adminroles:- USER- ADMIN
要使用@ConfigurationPropertiesbean,可以像使用其他任何bean一样注入它们,如下所示:
@Servicepublic class MyService {private final AcmeProperties properties;@Autowiredpublic MyService(AcmeProperties properties) {this.properties = properties;}//...@PostConstructpublic void openConnection() {Server server = new Server(this.properties.getRemoteAddress());// ...}}
使用
@ConfigurationProperties还可以为您生成元数据文件,IDE可以使用这些元数据文件为自己的键提供自动完成功能。有关详细信息,请参见附录。
2.7.5 第三方配置
@ConfigurationProperties除了可以用于注解类外,还可以在public的@Bean方法上使用。当要将属性绑定到控制范围之外的第三方组件时,这样做特别有用。
要从Environment属性中配置bean ,添加@ConfigurationProperties到它的bean注册中,如下示例所示:
@ConfigurationProperties(prefix = "another")@Beanpublic AnotherComponent anotherComponent() {...}
用another前缀定义的所有JavaBean属性,都被以类似于前面AcmeProperties示例的方式,映射到AnotherComponentbean 。
2.7.6 宽松绑定
Spring Boot使用一些宽松的规则将Environment属性绑定到@ConfigurationPropertiesBean,因此Environment属性名称和Bean属性名称之间不需要完全匹配。有用的常见示例包括:破折号分隔的环境属性(例如,context-path绑定到contextPath),大写的环境属性(例如,PORT绑定到port)。
例如,考虑如下的@ConfigurationProperties类:
@ConfigurationProperties(prefix="acme.my-project.person")public class OwnerProperties {private String firstName;public String getFirstName() {return this.firstName;}public void setFirstName(String firstName) {this.firstName = firstName;}}
前面的代码中,可以使用以下属性名称:
表3.宽松的绑定
| 属性 | 注意 | 
|---|---|
acme.my-project.person.first-name | 
短横线命名(Kebab case),在.properties和.yml文件中,建议使用。 | 
acme.myProject.person.firstName | 
标准驼峰式语法。 | 
acme.my_project.person.first_name | 
下划线表示法,在.properties和.yml文件中,建议使用的另一种格式。 | 
ACME_MYPROJECT_PERSON_FIRSTNAME | 
大写格式,在使用系统环境变量时,建议这样使用。 | 
![]()
prefix注解的值必须使用短横线命名(用-分隔的小写字母,例如acme.my-project.person)。
表4.每个属性源的宽松绑定规则
| 属性源 | 单个 | 列表 | 
|---|---|---|
| Properties文件 | 骆驼命名法,短横线命名法,下划线分隔 | 使用[ ]或以逗号分隔值的标准列表语法 | 
| YAML文件 | 骆驼命名法,短横线命名法,下划线分隔 | 标准YAML列表语法或以逗号分隔的值 | 
| 环境变量 | 以下划线作为定界符的大写字母格式(请参阅从环境变量绑定)。 | 包围的数字值的下划线界定格式(请参阅环境变量的绑定) | 
| 系统属性 | 骆驼命名法,短横线命名法,下划线分隔 | 使用[ ]或以逗号分隔值的标准列表语法 | 
我们建议,如果可能的话,属性以小写的以短横线间隔的格式存储,例如
my.property-name=acme。
绑定映射
绑定到Map属性时,如果key包含除小写字母数字字符或-之外的任何内容,则需要使用方括号表示法,以便保留原始值。如果键没有被[]环绕,所有的除非字母数字或-以外的字符会被删除。例如,考虑将以下属性绑定到Map:
Properties:
acme.map.[/key1]=value1acme.map.[/key2]=value2acme.map./key3=value3
Yaml:
acme:map:"[/key1]": value1"[/key2]": value2"/key3": value3
上面的属性绑定到Map中/key1,/key2并key3的键。
对于YAML文件,方括号需要用引号引起来,以键能被正确解析。
环境变量的绑定
大多数操作系统对可用于环境变量的名称有严格的规则。例如,Linux shell变量只能包含字母(a到z、A到Z)、数字(0到9)或下划线字符(_)。按照约定,Unix shell变量也将以大写字母命名。
Spring Boot的宽松绑定规则尽可能设计成与这些命名限制相兼容。
要将规范形式的属性名称转换为环境变量名称,可以遵循以下规则:
- 用下划线(
_)代替点(.)。 - 删除所有破折号(
-)。 - 转换成大写。
 
例如,配置属性spring.main.log-startup-info是名为SPRING_MAIN_LOGSTARTUPINFO的环境变量。
绑定到对象列表时也可以使用环境变量。要绑定到List,元素编号应在变量名称中用下划线包起来。
例如,配置属性my.acme[0].other将使用名为MY_ACME_0_OTHER的环境变量。
2.7.7 合并复杂类型
如果在多个位置配置了列表,则通过替换整个列表来进行覆盖。
例如,假定MyPojo具有name和description属性,默认情况下属性值为null。以下示例暴露了来自AcmeProperties对象的MyPojo列表:
@ConfigurationProperties("acme")public class AcmeProperties {private final List<MyPojo> list = new ArrayList<>();public List<MyPojo> getList() {return this.list;}}
考虑以下配置:
Properties:
acme.list[0].name=my nameacme.list[0].description=my description#---spring.config.activate.on-profile=devacme.list[0].name=my another name
Yaml:
acme:list:- name: "my name"description: "my description"---spring:config:activate:on-profile: "dev"acme:list:- name: "my another name"
如果dev配置未激活,则AcmeProperties.list包含一个MyPojo条目,如前面定义的那样。如果启用了dev配置,则list 仍然仅包含一个条目(name为my another name,description为null)。此配置不会在列表中添加第二个MyPojo实例,并且不会合并项目。
在多个配置中指定了List时,将使用优先级最高的那个(并且只是该优先级的那个)。考虑以下示例:
Properties:
acme.list[0].name=my nameacme.list[0].description=my descriptionacme.list[1].name=another nameacme.list[1].description=another description#---spring.config.activate.on-profile=devacme.list[0].name=my another name
Yaml:
acme:list:- name: "my name"description: "my description"- name: "another name"description: "another description"---spring:config:activate:on-profile: "dev"acme:list:- name: "my another name"
在前面的示例中,如果dev配置处于活动状态,则AcmeProperties.list包含一个 MyPojo条目(name为my another name,description为null)。对于YAML而言,可以使用逗号分隔的列表和YAML列表都可以完全覆盖列表的内容。
对于Map属性,可以绑定从多个来源的属性值。但是,对于多个源中的同一属性,将使用优先级最高的属性。以下示例暴露了AcmeProperties中的Map<String, MyPojo>:
@ConfigurationProperties("acme")public class AcmeProperties {private final Map<String, MyPojo> map = new HashMap<>();public Map<String, MyPojo> getMap() {return this.map;}}
考虑以下配置:
Properties:
acme.map.key1.name=my name 1acme.map.key1.description=my description 1#---spring.config.activate.on-profile=devacme.map.key1.name=dev name 1acme.map.key2.name=dev name 2acme.map.key2.description=dev description 2
Yaml:
acme:map:key1:name: "my name 1"description: "my description 1"---spring:config:activate:on-profile: "dev"acme:map:key1:name: "dev name 1"key2:name: "dev name 2"description: "dev description 2"
如果dev配置未处于活动状态,则AcmeProperties.map包含一个带有键为key1的条目(name为my name 1,description为my description 1)。如果启用了dev配置,则map包含两个带有键为key1(name为dev name 1,description为my description 1)和key2(name为dev name 2,description为dev description 2)的项。
前述合并规则适用于所有属性源中的属性,而不仅适用于文件的。
2.7.8 属性转换
当Spring Boot将外部应用程序属性绑定到@ConfigurationProperties bean时,它试图强制将外部应用程序属性转换成正确的类型。如果需要自定义类型转换,则可以提供一个ConversionService bean(具有一个名为conversionService的Bean)或一个自定义的属性编辑器(通过CustomEditorConfigurerBean)、自定义Converters(具有注解为@ConfigurationPropertiesBinding的Bean)。
由于在应用程序生命周期中的非常早期就请求了此bean,因此请确保限制
ConversionService正在使用的依赖项。通常,您需要的任何依赖项可能在创建时未完全初始化。如果配置的key不需要强制且仅依赖具有@ConfigurationPropertiesBinding限定符的自定义转换器,你可能需要重命名自定义ConversionService。
持续时间的转换
Spring Boot为表达持续时间提供了专门的支持。如果暴露一个java.time.Duration类型的属性,在应用程序属性中可以使用以下格式:
- 用常规
long类型数值的表现形式(使用毫秒作为默认单位,除非指定了@DurationUnit注解) java.time.Duration使用的标准ISO-8601格式- 将数值和单位耦合的更易读格式(例如,
10s表示10秒) 
考虑以下示例:
@ConfigurationProperties("app.system")public class AppSystemProperties {@DurationUnit(ChronoUnit.SECONDS)private Duration sessionTimeout = Duration.ofSeconds(30);private Duration readTimeout = Duration.ofMillis(1000);public Duration getSessionTimeout() {return this.sessionTimeout;}public void setSessionTimeout(Duration sessionTimeout) {this.sessionTimeout = sessionTimeout;}public Duration getReadTimeout() {return this.readTimeout;}public void setReadTimeout(Duration readTimeout) {this.readTimeout = readTimeout;}}
要指定一个会话的超时为30秒,使用30,PT30S和30s都是等效的。一个500毫秒的读取超时,可以的格式包括:500,PT0.5S和500ms。
您还可以使用任何受支持的单位,包括:
ns,十亿分之一秒us,微秒ms,毫秒s,秒m,分钟h,小时d,天
默认单位是毫秒,可以使用@DurationUnit覆盖默认值(使用上面的示例中所示的方法)。
如果您更喜欢使用构造函数绑定,则可以暴露这些相同的属性,如以下示例所示:
@ConfigurationProperties("app.system")@ConstructorBindingpublic class AppSystemProperties {private final Duration sessionTimeout;private final Duration readTimeout;public AppSystemProperties(@DurationUnit(ChronoUnit.SECONDS) @DefaultValue("30s") Duration sessionTimeout,@DefaultValue("1000ms") Duration readTimeout) {this.sessionTimeout = sessionTimeout;this.readTimeout = readTimeout;}public Duration getSessionTimeout() {return this.sessionTimeout;}public Duration getReadTimeout() {return this.readTimeout;}}
如果要升级一个
Long类型的时间属性值,如果定义的时间单位不是毫秒的话,请使用@DurationUnit确保时间的单位。这样做可以提供透明的升级途径,同时支持更丰富的格式。
日期的转换
Spring Boot除了对持续时间的支持外,还可以使用java.time.Period类型。在应用程序属性中,可以使用以下格式:
- 常规
int类型的表示形式(除非使用@PeriodUnit注解指定,否则使用天作为默认单位) java.time.Period使用的标准ISO-8601格式- 将数值和单位耦合的更简洁格式(例如,
1y3d表示1年零3天) 
这种简洁格式支持以下单位:
y,年m,月w,周d,天
![]()
java.time.Period类型从不实际存储星期数,这只是一个表示“ 7天”的快捷方式。
数据大小的转换
Spring框架使用DataSize类型的值表示字节数。应用程序属性中, 如果要暴露一个DataSize类型的属性,提供了以下格式:
- 常规的
long类型的数值的表示形式(除非已经使用@DataSizeUnit指定,否则使用字节作为默认单位) - 将值和单位耦合的更具可读性的格式(例如,
10MB意味着10兆字节) 
考虑以下示例:
@ConfigurationProperties("app.io")public class AppIoProperties {@DataSizeUnit(DataUnit.MEGABYTES)private DataSize bufferSize = DataSize.ofMegabytes(2);private DataSize sizeThreshold = DataSize.ofBytes(512);public DataSize getBufferSize() {return this.bufferSize;}public void setBufferSize(DataSize bufferSize) {this.bufferSize = bufferSize;}public DataSize getSizeThreshold() {return this.sizeThreshold;}public void setSizeThreshold(DataSize sizeThreshold) {this.sizeThreshold = sizeThreshold;}}
指定一个10 兆字节的缓冲区大小,使用10000000和10MB是等效的。指定一个256个字节的阈值,可以使用256或256B。
您也可以使用任何受支持的单位。它们是:
B,字节KB,千字节MB,兆字节GB,千兆字节TB,太字节
默认单位是字节,可以使用@DataSizeUnit覆盖默认值(如上面的示例中所示的方法)。
如果您更喜欢使用构造函数绑定,则使用如下例所示的方法暴露这些相同的属性:
@ConfigurationProperties("app.io")@ConstructorBindingpublic class AppIoProperties {private final DataSize bufferSize;private final DataSize sizeThreshold;public AppIoProperties(@DataSizeUnit(DataUnit.MEGABYTES) @DefaultValue("2MB") DataSize bufferSize,@DefaultValue("512B") DataSize sizeThreshold) {this.bufferSize = bufferSize;this.sizeThreshold = sizeThreshold;}public DataSize getBufferSize() {return this.bufferSize;}public DataSize getSizeThreshold() {return this.sizeThreshold;}}
如果要升级
Long类型的属性,请使用@DataSizeUnit确保定义的单位(如果不是字节的话)。这样做可以提供透明的升级途径,同时支持更丰富的格式。
2.7.9 @ConfigurationProperties验证
当使用Spring的@Validated注解对@ConfigurationProperties类注释时,Spring Boot就会尝试验证这个类。可以直接在配置类上使用JSR-303 javax.validation约束注解。为此,请确保在类路径上有兼容的JSR-303实现,然后将约束注解添加到字段中,如下例所示:
@ConfigurationProperties(prefix="acme")@Validatedpublic class AcmeProperties {@NotNullprivate InetAddress remoteAddress;// ... getters and setters}
您还可以通过使用注解为
@Bean方法(此方法使用@Validated创建配置属性)来触发验证。
为了确保始终为嵌套的属性触发验证,即使未找到任何属性,相关字段也必须使用@Valid注解。如下例(基于前面的AcmeProperties示例)所示:
@ConfigurationProperties(prefix="acme")@Validatedpublic class AcmeProperties {@NotNullprivate InetAddress remoteAddress;@Validprivate final Security security = new Security();// ... getters and setterspublic static class Security {@NotEmptypublic String username;// ... getters and setters}}
您还可以通过创建名为configurationPropertiesValidator的bean定义来添加自定义Spring Validator。该@Bean方法应声明成static类型。在应用程序生命周期中,配置属性验证器创建的是时期是非常早的,并且将@Bean方法声明为static类型可以在没有实例化@Configuration类的情况下使得Bean创建。这样做避免了由早期实例化引起的任何问题。
![]()
spring-boot-actuator模块包括一个暴露所有@ConfigurationPropertiesbean的端点。将Web浏览器指向/actuator/configprops,或使用等效的JMX端点。 有关详细信息,请参见“生产就绪功能”部分。
2.7.10 @ConfigurationProperties与@Value
@Value注解是核心容器的功能,它不提供作为类型安全配置属性的相同的功能。下表总结了@ConfigurationProperties和@Value支持的功能:
| 特征 | @ConfigurationProperties | 
@Value | 
|---|---|---|
| 宽松的绑定 | 是 | 受限(请参阅下面的注释) | 
| 元数据支持 | 是 | 否 | 
SpEL 评价 | 
否 | 是 | 
如果您确实想使用
@Value,我们建议您以规范形式(仅使用小写字母的短横线命名法)引用属性名称。这将使Spring Boot可以使用与@ConfigurationProperties进行放松绑定的相同逻辑。例如,使用@Value("{demo.item-price}"),将从application.properties文件中采集demo.item-price和``demo.itemPrice格式的声明,也同时会从系统变量中查找DEMO_ITEMPRICE的定义。如果你使用的@Value("{demo.itemPrice}"),demo.item-price和``DEMO_ITEMPRICE将不会予以考虑。
如果您为自己的组件定义了一组配置键,我们建议您将它们组合在以@ConfigurationProperties标记的POJO中。这样做将为您提供结构化、类型安全的对象,您可以将其注入到自己的bean中。
最后,尽管您可以在@Value中写入SpEL表达式,但在应用程序属性文件中不会处理此类表达式。
