Spring Boot @ConfigurationProperties(用法)

用法:https://blog.csdn.net/qq_32868023/article/details/123390627
原理一:https://blog.csdn.net/qq_32868023/article/details/123515652
原理二:https://blog.csdn.net/qq_32868023/article/details/123603241
原理三:https://blog.csdn.net/qq_32868023/article/details/123604439
原理四:https://blog.csdn.net/qq_32868023/article/details/123606236
原理五:https://blog.csdn.net/qq_32868023/article/details/123616110

Spring Boot提供了一些方式,将定义在配置文件里(例如.properties)的配置内容绑定到DataObject的字段上,本节主要介绍@ConfigurationProperties、@EnableConfigurationProperties、@ConfigurationPropertiesScan注解的使用方法。主要有几个问题:

  • .properties里面的kv如何绑定到指定的DataObject上
  • .properties里面的k跟DataObject里面的field如何映射
  • .properties里面的kv都是字符串,如何转换成DataObject里面field的各种类型,能否自定义转换方法
  • .properties绑定到DataObject的值如何验证

1、如何绑定到DataObject上

方式1:@component+@ConfigurationProperties

application.properties如下

demo.email=111
@Component
@ConfigurationProperties(prefix = "demo")
public class ConfigurationBindingDemo {
    
    private String email;

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}

启动spring应用,ConfigurationBindingDemo#email就会有值111
image.png

方式2:@Bean+@ConfigurationProperties

如果同一个类需要注册成多个bean(即多个DataObject实例),可以采用这种方式
application.properties如下

demo1.email=111
demo2.email=222

去掉上面的注解,改用@Bean注册bean

public class ConfigurationBindingDemo {

    private String email;

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}

@Configuration
public class Config {

    @Bean
    @ConfigurationProperties(prefix = "demo1")
    public ConfigurationBindingDemo demo1() {
        return new ConfigurationBindingDemo();
    }

    @Bean
    @ConfigurationProperties(prefix = "demo2")
    public ConfigurationBindingDemo demo2() {
        return new ConfigurationBindingDemo();
    }
}

启动spring应用,会有两个ConfigurationBindingDemo类型的bean
image.png

方式3:使用@ConfigurationPropertiesScan扫描指定目录下的@ConfigurationProperties

这种方式还可以将指定目录下的@ConfigurationProperties全部扫描进来,类似于@ComponentScan的用法

方式4:@EnableConfigurationProperties

将@EnableConfigurationProperties标在一个配置类上,指定多个标有 @ConfigurationProperties的类

@Configuration
@EnableConfigurationProperties(DemoAutoConfiguration.class)
public class Config {
}

方式5:AutoConfiguration+@EnableConfigurationProperties

三方maven依赖希望使用@ConfigurationProperties的话,需要借助AutoConfiguration机制,导入AutoConfiguration配置类,在AutoConfiguration配置类上标注@EnableConfigurationProperties,这样当AutoConfiguration配置类生效时@EnableConfigurationProperties也会生效
application.properties如下

demo.email=111

ConfigurationBindingDemo类

@ConfigurationProperties(prefix = "demo")
public class ConfigurationBindingDemo {

    private String email;

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}

开启自动配置

@EnableConfigurationProperties(ConfigurationBindingDemo.class)
public class DemoAutoConfiguration {
}

META-INF/spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.example.binder.DemoAutoConfiguration

启动spring应用,AutoConfiguration会生效
image.png

2、ignoreInvalidFields&ignoreUnknownFields

ignoreInvalidFields和ignoreUnknownFields是@ConfigurationProperties里的两个属性

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface ConfigurationProperties {

	@AliasFor("prefix")
	String value() default "";

	@AliasFor("value")
	String prefix() default "";

	boolean ignoreInvalidFields() default false;

	boolean ignoreUnknownFields() default true;
}

ignoreInvalidFields默认是false,当绑定时候出现错误是否要忽略,这种错误一般是类型转换错误
application.properties如下

demo.number=1.1

ConfigurationBindingDemo类

@ConfigurationProperties(prefix = "demo")
@Component
public class ConfigurationBindingDemo {

