Elim的博客

用来记录一些原创性的总结


Elim

ConfigurationProperties介绍

ConfigurationProperties是一个注解,可以标注在一个Class上,这样Spring Boot会从Environment中获取其属性对应的属性值给其进行注入。比如下面的代码定义中,Spring Boot在实例化TestConfigurationProperties这个bean时就会把从Environment中获取属性名为appName的属性值赋给TestConfigurationProperties的appName属性。

@ConfigurationProperties
@Data
public class TestConfigurationProperties {

    private String appName;
    
}

所以当你的application.properties文件中定义了appName=Test时就会把Test赋值给TestConfigurationProperties对象的appName属性。实际上下面的定义和appName=Test是等价的。也就是说在从Environment中获取属性值绑定到ConfigurationProperties标注的对象上时,对大小写是不敏感的,而且其中的-_都会被剔除。

APPname=Test
app-Name=Test
app-name=Test
app_name=Test

@ConfigurationProperties标注的Class通常用于从Environment中绑定属性值,然后供bean容器中的其它bean使用,通常是跟@Configuration标注的Class一起使用,其内部会注入@ConfigurationProperties标注的对象用来定义bean。如果你去查看Spring Boot的AutoConfiguration包,你会发现里面基本都是这样的用法。单独跟@Configuration标注的Class一起使用时,通常还会在@Configuration标注的Class上加上@EnableConfigurationProperties指定允许使用的标注了@ConfigurationProperties的配置类,这样Spring Boot就会把它实例化为一个bean,然后在@Configuration配置类中就可以进行依赖注入并进行使用了。以下代码就是一个简单的示例。

@Configuration
@EnableConfigurationProperties(TestConfigurationProperties.class)
public class TestConfig {

    @Autowired
    private TestConfigurationProperties props;
    
    @Bean
    public Object initBean() {
        //使用注入的ConfigurationProperties标注的对象进行bean构造
        return this.props.getAppName();
    }
    
}

@ConfigurationProperties标注的Class本身就标注为一个bean定义时就不需要在@Configuration标注的Class上使用@EnableConfigurationProperties进行指定了,可以直接进行注入,因为它已经是一个bean了。

指定需要映射的前缀

在application.properties文件中定义的属性通常不是单一名称的属性,而是以a.b.c.d这种形式构成的属性,多个层级之间以点分隔,从而形成不同的分类。这种属性需要绑定到@ConfigurationProperties标注的对象属性上时可以指定一个通用的前缀,然后只对去除前缀之后的内容进行绑定。下面的代码指定了绑定属性时的前缀是test.config,所以TestConfigurationProperties对象的username属性将绑定配置文件中的test.config.username属性,password属性将匹配配置文件中的test.config.password属性。

@ConfigurationProperties("test.config")
@Data
public class TestConfigurationProperties {

    private String username;
    
    private String password;
    
}

当在application.properties文件中进行了如下定义时,TestConfigurationProperties对象的username属性绑定的值是u1,password属性绑定的值是p1

test.config.username=u1
test.config.password=p1

@ConfigurationProperties中有一个属性value用来指定前缀,属性prefix也可以用来指定前缀。有一个ignoreInvalidFields用来指定当需要绑定的属性值不合法时是否需要忽略该属性绑定,属性不合法主要是指类型不匹配。比如需要绑定值的属性定义的类型是int,通过自动绑定机制获取到的属性值是abc,它就不能转换为int。ignoreInvalidFields默认是false,即当出现属性不合法时将不忽略,将抛出异常。还有一个ignoreUnknownFields属性,用来指定当需要绑定值的属性没有找到对应的绑定属性时是否将忽略,默认是true

级联绑定

下面的代码中TestConfigurationProperties的inner属性是一个对象,需要对其进行绑定时需要以.进行级联绑定。

@ConfigurationProperties("test.config")
@Data
public class TestConfigurationProperties {

    private Inner inner;
    
    @Data
    public static class Inner {
        private String username;
        private String password;
    }
    
}

在application.properties文件中进行如下定义会为TestConfigurationProperties对象的inner属性绑定一个Inner对象,其username属性的值是u1,password的值是p1。

test.config.inner.username=u1
test.config.inner.password=p1

在application.yml文件中进行如下定义与上面的定义等价。

test.config.inner:
  username: u1
  password: p1

绑定集合属性

下面的代码中使用@ConfigurationProperties标注的Class有一个List类型的属性。

@ConfigurationProperties("test.config")
@Data
public class TestConfigurationProperties {

    private List<String> list;
    
}

需要给List绑定值时,可以通过[index]的形式指定值,下面的代码就定义了List中的三个元素,分别是ABCDEFGHI

test.config.list[0]=ABC
test.config.list[1]=DEF
test.config.list[2]=GHI

也可以使用英文逗号分隔List中的多个值,以下配置跟上面的配置是等价的。

test.config.list=ABC,DEF,GHI

