24.7 Type-safe Configuration Properties

使用@Value("${property}")注解注入配置属性有时会很麻烦,特别是如果你使用多个属性或数据本质上是分层的.Spring Boot为使用属性提供了替换方法,让属性能让强类型的实例配置管理及检验应用程序配置,如以下示例所示:

  1. package com.example;
  2. import java.net.InetAddress;
  3. import java.util.ArrayList;
  4. import java.util.Collections;
  5. import java.util.List;
  6. import org.springframework.boot.context.properties.ConfigurationProperties;
  7. @ConfigurationProperties("acme")
  8. public class AcmeProperties {
  9. private boolean enabled;
  10. private InetAddress remoteAddress;
  11. private final Security security = new Security();
  12. public boolean isEnabled() { ... }
  13. public void setEnabled(boolean enabled) { ... }
  14. public InetAddress getRemoteAddress() { ... }
  15. public void setRemoteAddress(InetAddress remoteAddress) { ... }
  16. public Security getSecurity() { ... }
  17. public static class Security {
  18. private String username;
  19. private String password;
  20. private List<String> roles = new ArrayList<>(Collections.singleton("USER"));
  21. public String getUsername() { ... }
  22. public void setUsername(String username) { ... }
  23. public String getPassword() { ... }
  24. public void setPassword(String password) { ... }
  25. public List<String> getRoles() { ... }
  26. public void setRoles(List<String> roles) { ... }
  27. }
  28. }

前面的POJO定义了以下属性:

  • acme.enabled,默认值为false.
  • acme.remote-address,可从String类型构造.
  • acme.security.username,内部类security属性,值由属性名指定.在这里根本没使用返回类型,可能是SecurityProperties.
  • acme.security.password.
  • acme.security.roles,字符串集合.

Note

getter和setter通常是强制性的,因为绑定是通过标准的Java Beans属性描述符,就像在Spring MVC一样.在以下情况下可以省略setter:

  • Maps只要初始化,需要一个getter但不一定需要setter,因为他们可被binder自动注入.
  • 集合和数组可以通过索引访问(通常使用YAML)或通过使用一个逗号分隔值(属性).在后一种情况下,setter是必需的.我们建议总是为这种类型添加setter.如果你初始化一个集合,确保它是可变的(如前面例子).
  • 如果嵌套POJO属性初始化(如前面例子中的Security字段),setter不是必需的.如果你想要通过使用其默认构造函数动态绑定创建实例,你需要一个setter.

有些人使用Lombok项目自动添加getter和setter.确保Lombok对于此类型不产生任何特定的构造函数,因为它是使用容器自动实例化对象.
最后,只有标准的Java Bean属性会被考虑并且不支持静态属性绑定.

Tip

请一并查看@Value和@ConfigurationProperties注解的区别.

你还需要通过@EnableConfigurationProperties注解注册导入属性类,如以下示例所示:

  1. @Configuration
  2. @EnableConfigurationProperties(AcmeProperties.class)
  3. public class MyConfiguration {
  4. }

Note

当使用上述方法注册@ConfigurationProperties实例时,通常实例有一个约定的名称:<prefix>-<fqn>,<prefix>是在@ConfigurationProperties注解中指定的环境键值前缀且<fqn>是实例的完全限定名称. 如果注解不提供任何前缀,那么只有完全限定名的实例被使用.
在上面的示例中实例名为acme-com.example.AcmeProperties.

即使前面的配置为AcmeProperties创建一个常规的Bean实例,我们还是建议@ConfigurationProperties只处理环境属性,特别是别注入其他上下文的Bean实例. 前面已经说过,@EnableConfigurationProperties注解会自动应用到您的项目,以便于任何标注@ConfigurationProperties注解的Bean实例都会根据环境属性配置. 你可以通过如下代码精简MyConfiguration确保AcmeProperties已经是一个Bean实例:

  1. @Component
  2. @ConfigurationProperties(prefix="acme")
  3. public class AcmeProperties {
  4.   // ... see the preceding example
  5. }