    private Integer number;

    public Integer getNumber() {
        return number;
    }

    public void setNumber(Integer number) {
        this.number = number;
    }
}

1.1无法转换成Integer,启动程序后会报错
image.png
当设置ignoreInvalidFields = true后,能够正常启动,number为默认值null
image.png
ignoreUnknownFields默认是true,会忽略掉.properties文件里未绑定到DataObject里的kv,当设置ignoreUnknownFields = false后,如果.properties文件里存在kv未绑定到DataObject里,会报错
application.properties如下

demo.number=1
demo.example=222

ConfigurationBindingDemo类跟上面一样,程序启动后会报错:
image.png

3、properties中key与DataObject fieldName映射

@ConfigurationProperties将.properties里面配置的kv绑定到DataObject里面的field的流程简单来说就是

  • 递归的遍历DataObject里面的每一个属性(因为DataObject里面的属性也可能是另一个DataObject)
  • 从.properties查找能够绑定到这个属性的kv
  • 将 .properties里配置的值转换成DataObject field属性的值

DataObject里的属性一般是驼峰命名法,其类型可能有几种情况:

  • 值属性,例如Integer、int、String
  • DataObject,需要对DataObject里面的每一个属性进行绑定
  • Map属性
  • Collection属性
  • 数组属性

不同类型映射到field有差异,下面分别看key与fieldName如何映射

值属性

一般来讲,fieldName使用驼峰命名法,而.properties文件为了可读性,使用’-'分隔多个单词。例如要给ConfigurationBindingDemo的phoneNumber属性绑定值的话,要写成phone-number,如下

package org.example.binder;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Map;


@ConfigurationProperties(prefix = "demo")
@Component
@Data
public class ConfigurationBindingDemo {
    private String phoneNumber;
}

demo.phone-number=123

可以看到绑定结果
image.png
而实际上规则没有这么死,.properties映射到field时,.properties有以下规则

  • 字母、数字、’.’、’-'是有效字符,其他无效字符会忽略
  • '.'是分隔符,指明field的层级结构
  • '-'增强可读性,映射到field会忽略
  • 忽略大小写

根据以上规则,假定希望绑定属性ConfigurationBindingDemo类的phoneNumber属性,如下

@ConfigurationProperties(prefix = "demo")
@Component
public class ConfigurationBindingDemo {

    private String phoneNumber1;
    private String phoneNumber2;
    private String phoneNumber3;
    private String phoneNumber4;
    private String phoneNumber5;
    private String phoneNumber6;
    private String phoneNumber7;
    // getters and setters
}

那么.properties里可以有很多写法

demo.phoneNumber1=1
demo.phonenumber2=2
demo.phone-number3=3
demo.phone_number4=3
DEMO.PHONEnumber5=4
demo.--_-phone______numBer----__6___=6
demo.--_-phone&&&nu**mBer----__7___=7

image.png

DataObject

通过字符’.'增加层级结构就行

@ConfigurationProperties(prefix = "demo")
@Component
@Data
public class ConfigurationBindingDemo {
    private Apple apple;

    @Data
    public static class Apple {
        private double weight;
    }
}
demo.apple.weight=1.11

image.png

数组属性

item能直接转换

@ConfigurationProperties(prefix = "demo")
@Component
@Data
public class ConfigurationBindingDemo {
    private String[] names;
}

要将.properties绑定到names数组上,可以有三种写法

a、将所有值用’,'分隔
demo.names=a,b,c,d,e

image.png

b、[index]指定位置
demo.names[0]=1
demo.names[1]=2
demo.names[2]=3
demo.names[3]=4
demo.names[4]=5

image.png

c、.index指定位置
demo.names.0=1
demo.names.1=2
demo.names.2=3
demo.names.3=4
demo.names.4=a

image.png

item不能直接转换

可以通过.index/[index]继续加上.fieldName设置值

@ConfigurationProperties(prefix = "demo")
@Component
@Data
public class ConfigurationBindingDemo {

    private Person[] persons;
    
    @Data
    private static class Person {
        private String name;
        private Double weight;
    }
}
demo.persons[0].name=jack
demo.persons[0].weight=120
demo.persons[1].name=luna
demo.persons[1].weight=110

