@ConfigurationProperties使用技巧

目前springboot是大部分java开发人员的首要选择,在平时工作中我们可能会有部分特殊的配置或者是自研的框架、模块需要用到application.yml自定义配置,以往通过@value注解来读取配置文件,现在介绍一种更优雅的方式来读取配置文件,采用@ConfigurationProperties 注解

@ConfigurationProperties注解

我们模拟一个场景,与第三方系统对接,我们常常需要配置对方地址、部分接口信息、加密密钥等参数,我们先来看看最简单的情况如下

@ConfigurationProperties(prefix = "external")
@Getter
@Setter
@ToString
public class ExternalProperties { 
    private String baseUrl;
    
    private String privateKey;
}
复制代码

配置文件中只含有最基本的类型,prefix表示配置的前缀,这样一个最简单的配置类就完成了,但是目前配置类并不会生效,因为需要把配置类交给spring容器进行管理才能完成配置的自动注入,注意:一定需要有getter、setter方法,常见的方式有两种:

  1. @Component注解

    通过@Component注解可以让配置类成为spring bean也就加入了Spring容器,这种方式比较简单,但是不能细粒度的控制,需要注意的是这种方式需要spring扫描到配置类,常用的方式是@ComponentScan@SpringBootApplication注解配置扫描路径,springboot默认是从启动类开始逐级向下扫描,所以启动类在最外层无需做特殊配置,只有在需要做更精细化的扫描时需要特别注意

    @ConfigurationProperties(prefix = "external")
    @Getter
    @Setter
    @ToString
    @Component
    public class ExternalProperties {
    
        private String baseUrl;
    
        private String privateKey;
    }
    复制代码
  2. @EnableConfigurationProperties注解

    Spring Boot 2.2之后,提供了@EnableConfigurationProperties注解,并将 value设置为{ExternalProperties.class}如下:

    @SpringBootApplication
    @EnableConfigurationProperties({ExternalProperties.class})
    public class Application {
    
        public static void main(String[] args) {
            SpringApplication.run(Application.class, args);
        }
    
    }
    复制代码

    目前看来这种方式与@Component无太大差异,但是@EnableConfigurationProperties还可以标记在@Configuration类上,配合Spring Boot的自动配置功能,我们就可以做到根据需要来配置文件,如下:

    @Configuration
    @EnableConfigurationProperties({ExternalProperties.class})
    public class ExternalAutoConfiguration {
        
    }
    复制代码

    spring.factories文件配置如下:

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.example.demo.config.ExternalAutoConfiguration
    复制代码

    这样只有当ExternalAutoConfiguration自动配置时,配置类才会生效,这种方式常常是自定义模块或者框架使用的小技巧,如果不是很了解springboot的自动配置,简单的解释就是spring会将spring.factories配置的类自动加载。

application.yml配置如下:

external:
  base-url: 'http://127.0.0.1:8080'
  private-key: 'custom'
复制代码

启动项目简单打印一下配置类可以得到:

ExternalProperties(baseUrl=http://127.0.0.1:8080, privateKey=custom)
复制代码

至此最简单的配置类就完成了,接下来介绍一些相关的知识,在工作中容易犯错并比较实用的技巧

Spring 宽松绑定规则 (relaxed binding)

可能有点读者已经注意到上面的配置base-url使用的是kebab-case格式,配置类使用的是camelCased格式,但是Spring依然能准确的对baseUrl赋值,这是因为Spring采用宽松绑定规则,以下的配置都可以生效:

external:
  base-url: 'http://127.0.0.1:8080'
  base_url: 'http://127.0.0.1:8080'
  baseUrl: 'http://127.0.0.1:8080'
  baseurl: 'http://127.0.0.1:8080'
  BASE-URL: 'http://127.0.0.1:8080'
  BASEURL: 'http://127.0.0.1:8080'
复制代码

yaml常用语法

#纯量
demo:
	int: 1
	string: 'demo'
	boolea: true
	float: 1.2
	date: 2021-06-16
	
#数组或者list  ['1','2','3']
demo:
	list:
	 - '1'
	 - '2'
	 - '3'
	 
#对象或map,下面配置对应{key1: '1', key2: '2'}
demo:
	map:
	 key1: '1'
	 key2: '2'
#或者
demo:
	map:  {key1: '1', key2: '2'}

#复杂的数组对象 [{key1:'1',key2:'2'},{key1:'3',key2:'4'}]
demo:
	- key1: '1'
	  key2: '2'
  - key1: '3'
  	key2: '4'
 #或者
demo:
	- {key1: '1', key2: '2'}
	- {key1: '3', key2: '4'}
复制代码

非法的配置项

现在我们加入一个boolean类型配置项enabled,并进行如下配置

@ConfigurationProperties(prefix = "external")
@Getter
@Setter
@ToString
public class ExternalProperties {

    private boolean enabled;

    private String baseUrl;

    private String privateKey;
}
复制代码
external:
  enabled: error
  private-key: 'custom'
  base-url: 'http://127.0.0.1:8080'
复制代码

启动项目我们会得到如下错误,意思就是第11行配置项external.enabledvalue为'error'字符串,不能转换为boolean类型,所以在开发中需要注意配置类型,并能快速定位到错误位置分析错误原因

***************************
APPLICATION FAILED TO START
***************************

Description:

Failed to bind properties under 'external.enabled' to boolean:

    Property: external.enabled
    Value: error
    Origin: class path resource [application.yml] - 11:12
    Reason: failed to convert java.lang.String to boolean

Action:

Update your application's configuration
复制代码

如果我们希望Spring不会因为错误的配置而启动失败我们可以用ignoreInvalidFields=true来进行配置,但是通常不建议这样做,会导致有些错误不能及时发现造成生产事故

@ConfigurationProperties(prefix = "external", ignoreInvalidFields = true)
@Getter
@Setter
@ToString
public class ExternalProperties {

    private boolean enabled;

    private String baseUrl;

    private String privateKey;
}
复制代码

内嵌配置

我们可以用Lists,Maps和Classes做内嵌配置,

对于Classes,Spring是建议放在一个配置类里,采用内部类的方式来进行配置,现在我们加入接口配置和每个接口失败时的重试次数,注意:内部类需要是static的,不然配置不生效,如下

@ConfigurationProperties(prefix = "external", ignoreInvalidFields = true)
@Getter
@Setter
@ToString
public class ExternalProperties {

    private boolean enabled;

    private String baseUrl;

    private String privateKey;

    private List<Api> apis;

    @Getter
    @Setter
    @ToString
    @Component
    public static class Api{

        private String url;

        private int retry;
    }
}
复制代码
external:
  enabled: true
  private-key: 'custom'
  base-url: 'http://127.0.0.1:8080/'
  apis:
    - url: '/token'
      retry: 3
    - url: '/users'
      retry: 2
复制代码

启动项目,打印配置类

ExternalProperties(enabled=true, baseUrl=http://127.0.0.1:8080/, privateKey=custom, apis=[ExternalProperties.Api(url=/token, retry=3), ExternalProperties.Api(url=/users, retry=2)])
复制代码

有时候我们希望创建一个单独的类来进行复杂的配置,也就是把Api类设计成单独的类,这种方式的差异在于使用spring-boot-configuration-processor生成自动提示时我们就需要@NestedConfigurationProperty注解标记属性,后面自动配置会讲到

自动提示

引入依赖,打开idea annotation processing

<dependency>
  	<groupId>org.springframework.boot</groupId>
  	<artifactId>spring-boot-configuration-processor</artifactId>
  	<optional>true</optional>
</dependency>
复制代码

image-20210616162142685.png
rebuild项目会自动生成spring-configuration-metadata.json文件,此时在进行配置时就会自动提示了

image-20210616162304884.png

image-20210616162732611.png

前面我们讲到的Api配置类设计成单独的配置类,在rebuild时并不会生成相关的spring-configuration-metadata.json配置,此时我们需要加上@NestedConfigurationProperty注解,但是List和Map除外,如下

@ConfigurationProperties(prefix = "external", ignoreInvalidFields = true)
@Getter
@Setter
@ToString
public class ExternalProperties {

    private boolean enabled;

    private String baseUrl;

    private String privateKey;

    private List<Api> apis;

    @NestedConfigurationProperty
    private Api api;
}
复制代码

至此,关于@ConfigurationProperties的介绍与使用就讲完了,有问题的小伙伴欢迎提问,有错误的地方发送邮件到yp.yang7@foxmail.com,谢谢