这种风格的配置在SpringApplication使用外部YAML配置时能很好的工作,如以下示例所示:

  1. # application.yml
  2. acme:
  3.  remote-address: 192.168.1.1
  4.  security:
  5.   username: admin
  6.   roles:
  7.    - USER
  8.    - ADMIN
  9. # additional configuration as required

为配合使用@ConfigurationProperties Bean实例,您可以与处理其他的Bean方式注入配置Bean实例,如以下示例所示:

  1. @Service
  2. public class MyService {
  3. private final AcmeProperties properties;
  4. @Autowired
  5.   public MyService(AcmeProperties properties) {
  6. this.properties = properties;
  7.   }
  8.   @PostConstruct
  9.   public void openConnection() {
  10. Server server = new Server(this.properties.getRemoteAddress());
  11. // ...
  12.   }
  13. }

Tip

使用@ConfigurationProperties还允许您生成元数据文件,可以用来在IDE中为你自己的键提供自动完成提供自动完成.参见Appendix B Configuration Metadata获取详细信息. .

Thir-party Configuration

除了使用@ConfigurationProperties注解标注类外,您还可以在公共的@Bean方法使用.这样做将特别有用,当你想绑定属性至你无法控制的第三方组件.

从环境属性中配置一个Bean实例,只需在他的Bean注册方法上添加@ConfigurationProperties注解,如以下示例所示:

  1. @ConfigurationProperties(prefix = "another")
  2. @Bean
  3. public AnotherComponent anotherComponent() {
  4. ...
  5. }

任何以前缀another开头的属性都将被映射到AnotherComponent Bean实例,就像前面AcmeProperties例子的方式.

Relaxed Binding

Spring Boot使用一些宽松的规则绑定环境中的属性至@ConfigurationProperties Bean实例,因此在绑定时不需要精确匹配环境属性名和Bean实例的属性名. 这里描述了几个非常有用的常见示例包.分割的环境属性(例如,context-path绑定到contextPath)和大小写配置环境属性(例如,PORT绑定到port)

例如,考虑下面的@ConfigurationProperties类:

  1. @ConfigurationProperties(prefix="acme.my-project.person")
  2. public class OwnerProperties {
  3. private String firstName;
  4. public String getFirstName() {
  5. return this.firstName;
  6. }
  7. public void setFirstName(String firstName) {
  8. this.firstName = firstName;
  9. }
  10. }

在前面的示例中,可以使用以下属性名:

Table 24.1 relaxed binding

Property Note
acme.my-project.person.first-name 短横线隔开,强烈要求配置在.properties和.yml文件中
acme.myProject.person.firstName 标准驼峰写法.
acme.my_project.person.first_name .properties文件中的另一种写法
ACME_MYPROJECT_PERSON_FIRSTNAME 建议在系统环境变量中使用.

Note

注解的prefix前缀值必须短横线隔开(需小写和使用-隔开,如acme.my-project.person)

Table 24.2 relaxed binding rules per property source  

| Property Source | Simple | List | | —————————— | ——————————————————— | 标准列表语法使用[]或者逗号分割值 | | Properties Files | 驼峰式大小写,短横线隔开,下划线标注 | 标准YAML列表语法使用[]或者逗号分割值 | | YAML Files | 驼峰式大小写,短横线隔开,下划线标注 | 数字用下划线包裹 例如MY_ACME_1_OTHER = my.acem[1].other | | Environment Variables | 大写格式使用下划线分割,不使用做为属性名 | 标准列表语法使用[]或者逗号分割值 | | System properties | 驼峰式大小写,短横线隔开,下划线标注

Tip

我们建议,在可能的情况下,属性值存储在小写横短线隔开格式,如my.property-name=acme.

Merging Complex Types

当在多个地方配置列表时,通过替换整个列表来覆写.

