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


    【基础系列】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修饰配置类之后,是否直接会生效呢?通常来讲,让它生效有下面三种方式

    a. @Component等注解修饰方式

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

    @Data
    @Component
    @ConfigurationProperties(prefix = "hhui.bind")
    public class BindConfig {
    }
    

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

    b. @Bean注册

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

    @Configuration
    public class AutoConfiguration {
        @Bean
        public BindConfig bindConfig() {
            return new BindConfig();
        }
    }
    

    c. @EnableConfigurationProperties方式

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

    @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. 项目

    项目源码

    系列博文

    1. 一灰灰 Blog

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

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

    一灰灰blog

  • 相关阅读:
    设置IIS允许下载.config文件
    SQL Server 触发器
    MVC参数自动装配
    sql之left join、right join、inner join的区别
    C# 之泛型详解
    Frameset使用教程
    网页引用Font Awesome图标
    ubuntu下apache2 安装 配置 卸载 CGI设置 SSL设置
    深入理解JAVA I/O系列二:字节流详解
    深入理解JAVA I/O系列三:字符流详解
  • 原文地址:https://www.cnblogs.com/yihuihui/p/14290089.html
Copyright © 2020-2023  润新知