8.ConfigurationProperties配置绑定中那些你不知道的事情
在SpringBoot项目中,获取配置属性可以说是一个非常简单的事情,将配置写在aplication.yml
文件之后,我们就可以直接通过@Value
注解来绑定并获取;此外我们也可以将一个结构化的配置,借助@ConfigurationPorperties
绑定到一个POJO,然后供项目使用,那么在使用它的时候,不知是否有想过
@ConfigurationPorperties
修饰的类如何生效- 配置参数与定义的POJO类型不匹配时会怎样
- 配置参数的必要性校验可以怎么支持
- 自定义的配置参数,idea中如何自动补全
- 已废弃的参数定义,怎样友好的提示使用方
- List/Map格式的参数,怎么使用
- 自定义参数解析规则如何支持
如果上面这些都已经了然于心,那么本文的帮助将不会特别大;如果对此有所疑问,接下来将逐一进行解惑
I. 项目环境
本项目借助SpringBoot 2.2.1.RELEASE
+ maven 3.5.3
+ IDEA
进行开发
下面是核心的pom.xml
(源码可以再文末获取)
<!-- 这个依赖是干嘛的,后文会介绍 -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
</dependencies>
II. ConfigurationProperties详解
1. 配置绑定
假定我们现在自定义一个功能模块,里面有一些我们自定义的参数,支持通过yaml配置文件的方式注入
首先我们可以先定义一个配置类 BindConfig
@Data
@ConfigurationProperties(prefix = "hhui.bind")
public class BindConfig {
private String name;
private Integer age;
private List<String> list;
private Map<String, String> map;
}
请注意上面的注解中,prefix = hhui.bind
,简单来讲就是会读取配置文件中,前缀为 hhui.bind
的属性,然后依次赋值到这个类中
BindConfig.name = hhui.bind.name
BindConfig.age = hhui.bind.age
- ...
对应的配置文件如下
hhui:
bind:
name: YiHui
age: 18
list:
- java
- c
- python
map:
wechat: 小灰灰blog
blogs: http://blog.hhui.top
git: http://github.com/liuyueyi
注意事项
- 配置类必须有公共的Setter方法,上文中主要是借助lombok的
@Data
省略了Setter方法的显示声明而已 - 类的属性名与配置文件中的配置名要求匹配
- 大小写不敏感
- 支持下划线转驼峰
- 配置类不要求必须是public
关于上面最后一点,也就表明我们可以在自动AutoConfiguration类中,声明一个内部类来绑定配置信息,如下
@Configuration
@EnableConfigurationProperties({AutoConfiguration.BindConfig.class})
public class AutoConfiguration {
@Data
@ConfigurationProperties(prefix = "hhui.bind")
static class BindConfig {
private String name;
private Integer age;
private List<String> list;
private Map<String, String> map;
}
}
2. 注册生效
我们通过@ConfigurationProperties
修饰配置类之后,是否直接会生效呢?通常来讲,让它生效有下面三种方式
@Component
等注解修饰方式
a. 直接在配置类上添加@Component
, @Configuration
等注解,让Spring容器扫描并加载它
@Data
@Component
@ConfigurationProperties(prefix = "hhui.bind")
public class BindConfig {
}
使用这种方式时,需要注意配置类在自动扫描的包路径下,否则可能不会被扫描(主要是作为第三方jar包提供服务时,可能出现扫描不到的问题)
@Bean
注册
b. 把它当成一个普通的bean,借助bean注册的方式来实现,也是一个可选的方案,一般的实现方式如下
@Configuration
public class AutoConfiguration {
@Bean
public BindConfig bindConfig() {
return new BindConfig();
}
}
@EnableConfigurationProperties
方式
c. 在配置类上,添加这个注解之后,可以实现配置注册,一般常见的使用姿势如
@EnableConfigurationProperties({BindConfig.class})
@Configuration
public class AutoConfiguration {
}
d. 小结
上面三种注册方式,前面两种的思路是将配置类作为bean,第三种实现思路和主动注册bean一致(所以想实现主动注册bean,可以考虑它的实现逻辑)
3. 参数类型不匹配
如果我们在配置中,一个本来希望接收int类型的参数,结果实际上填了一个非整形,会怎样?
比如前面的配置类,我们实际的配置文件将age
填18y,来看一下最终会发生什么事情
hhui:
bind:
Name: YiHui
AGE: 18y
list:
- java
- c
- python
map:
wechat: 小灰灰blog
blogs: http://blog.hhui.top
git: http://github.com/liuyueyi
简单演示,直接在启动类中测试一下会如何
@SpringBootApplication
public class Application {
public Application(BindConfig config) {
System.out.println(config);
}
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
参数异常之后,直接启动失败,如果对参数的要求没有那么严格,即允许失败,我们可以通过设置ignoreInvalidFields = true
@Data
@ConfigurationProperties(prefix = "hhui.bind", ignoreInvalidFields = true)
public class BindConfig {
}
再次执行之后,会发现正常启动,输出如下
BindConfig(name=YiHui, age=null, list=[java, c, python], map={wechat=小灰灰blog, blogs=http://blog.hhui.top, git=http://github.com/liuyueyi})
注意查看上面的age,因为传入的参数非法,所以是null
说明
结合默认值 + ignoreInvalidFields
方式来支持配置的最大可用性:
- 直接在配置类中,设置属性的默认值,表示当这个配置不存在或者设置非法时,使用默认的配置
@Data
@ConfigurationProperties(prefix = "hhui.bind", ignoreInvalidFields = true)
public class BindConfig {
private String name;
private Integer age = 18;
private List<String> list;
private Map<String, String> map;
}
再次执行输出如
BindConfig(name=YiHui, age=18, list=[java, c, python], map={wechat=小灰灰blog, blogs=http://blog.hhui.top, git=http://github.com/liuyueyi}, mainPwd=Pwd(user=一灰灰blog, pwd=yihuihui, code=9))
4. 配置解析规则
常见的配置除了基本类型之外,能嵌套自定义对象么,非基本类型又可以如何解析呢?
a. POJO,List,Map参数类型
我们新定义一个Pwd类
@Data
public class Pwd {
private String user;
private String pwd;
private Integer code;
}
然后扩展一下BindConfig
@Data
@ConfigurationProperties(prefix = "hhui.bind", ignoreInvalidFields = true)
public class BindConfig {
private String name;
private Integer age = 18;
private List<String> list;
private Map<String, String> map;
private Pwd mainPwd;
}
这个时候mainPwd对应的yaml配置文件可以如下设置
hhui:
bind:
Name: YiHui
AGE: 1h
list:
- java
- c
- python
map:
wechat: 小灰灰blog
blogs: http://blog.hhui.top
git: http://github.com/liuyueyi
# 下面这个对应的是 BindConfg.mainPwd; 可以写成 main_pwd也可以写成mainPwd
main_pwd:
user: 一灰灰blog
pwd: yihuihui
code: 9
从上面的介绍也可以看出,对于自定义的POJO类是支持的,使用姿势也没什么区别
此外,对于List和Map的使用也给出了实例
b.自定义配置解析
上面我们自定义的Pwd
类,主要借助setter
方法,将匹配的属性塞入进去;如果我的配置就是一个json串,可以注入到一个POJO类么
hhui:
bind:
Jwt: '{"token": "11111111123", "timestamp": 1610880489123}'
对应的Jwt类如下
@Data
public class Jwt {
private String token;
private Long timestamp;
}
这个时候如想实现上面的配置解析,可以通过实现org.springframework.core.convert.converter.Converter
接口来支持,并通过@ConfigurationPropertiesBinding
注解来表明这是个配置属性转换类,不加这个注解会不生效哦
@Component
@ConfigurationPropertiesBinding
public class JwtConverter implements Converter<String, Jwt> {
@Override
public Jwt convert(String source) {
return JSONObject.parseObject(source, Jwt.class);
}
}
说明
使用自定义的配置解析规则时,注意两点
- 实现接口
Converter
- 使用
@ConfigurationPropertiesBinding
修饰注解
Spring提供了一些默认的配置解析规则,如
- 文件大小
DataSize
- 对应的value可以是 1B, 1KB, 1MB, 1GB...
- 持续时间
Duration
- 对应的value可已是 1ns,1us,1ms,1s,1m,1h,1d
5. 配置不存在场景
一个配置类,对应的类中没有这个属性会怎样?
如针对前面的BindConfig
,没有notExist
这个属性,但是配置文件中,却加上了这个
hhui:
bind:
notExist: true
实测之后,发现没有任何影响,通过查看@ConfigurationProperties
注解的成员,发现可以设置ignoreUnknownFields=false
,从字面上表示出现了未能识别的成员,不会略错误,但是在实际测试中,并没有生效
6. 参数校验
参数校验可以说比较常用的case了,比如前面的配置age
,基本上不会允许这个参数能是负数,如需要对参数进行校验,我们可以借助@Validated
来实现校验
添加pom依赖
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
然后再配置类上添加@Validated
,然后就可以在需要校验的字段上添加对应的限制
@Data
@Validated
@ConfigurationProperties(prefix = "hhui.bind", ignoreInvalidFields = true, ignoreUnknownFields = false)
public class BindConfig {
@Min(13)
@Max(66)
private Integer age = 18;
}
如果我们将age参数设置不满足上面的条件
hhui:
bind:
age: 10
再次测试会发现报如下错误
7. IDEA自动补全提示
平时在Spring开发过程中,在yaml文件中添加配置时,配合idea有非常友好的提示,可以非常友好的补全参数配置
那么我们自定义的参数想实现这个效果应该怎么做呢?
添加文章最开头的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
添加上面的依赖之后,打包mvn clean package
,然后会发现在META-INF下面有个spring-configuration-metadata.json
{
"groups": [
{
"name": "hhui.bind",
"type": "com.git.hui.boot.bind.config.BindConfig",
"sourceType": "com.git.hui.boot.bind.config.BindConfig"
}
],
"properties": [
{
"name": "hhui.bind.age",
"type": "java.lang.Integer",
"sourceType": "com.git.hui.boot.bind.config.BindConfig",
"defaultValue": 18
},
{
"name": "hhui.bind.jwt",
"type": "com.git.hui.boot.bind.config.Jwt",
"sourceType": "com.git.hui.boot.bind.config.BindConfig"
},
{
"name": "hhui.bind.list",
"type": "java.util.List<java.lang.String>",
"sourceType": "com.git.hui.boot.bind.config.BindConfig"
},
{
"name": "hhui.bind.main-pwd",
"type": "com.git.hui.boot.bind.config.Pwd",
"sourceType": "com.git.hui.boot.bind.config.BindConfig"
},
{
"name": "hhui.bind.map",
"type": "java.util.Map<java.lang.String,java.lang.String>",
"sourceType": "com.git.hui.boot.bind.config.BindConfig"
},
{
"name": "hhui.bind.name",
"type": "java.lang.String",
"sourceType": "com.git.hui.boot.bind.config.BindConfig"
}
],
"hints": []
}
然后自动补全就有了
说明
idea推荐添加插件Spring Assistant
,支持非常友好的配置注入
8.小结
本文介绍了@ConfigurationProperties
修饰POJO类,实现配置的绑定,可以通过将这个类声明为一个普通bean的方式进行注册,也可以借助@EnableConfigurationProperties
来注册
在配置参数时,需要注意如果参数类型不一致,会导致项目启动失败;可以通过设置ConfigurationProperties#ignoreInvalidFields = true
,来避免这种场景
通过实现接口Converter
+ @ConfigurationPropertiesBinding
来自定义参数解析转换规则,可以实现各路姿势的参数解析
配置的自动提示支持也比较简单,添加org.springframework.boot:spring-boot-configuration-processor
依赖,打包之后在META-INF中会多一个json文件spring-configuration-metadata.json
II. 其他
0. 项目
项目源码
- 工程:https://github.com/liuyueyi/spring-boot-demo
- 源码: https://github.com/liuyueyi/spring-boot-demo/tree/master/spring-boot/002-properties-bind
系列博文