例如,假设MyPojo对象存在默认为nullnamedescription属性.下面的例子在AcmeProperties类中配置MyPojo对象列表:

  1. @ConfigurationProperties("acme")
  2. public class AcmeProperties {
  3. private final List<MyPojo> list = new ArrayList<>();
  4. public List<MyPojo> getList() {
  5. return this.list;
  6. }
  7. }

考虑如下配置:

  1. acme:
  2. list:
  3. - name: my name
  4. description: my description
  5. ---
  6. spring:
  7. profiles: dev
  8. acme:
  9. list:
  10. - name: my another name

如果dev特定配置未激活,则AcmeProperties.list包含一个MyPojo对象,如之前定义.如果dev特定配置激活,然而,list列表仍然只包含一个MyPojo对象(name属性值为my another namedescription属性为null). 这个配置未添加第二个MyPojo实例至列表,即它并没有合并项目.

当list配置在多个profile文件时,优先级最高的(只有一个)将被使用.考虑下面的例子:

  1. acme:
  2. list:
  3. - name: my name
  4. description: my description
  5. - name: another name
  6. description: another description
  7. ---
  8. spring:
  9. profiles: dev
  10. acme:
  11. list:
  12. - name: my another name

在前面的示例中,如果dev特定配置文件是激活的,则AcmeProperties.list包含一个MyPojo对象(name属性值为my another namedescription属性为null). YAML的逗号分隔列表和YAML列表可以用来完全覆盖列表内容.

对于Map属性,您可以绑定来自多个源的属性值.然而,对于多个源中的同一属性,使用优先级最高的.下面的例子在AcmeProperties类中配置Map<String,MyPojo>:

  1. @ConfigurationProperties("acme")
  2. public class AcmeProperties {
  3. private final Map<String, MyPojo> map = new HashMap<>();
  4. public Map<String, MyPojo> getMap() {
  5. return this.map;
  6. }
  7. }

考虑以下配置:

  1. acme:
  2. map:
  3. key1:
  4. name: my name 1
  5. description: my description 1
  6. ---
  7. spring:
  8. profiles: dev
  9. acme:
  10. map:
  11. key1:
  12. name: dev name 1
  13. key2:
  14. name: dev name 2
  15. description: dev description 2

如果dev特定配置未激活,AcmeProperties.map包含一个键为key1对象(name属性为my name 1且description属性为my description 1). 如果dev特定配置为激活,map属性将包含两个条目,一个键值为key1(name属性为my name 1且description属性为my description 1)和键值为key2(name属性为my name 2且description属性为my description 2).

Note

前面所述的合并规则适用于来自所有属性源的属性,而不仅仅是YAML文件.

Properties Conversion

当绑定到@ConfigurationProperties实例属性时,Spring Boot试图强制转换外部应用程序属性为正确类型.如果你需要自定义类型转换,您可以配置一个ConversionService (Bean名为ConversionService)或自定义属性编辑器(通过CustomEditorConfigurer Bean)或自定义Converters(标注@ConfigurationPropertiesBinding注解的Bean定义).

Note

这个实例在应用程序生命周期的使用,一定要限制ConversionService使用的依赖关系.通常,任何您需要的依赖可能不是完全在创建时初始化. 如果不是必需的强制配置键,你可能需要重命名你的ConversionService且只有在通过限定@ConfigurationPropertiesBinding注解使用自定义转换器时需要.

Converting durations

Spring Boot对持续时间表示提供了很好的支持.如果您正在使用java.time.Duration属性,以下时间格式在应用程序属性文件中可用:

  • 特定的long长整型表示(使用毫秒作为默认单位,除非指定了@DurationUnit).
  • java.util.Duration所使用的标准的ISO-8601格式.
  • 可读格式,值和单位表示(例如:10s意味着10秒).