image.png

Collection属性

@ConfigurationProperties(prefix = "demo")
@Component
@Data
public class ConfigurationBindingDemo {
    private List<String> names;
}

绑定collection .properties的写法跟数组一样,底层代码有部分都是相同的

Map属性

map属性的绑定,key的类型必须是能直接从string转换的,value可以通过.fieldName绑定值

kv都能直接转换

@ConfigurationProperties(prefix = "demo")
@Component
@Data
public class ConfigurationBindingDemo {
    private Map<String, Integer> age;
}

类似上面,但是只有两种写法

a、[index]指定map的kv
demo.age[jack]=13
demo.age[luna\ A_-1]=14

image.png
key写在’[]‘使用’\ '转义了一下空格,map的kv是没有大小写、特殊字符限制的

b、.index指定map的kv
demo.age.jack=13
demo.age.luna\ A_-1=14

image.png
这种写法map的key有限制:忽略掉无效字符(字母、数字、’-'是有效字符)

v不能直接转换

@ConfigurationProperties(prefix = "demo")
@Component
@Data
public class ConfigurationBindingDemo {

    private Map<String, Person> persons;

    @Data
    private static class Person {
        private String name;
        private Double weight;
    }
}
demo.persons[jack].name=jack
demo.persons[jack].weight=120
demo.persons[luna].name=luna
demo.persons[luna].weight=110

image.png

4、properties中value到field值转换

Spring默认支持了String到一系列基础类型(Number、Date、Enum)、数组、集合、Map的转换,如何去看到底支持了哪些呢?
处理值转换的是org.springframework.boot.context.properties.bind.BindConverter这个类,底层包装了用于处理类型转换的两个代理类

  • org.springframework.boot.context.properties.bind.BindConverter.TypeConverterConversionService
  • org.springframework.boot.convert.ApplicationConversionService

其中第一个会处理PropertyEditor逻辑,重点关注第二个。ApplicationConversionService在实例化时候默认配置了一些String到TargetType的转换,看下常用的一些转换

a、基本类型

基本类型和包装类型都是支持的,Bigdecimal也是支持的

@ConfigurationProperties(prefix = "demo")
@Component
@Data
public class ConfigurationBindingDemo {

    private int a;
    private Integer a2;

    private boolean b;
    private Boolean b2;

    private char c;
    private Character c2;

    private BigDecimal d;
}
demo.a=123
demo.a2=1234
demo.b=true
demo.b2=false
demo.c=a
demo.c2=A
demo.d=1.234

image.png

b、Enum类型

处理string到Enum转换的是LenientToEnumConverter,忽略大小写、忽略字母数字以外其他字符,不能按ordinal转换

@ConfigurationProperties(prefix = "demo")
@Component
@Data
public class ConfigurationBindingDemo {

    private List<E> e;

    public enum E {
        NAME,
        SPLIT_NAME,
        lowercase
    }
}
demo.e=NAME, NA-ME, split-name, splitname, LOWERCASE, lower&case

image.png

c、Date

处理String到日期的转换是org.springframework.format.support.FormattingConversionService.AnnotationParserConverter,需要借助@DateTimeFormat指定.properties里的Date格式

@ConfigurationProperties(prefix = "demo")
@Component
@Data
public class ConfigurationBindingDemo {

    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date date;
}
demo.date=2022-02-02 9:00:00

image.png

d、数组、集合、Map

spring在进行数据绑定时,首先会尝试从.properties里尝试获取值,如果获取到值,则通过BindConverter进行类型转换,这一步骤对于数组、集合、Map也是适应的

  • 对于数组、集合,会采用逗号分隔符分割每个元素,对每个元素进行转换
  • 对于Map,默认没有String到Map的转换,会抛异常

如果获取不到值,对数组、集合、Map还会进行进一步处理

  • 数组、集合:在fieldName后面加上[index]、.index继续获取值,对每个元素将那些转换
  • Map:.properties的kv会转换成Map的key和value

e、如何扩展

BinderConverter提供的转换功能以及对数组、集合、Map的特殊处理已经能够适应绝大部分场景,仍然存在一些情况需要扩展

  • Map<Person, Person>这种key不能直接转换的类型则不能绑定
  • 也没有直接从String到Map的转换
  • List<List>类型

解决这些问题就需要我们自定义一些String到这些类型的Converter,而Spring在从beanFactory中获取的时候,spring怎么知道你定义的这个Converter是为了数据绑定用的还是就是单纯的加入到beanFactory另有用处呢?所以需要@Qualifier来指明是给数据绑定用的。扩展的代码极其简单

@Configuration
public class Config {

    @Bean
    @Qualifier(ConfigurationPropertiesBinding.VALUE)
    public Converter<String, YourBean> converter() {
        return new Converter<String, YourBean>() {

            @Override
            public YourBean convert(String source) {
                // cvonert string to your bean
            }
        };
    }
}

看两个具体例子

绑定Map<Person, Dog>属性

@ConfigurationProperties(prefix = "demo")
@Component
@Data
public class ConfigurationBindingDemo {

    private Map<Person, Dog> map;

    @Data
    public static class Person {
        private String name;
        private Double weight;
    }

    @Data
    public static class Dog {
        private String name;
    }
}

定义两个Converter,通过fastjson将字符串转换为DataObject

@Configuration
public class Config {

    @Bean
    @Qualifier(ConfigurationPropertiesBinding.VALUE)
    public Converter<String, ConfigurationBindingDemo.Person> convertToPerson() {
        return new Converter<String, ConfigurationBindingDemo.Person>() {

            @Override
            public ConfigurationBindingDemo.Person convert(String source) {
                return JSON.parseObject(source, ConfigurationBindingDemo.Person.class);
            }
        };
    }

    @Bean
    @Qualifier(ConfigurationPropertiesBinding.VALUE)
    public Converter<String, ConfigurationBindingDemo.Dog> convertToDog() {
        return new Converter<String, ConfigurationBindingDemo.Dog>() {

            @Override
            public ConfigurationBindingDemo.Dog convert(String source) {
                return JSON.parseObject(source, ConfigurationBindingDemo.Dog.class);
            }
        };
    }
}

.properties的key需要转义一下

demo.map[{"name"\:\ "jack",\ "weight"\:\ 111.1}]={"name": "cute"}

image.png

直接字符串转Map

注册一个转换到Map的Converter

@Configuration
public class Config {

    @Bean
    @Qualifier(ConfigurationPropertiesBinding.VALUE)
    public Converter<String, Map<ConfigurationBindingDemo.Person, ConfigurationBindingDemo.Dog>> convertToPerson() {
        return new Converter<String, Map<ConfigurationBindingDemo.Person, ConfigurationBindingDemo.Dog>>() {

            @Override
            public Map<ConfigurationBindingDemo.Person, ConfigurationBindingDemo.Dog> convert(String source) {
                return JSON.parseObject(source, new TypeReference<Map<ConfigurationBindingDemo.Person, ConfigurationBindingDemo.Dog>>() {});
            }
        };
    }
}
demo.map={{"name": "jack", "weight": 111.1}: {"name": "cute"}}

image.png

5、field值验证

对field的验证支持jsr303Validator,也可以自定义Validator

jsr303

jsr303提供了一些校验限制注解,有哪些注解可以查阅代码,并且DataObject类上有@Validated注解才会开启jsr303校验,然后将这些jsr303注解标注在DataObject内属性上即可

@ConfigurationProperties(prefix = "demo")
@Component
@Data
@Validated
public class ConfigurationBindingDemo {

    private Person person;

    @Data
    public static class Person {
        private String name;

        @Min(50)
        @Max(300)
        private Double weight;
    }
}
demo.person.weight=49.9

启动之后会抛出异常
image.png

自定义Validator

jsr303提供验证注解的比较有限,这时候就需要自己去定义验证规则,可以自定义一个name=configurationPropertiesValidator的Validator。Validator接口有两个方法,supports用来判断这个validator是否能用来验证这个类,如果能,再调用validate验证这个类的target值是否合法

public interface Validator {

	boolean supports(Class<?> clazz);

	void validate(Object target, Errors errors);
}

下面自定义一个注解@Odd,用于验证int或long是奇数,并且跟jsr303作用范围一致:最外层有@Validated注解才生效,主要过程如下

  • 定义一个@Odd注解
  • 定义一个Validator,递归遍历DataObject的Field和Field内的Field,对有@Odd注解的field的值拿出来,校验是否为奇数
  • 将这个validator注册到beanFactory,name=configurationPropertiesValidator

@Odd注解

@Target({FIELD})
@Retention(RUNTIME)
public @interface Odd {
}

定义Validator并注册到beanFactory


@Configuration
public class Config {

    @Bean("configurationPropertiesValidator")
    public Validator getValidator() {
        return new OddValidator();
    }

    public static class OddValidator implements Validator {

        @Override
        public boolean supports(Class<?> clazz) {
            return clazz.isAnnotationPresent(Validated.class);
        }

        @Override
        public void validate(Object target, Errors errors) {
            validate("", target, errors);
        }

        public void validate(String path, Object target, Errors errors) {
            Class<?> targetClass = target.getClass();
            for (Field field : targetClass.getDeclaredFields()) {
                path = "".equals(path) ? field.getName() : path + "." + field.getName();
                if (field.getAnnotation(Odd.class) != null) {
                    field.setAccessible(true);
                    Object fieldValue = ReflectionUtils.getField(field, target);
                    if (fieldValue != null && !validateValue(fieldValue)) {
                        errors.rejectValue(path, "", path + " can't be non odd, value: " + fieldValue);
                    }
                }

                Class<?> declaringClass = field.getDeclaringClass();
                if (!declaringClass.isPrimitive() && !declaringClass.isArray() && !declaringClass.getName().startsWith("java")) {
                    field.setAccessible(true);
                    Object fieldValue = ReflectionUtils.getField(field, target);
                    if (fieldValue != null) {
                        validate(path, fieldValue, errors);
                    }
                }
            }
        }

        private boolean validateValue(Object target) {
            Class<?> targetClass = target.getClass();
            if (Arrays.asList(int.class, long.class).contains(targetClass)) {
                long num = (long) target;
                return num % 2 == 1;
            }
            if (Arrays.asList(Integer.class, Long.class).contains(targetClass)) {
                return ((Number) target).longValue() % 2 == 1;
            }
            return true;
        }
    }
}

看一下效果
DataObject

@ConfigurationProperties(prefix = "demo")
@Component
@Data
@Validated
public class ConfigurationBindingDemo {

    private Person person;

    @Odd
    private int num;

    @Data
    public static class Person {
        private String name;

        @Odd
        private Integer weight;
    }
}
demo.person.weight=50
demo.person.name=49.9
demo.num=1

image.png

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
@ConfigurationPropertiesSpring Boot 中的一个注解,用于将配置文件中的属性映射到 Java 对象中。通过 @ConfigurationProperties 注解,我们可以将配置文件中的属性值注入到一个被 @Component 或 @Configuration 注解的类中作为属性。 使用 @ConfigurationProperties 注解时需要指定一个前缀,该前缀与配置文件中的属性名进行匹配。然后,通过添加对应的 setter 方法,可以将属性值注入到被注解的类的实例中。 例如,假设我们有一个名为 "myapp" 的属性前缀,配置文件中有一个属性 "myapp.name",我们可以通过以下方式进行注解和映射: ```java @Component @ConfigurationProperties(prefix = "myapp") public class MyAppProperties { private String name; // Getter and Setter methods } ``` 在上述示例中,当 Spring Boot 启动时,会自动将配置文件中的 "myapp.name" 属性的值注入到 MyAppProperties 类的实例中的 name 属性。 注意,为了使 @ConfigurationProperties 生效,我们还需要在应用程序的入口处添加 @EnableConfigurationProperties 注解,并指定要扫描的类。 ```java @SpringBootApplication @EnableConfigurationProperties(MyAppProperties.class) public class MyApplication { public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); } } ``` 这样,我们就可以在应用程序中使用 @Autowired 注解将 MyAppProperties 类注入到其他类中,并使用其中的属性值。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值