【基础系列】ConfigurationProperties配置绑定中那些你不知道的事情

文章目录
  1. I. 项目环境
  2. II. ConfigurationProperties详解
    1. 1. 配置绑定
    2. 2. 注册生效
      1. a. @Component等注解修饰方式
      2. b. @Bean注册
      3. c. @EnableConfigurationProperties方式
      4. d. 小结
    3. 3. 参数类型不匹配
    4. 4. 配置解析规则
      1. a. POJO,List,Map参数类型
      2. b.自定义配置解析
    5. 5. 配置不存在场景
    6. 6. 参数校验
    7. 7. IDEA自动补全提示
    8. 8.小结
  3. II. 其他
    1. 0. 项目
    2. 1. 一灰灰Blog

在SpringBoot项目中,获取配置属性可以说是一个非常简单的事情,将配置写在aplication.yml文件之后,我们就可以直接通过@Value注解来绑定并获取;此外我们也可以将一个结构化的配置,借助@ConfigurationPorperties绑定到一个POJO,然后供项目使用,那么在使用它的时候,不知是否有想过

  • @ConfigurationPorperties修饰的类如何生效
  • 配置参数与定义的POJO类型不匹配时会怎样
  • 配置参数的必要性校验可以怎么支持
  • 自定义的配置参数,idea中如何自动补全
  • 已废弃的参数定义,怎样友好的提示使用方
  • List/Map格式的参数,怎么使用
  • 自定义参数解析规则如何支持

如果上面这些都已经了然于心,那么本文的帮助将不会特别大;如果对此有所疑问,接下来将逐一进行解惑

I. 项目环境

本项目借助SpringBoot 2.2.1.RELEASE + maven 3.5.3 + IDEA进行开发

下面是核心的pom.xml(源码可以再文末获取)

1
2
3
4
5
6
7
<!-- 这个依赖是干嘛的,后文会介绍 -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
</dependencies>

II. ConfigurationProperties详解

1. 配置绑定

假定我们现在自定义一个功能模块,里面有一些我们自定义的参数,支持通过yaml配置文件的方式注入

首先我们可以先定义一个配置类 BindConfig

1
2
3
4
5
6
7
8
9
10
11
@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

对应的配置文件如下

1
2
3
4
5
6
7
8
9
10
11
12
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类中,声明一个内部类来绑定配置信息,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@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修饰配置类之后,是否直接会生效呢?通常来讲,让它生效有下面三种方式

a. @Component等注解修饰方式

直接在配置类上添加@Component, @Configuration等注解,让Spring容器扫描并加载它

1
2
3
4
5
@Data
@Component
@ConfigurationProperties(prefix = "hhui.bind")
public class BindConfig {
}

使用这种方式时,需要注意配置类在自动扫描的包路径下,否则可能不会被扫描(主要是作为第三方jar包提供服务时,可能出现扫描不到的问题)

b. @Bean注册

把它当成一个普通的bean,借助bean注册的方式来实现,也是一个可选的方案,一般的实现方式如下

1
2
3
4
5
6
7
@Configuration
public class AutoConfiguration {
@Bean
public BindConfig bindConfig() {
return new BindConfig();
}
}

c. @EnableConfigurationProperties方式

在配置类上,添加这个注解之后,可以实现配置注册,一般常见的使用姿势如

1
2
3
4
@EnableConfigurationProperties({BindConfig.class})
@Configuration
public class AutoConfiguration {
}

d. 小结

上面三种注册方式,前面两种的思路是将配置类作为bean,第三种实现思路和主动注册bean一致(所以想实现主动注册bean,可以考虑它的实现逻辑)

3. 参数类型不匹配

如果我们在配置中,一个本来希望接收int类型的参数,结果实际上填了一个非整形,会怎样?

比如前面的配置类,我们实际的配置文件将age填18y,来看一下最终会发生什么事情

1
2
3
4
5
6
7
8
9
10
11
12
hhui:
bind:
Name: YiHui
AGE: 18y
list:
- java
- c
- python
map:
wechat: 小灰灰blog
blogs: http://blog.hhui.top
git: http://github.com/liuyueyi

简单演示,直接在启动类中测试一下会如何

1
2
3
4
5
6
7
8
9
10
11
12
@SpringBootApplication
public class Application {

public Application(BindConfig config) {
System.out.println(config);
}

public static void main(String[] args) {
SpringApplication.run(Application.class);
}

}

参数异常之后,直接启动失败,如果对参数的要求没有那么严格,即允许失败,我们可以通过设置ignoreInvalidFields = true

1
2
3
4
@Data
@ConfigurationProperties(prefix = "hhui.bind", ignoreInvalidFields = true)
public class BindConfig {
}

再次执行之后,会发现正常启动,输出如下

1
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 方式来支持配置的最大可用性:

  • 直接在配置类中,设置属性的默认值,表示当这个配置不存在或者设置非法时,使用默认的配置
1
2
3
4
5
6
7
8
9
10
11
12
@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;
}

再次执行输出如

1
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类

1
2
3
4
5
6
7
8
@Data
public class Pwd {
private String user;

private String pwd;

private Integer code;
}

然后扩展一下BindConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@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配置文件可以如下设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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类么

1
2
3
hhui:
bind:
Jwt: '{"token": "11111111123", "timestamp": 1610880489123}'

对应的Jwt类如下

1
2
3
4
5
6
@Data
public class Jwt {
private String token;

private Long timestamp;
}

这个时候如想实现上面的配置解析,可以通过实现org.springframework.core.convert.converter.Converter接口来支持,并通过@ConfigurationPropertiesBinding注解来表明这是个配置属性转换类,不加这个注解会不生效哦

1
2
3
4
5
6
7
8
@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这个属性,但是配置文件中,却加上了这个

1
2
3
hhui:
bind:
notExist: true

实测之后,发现没有任何影响,通过查看@ConfigurationProperties注解的成员,发现可以设置ignoreUnknownFields=false,从字面上表示出现了未能识别的成员,不会略错误,但是在实际测试中,并没有生效

6. 参数校验

参数校验可以说比较常用的case了,比如前面的配置age,基本上不会允许这个参数能是负数,如需要对参数进行校验,我们可以借助@Validated来实现校验

添加pom依赖

1
2
3
4
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>

然后再配置类上添加@Validated,然后就可以在需要校验的字段上添加对应的限制

1
2
3
4
5
6
7
8
@Data
@Validated
@ConfigurationProperties(prefix = "hhui.bind", ignoreInvalidFields = true, ignoreUnknownFields = false)
public class BindConfig {
@Min(13)
@Max(66)
private Integer age = 18;
}

如果我们将age参数设置不满足上面的条件

1
2
3
hhui:
bind:
age: 10

再次测试会发现报如下错误

7. IDEA自动补全提示

平时在Spring开发过程中,在yaml文件中添加配置时,配合idea有非常友好的提示,可以非常友好的补全参数配置

那么我们自定义的参数想实现这个效果应该怎么做呢?

添加文章最开头的依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>

添加上面的依赖之后,打包mvn clean package,然后会发现在META-INF下面有个spring-configuration-metadata.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
{
"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. 项目

项目源码

系列博文

1. 一灰灰Blog

尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激

下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

一灰灰blog


打赏 如果觉得我的文章对您有帮助,请随意打赏。
分享到