@ConfigurationProperties源码分析及某些你可能不知道的特性
1. 可能被忽视的特性
1.1 使用Duration作为时间配置 ★★★★★
ConfigurationClass
@ConfigurationProperties(prefix = "business")
@Configuration
public class BusinessConfig {
private Duration timeout;
// 省略getter和setter
}
application.yaml
business:
timeout: 30s
Test
void test() {
assert businessConfig.timeout.toMillis() == 30 * 1000;
assert durationConfig.timeout.getSeconds() == 30;
}
支持的时间单位
- ns (纳秒)
- us (微秒)
- ms (毫秒)
- s (秒)
- m (分)
- h (时)
- d (天)
1.2 使用DataSize作为大小配置 ★★★
ConfigurationClass
@ConfigurationProperties(prefix = "business")
@Configuration
public class BusinessConfig {
public DataSize maxFileSize;
// 省略getter和setter
}
application.yaml
business:
maxFileSize: 2MB
Test
void test() {
assert businessConfig.maxFileSize.toBytes() == 2 * 1024 * 1024;
assert businessConfig.maxFileSize.toKilobytes() == 2 * 1024;
assert businessConfig.maxFileSize.toMegabytes() == 2;
}
支持的时间单位(不支持小写)
- B
- KB
- MB
- GB
- TB
1.3 基于构造函数注入 ★★★
- 如果没有标注
@ConstructorBinding
注解- 没有无参构造函数:启动报错
- 有无参构造函数 : Spring依旧通过setter进行赋值,不会调用有参构造函数
- 如果标注了
@ConstructorBinding
注解- 通过标注了
@ConstructorBinding
注解的构造函数进行注入
- 通过标注了
private String name;
private Integer age;
private Boolean married;
private List<String> hobby;
public PersonConfig {
}
@ConstructorBinding
public PersonConfig(String name, Integer age, Boolean married, List<String> hobby) {
this.name = name;
this.age = age;
this.married = married;
this.hobby = hobby;
}
1.4 属性校验 ★★★
ConfigurationClass
@ConfigurationProperties(prefix = "business")
@Configuration
@Validated
public class BusinessConfig {
@NotBlank
@Length(min = 3)
private String strProp;
@Range(min = 0, max = 100)
private Integer numProp;
@Size(min = 3)
private List<Object> listProp;
@Size(max = 5)
private Map<String, String> mapProp;
// 省略getter和setter
}
如果任一属性不满足,Spring容器将启动失败,并给予校验失败的提示信息
***************************
APPLICATION FAILED TO START
***************************
Description:
Binding to target org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'business' to com.zzzj.config.BusinessConfig failed:
Property: business.strProp
Value: null
Reason: 不能为空
Action:
Update your application's configuration
1.5 ignoreInvalidFields★★
如果注解类中字段类型和属性类型不匹配,是否忽略这种情况
默认为不忽略,出现了上述情况将会导致Spring容器启动失败
public @interface ConfigurationProperties {
boolean ignoreInvalidFields() default false;
// ...
}
@Component
@ConfigurationProperties(prefix = "business")
public class BusinessConfig {
public Integer numProp;
// 省略getter和setter
}
application.yaml
business:
numProp: stringValue
***************************
APPLICATION FAILED TO START
***************************
Description:
Failed to bind properties under 'business.num-prop' to java.lang.Integer:
Property: business.num-prop
Value: zzzj1233
Origin: "business.numProp" from property source "Inlined Test Properties"
Reason: failed to convert java.lang.String to java.lang.Integer
Action:
Update your application's configuration
设置ignoreInvalidFields == true后
@Component
@ConfigurationProperties(prefix = "business", ignoreInvalidFields = true)
public class BusinessConfig {
public Integer numProp;
// 省略getter和setter
}
Test
void test() {
// Spring容器不会启动失败,只是值为null
assert businessConfig.numProp == null;
1.6 ignoreUnknownFields★
如果属性在配置类中不存在,是否忽略这种情况
默认为忽略,出现了上述情况将会被无视
public @interface ConfigurationProperties {
boolean ignoreUnknownFields() default true;
// ...
}
设置 ignoreUnknownFields == false
ConfigurationClass
@ConfigurationProperties(prefix = "business", ignoreUnknownFields = false)
public class BusinessConfig {
public Integer numProp;
// 省略getter和setter
}
application.yaml
business:
nonExistentProp: any
***************************
APPLICATION FAILED TO START
***************************
Description:
Binding to target [Bindable@757194dc type = com.zzzj.config.BusinessConfig, value = 'provided', annotations = array<Annotation>[@org.springframework.boot.context.properties.ConfigurationProperties(ignoreInvalidFields=false, ignoreUnknownFields=false, prefix=business, value=business)]] failed:
Property: business.nonexistentprop
Value: any
Origin: "business.nonExistentProp" from property source "Inlined Test Properties"
Reason: The elements [business.nonexistentprop] were left unbound.
Action:
Update your application's configuration
2. 前置知识
2.1 Binder
- 用于给
Bindable
对象的属性进行赋值,在SpringBoot的源码中常常可以见到 - 其实处理
@ConfigurationProperties
注解的逻辑并不多,真正赋值的逻辑全都在Binder
对象中 - 可以认为,
@ConfigurationProperties
的作用就是给标注了注解的对象转换成一个Bindable
对象,用Environment#getPropertySources()
创建一个Binder
对象给Bindable
对象赋值
example
public class PersonConfig {
private String name;
private Integer age;
private Boolean married;
private List<String> hobby;
// 省略getter setter toString
}
public void test() throws Exception {
Map<String, Object> map = new HashMap<>(8);
map.put("person.name", "zzzj");
map.put("person.age", 99);
map.put("person.hobby", Arrays.asList("game", "music", "code"));
// 使用Map构建出一个ConfigurationPropertySource
ConfigurationPropertySource propertySource = new MapConfigurationPropertySource(map);
PersonConfig personConfig = new PersonConfig();
Bindable<PersonConfig> bindable = Bindable.ofInstance(personConfig)