Set、Array类型的属性值绑定也可以使用类似的语法(索引和逗号分隔)。

在YAML配置文件定义集合类型的值绑定时可以定义为如下这样:

test.config.list:
  - ABC
  - DEF
  - GHI

它也可以使用逗号分隔的多个值。

test.config.list: ABC,DEF,GHI

如果需要绑定值的集合元素是一个对象怎么办呢?下面的代码中list属性的元素类型就是一个Inner对象,其中Inner对象又有username和password两个属性。

@ConfigurationProperties("test.config")
@Data
public class TestConfigurationProperties {

    private List<Inner> list;
    
    @Data
    public static class Inner {
        private String username;
        private String password;
    }
    
}

在application.properties文件中进行如下定义可以为list属性绑定两个Inner对象,其中第一个对象的username属性值为u1,password属性值为p1;第二个对象的username属性值为u2,password属性值为p2。

test.config.list[0].username=u1
test.config.list[0].password=p1

test.config.list[1].username=u2
test.config.list[1].password=p2

在application.yml文件中进行如下定义与上面的定义等价,可以达到相同的值绑定效果。

test.config.list:
  -
    username: u1
    password: p1
  -
    username: u2
    password: p2

绑定Map属性

下面的代码中拥有一个Map类型的map属性,Key和Value都是String类型。

@ConfigurationProperties("test.config")
@Data
public class TestConfigurationProperties {

    private Map<String, String> map;
    
}

需要给上面的map属性绑定值时可以使用key=value的形式,下面的配置会给map属性绑定两个元素,分别是key1对应value1,key2对应value2。

test.config.map.key1=value1
test.config.map.key2=value2

在application.yml文件中使用YAML语法定义就更简单了,以下定义等价于上面的定义。

test.config.map:
  key1: value1
  key2: value2

如果需要绑定的Value是一个对象怎么办呢?比如map属性的定义改为如下这样:

@ConfigurationProperties("test.config")
@Data
public class TestConfigurationProperties {

    private Map<String, Inner> map;
    
    @Data
    public static class Inner {
        private String username;
        private String password;
    }
    
}

在application.properties文件中进行如下定义,会绑定两个元素到map,第一个元素的Key是key1,Value是一个Inner对象,其username属性的值是u1,password属性的值是p1;第二个元素的Key是key2,Value的username属性的值是u2,password属性的值是p2。

test.config.map.key1.username=u1
test.config.map.key1.password=p1

test.config.map.key2.username=u2
test.config.map.key2.password=p2

在application.yml文件中定义时,如下定义等价于上面的定义。

test.config.map:
  key1:
    username: u1
    password: p1
  key2:
    username: u2
    password: p2

使用JSR303注解进行有效性校验

可以对@ConfigurationProperties标注的Class的属性进行有效性校验,要使校验生效,需要在Class上添加@org.springframework.validation.annotation.Validated,还需要Classpath下拥有JSR303 Validator的实现,比如Hibernate Validator,这样Spring Boot在进行属性值绑定后会校验其合法性。下面的代码中就指定了name属性不能为null或空字符串,如果绑定后的值为空将抛出异常。

@Validated
@ConfigurationProperties("test.config")
@Data
public class TestConfigurationProperties {

    @NotBlank(message="参数test.config.name不能为空")
    private String name;
    
}

如果需要进行属性值绑定的属性是一个对象,需要对该对象中的某个属性进行合法性校验,比如下面代码中需要对Inner对象中的username属性进行非空校验,则需要在inner属性上加上@Valid,同时在username属性上加上@NotBlank

@Validated
@ConfigurationProperties("test.config")
@Data
public class TestConfigurationProperties {

    @NotBlank(message="参数test.config.name不能为空")
    private String name;
    
    @Valid
    private Inner inner;
    
    @Data
    public static class Inner {
        
        @NotBlank(message="参数test.config.inner.username不能为空")
        private String username;
        private String password;
    }
    
}

绑定属性值到第三方jar中包含的Class

如果需要绑定属性值到第三方jar中包含的Class对象,我们是无法直接在Class上加上@ConfigurationProperties注解的,这时候可以在@Configuration标注的Class中定义一个需要绑定值的Class类型的bean,然后在该方法上加上@ConfigurationProperties。比如下面代码中通过initTestConfigurationProperties()定义了一个TestConfigurationProperties类型的bean,在该方法上加上了@ConfigurationProperties,Spring Boot就会为该bean进行属性值绑定。

@Configuration
public class TestConfig {

    @Bean
    @ConfigurationProperties("test.config")
    public TestConfigurationProperties initTestConfigurationProperties() {
        return new TestConfigurationProperties();
    }
    
}

参考文档

https://docs.spring.io/spring-boot/docs/2.0.3.RELEASE/reference/html/boot-features-external-config.html#boot-features-external-config-typesafe-configuration-properties

(注:本文是基于Spring Boot 2.0.3所写)