考虑下面例子:

  1. @ConfigurationProperties("app.system")
  2. public class AppSystemProperties {
  3. @DurationUnit(ChronoUnit.SECONDS)
  4. private Duration sessionTimeout = Duration.ofSeconds(30);
  5. private Duration readTimeout = Duration.ofMillis(1000);
  6. public Duration getSessionTimeout() {
  7. return this.sessionTimeout;
  8. }
  9. public void setSessionTimeout(Duration sessionTimeout) {
  10. this.sessionTimeout = sessionTimeout;
  11. }
  12. public Duration getReadTimeout() {
  13. return this.readTimeout;
  14. }
  15. public void setReadTimeout(Duration readTimeout) {
  16. this.readTimeout = readTimeout;
  17. }
  18. }

要指定会话超时30秒,30PT30S30s配置都是等效的.读超时500ms可以使用以下格式:500,PT0.5S500ms.

您还可以以下支持的单位:

  • ns表示为纳秒.
  • ms表示为毫秒.
  • s表示为秒.
  • m表示为分.
  • h表示为小时.
  • d表示为日.

程序默认的单位为毫秒,可通过@DurationUnit注解覆盖默认值,具体使用说明请参照上面的示例.

Tip

如果你的Spring Boot是由低版本升级而来,程序简单使用Long来表述时间.当切换到Duration时可能默认单位不是毫秒,你需要确保定义单元(使用@DurationUnit注解). 这样做可以提供透明的升级路径便于同时支持更丰富的格式.

@ConfigurationProperties Validation

当配置类标注Spring’s的@Valicated注解时,Spring Boot会试图验证@ConfigurationProperties类.您可以直接使用JSR-303 javax.validation约束注解在您的配置类. 如果这么做,确保符合JSR-303规范实现在您的类路径中,然后在字段上添加约束注解,如以下示例所示:

  1. @ConfigurationProperties(prefix="acme")
  2. @Validated
  3. public class AcmeProperties {
  4. @NotNull
  5. private InetAddress remoteAddress;
  6. // ... getters and setters
  7. }

Tip

你也可以在创建配置属性的@Bean方法上标准@Validated注解来触发验证.

尽管嵌套的属性也将在绑定时验证,但在关联的字段上标准@Valid注解将是良好的实践.这将在即使没有嵌套属性时也会确保触发验证.以下示例基于前面的AcmeProperties示例:

  1. @ConfigurationProperties(prefix="acme")
  2. @Validated
  3. public class AcmeProperties {
  4. @NotNull
  5. private InetAddress remoteAddress;
  6. @Valid
  7. private final Security security = new Security();
  8. // ... getters and setters
  9. public static class Security {
  10. @NotEmpty
  11. public String username;
  12. // ... getters and setters
  13. }
  14. }

你还可以通过创建名为configurationPropertiesValidator的Bean示例来增加自定义的Spring Validator.@Bean方法必须声明为静态的. 配置属性验证器将在应用程序的生命周期早期被创建,且声明@Bean方法为静态能让bean创建无需实例化@Configuration类.这样做可以避免早期实例化可能导致的任何问题.关于如何配置此相关验证请参考此.

Tip

Spring-boot-actuator模块包含了端点用来显示@ConfigurationProperties实例.打开你的Web浏览器指向/actuator/configprops或者使用一致的JMX端点.请参考Production ready feature获取详细信息.

@ConfigurationProperties vs. @Value

@Value注解是核心容器的特性,它不提供类似安全配置属性的功能.下表总结了@ConfigurationProperties和@Value支持的功能特性:

Feature @ConfigurationProperties @Value
Relaxed binding Yes No
Meta-data support Yes No
SpEL evaluation No Yes

如果你为自己的组件定义一组配置主键,我们建议在你的POJO中将它们分组并标注@ConfigurationProperties注解.你也应该知道,因为@Value不支持复杂绑定,如果你需要提供使用环境变量的值,它不是一个好的候选.

最后,您可以在@Value注解中编写SpEL表达式,应用属性文件将不会处理这些表达式.