【每天学点Spring】Spring Boot使用@ConfigurationProperties绑定配置

【参考】

【简介】
@ConfigurationProperties注解是从Spring Boot 1.0.0开始就有的,主要的作用是可以将外部的配置(如从.properties文件中)绑定并验证到目标类中。主要是通过调用class的setter方法或调用构造方法(如果有配置@ConstructorBinding的话)将配置绑定到目标类中。

【本文内容】

  • 第一章:介绍了通过类的setter将配置绑定到java bean中。
  • 第二章:介绍了通过类的构造函数将配置绑定到java bean中。
  • 第二章:配置@ConfigurationProperties后,如何被Spring读取到?有三种方式:
    • @Configuration
    • @EnableConfigurationProperties({xx.class})
    • @ConfigurationPropertiesScan("xx.config")
  • 第三章:与@Bean一起使用
  • 第四章:宽松的绑定方式(Relaxed binding)
  • 第五章:更为复杂的绑定
    • 绑定为Map对象
    • 绑定为List对象
  • 第七章:校验相关,与@Validated以及@NotNull, @NotBlank, @Min, @Max等JSR-380规范一起使用。
  • 第八章@ConfigurationProperties@Value的区别
  • 第九章@NestedConfigurationProperty:这个注解是用在Meta-data支持中使用的,主要是用来做配置提示用的,并不是作用在绑定这一步的。

1. 使用@ConfigurationProperties将属性绑定到JavaBean

想要在类中使用以下两个属性,除了使用@Value("${my.service.enabled}")的方式,还可以使用@ConfigurationProperties直接映射到类中。

my.service.enabled=true
my.service.remote-address=0.0.0.1
my.service.security.username=test
my.service.security.password=password

新建MyPropertiesConfig类:

@Data
@Configuration
@ConfigurationProperties("my.service")
public class MyPropertiesConfig {
    private boolean enabled;
    private InetAddress remoteAddress;
    private final Security security = new Security();
    // 省略setter/getter方法

    @Data
    public static class Security {
        private String username;
        private String password;
        private List<String> roles = new ArrayList<>(Collections.singleton("USER"));
        // 省略setter/getter方法
    }
}

通过这样的方式,可以直接用@Autowired来获取这个配置类:

    @Autowired
    private MyPropertiesConfig myPropertiesConfig;

如果没有配置my.service.security.username,那么默认为null,也可以通过定义设置默认值,如上述的roles

【注意事项】

  • 通过@ConfigurationProperties的方式来注入配置类,必须配合setter/getter来使用,如果没有setter,会报错:org.springframework.boot.context.properties.bind.BindException
  • 需要有一个无参的构造方法。如果没有,则会报错:Unsatisfied dependency expressed through constructor parameter 0;
  • 无法为static属性注入配置值。

2 通过构造函数绑定配置

通过构造函数绑定配置,这时候可以没有setter方法。
需要通过@ConstructorBinding来标记使用具体哪个构造方法来绑定。若没有标明该注解,报错:Unsatisfied dependency expressed through constructor parameter 0;

关于默认值的设置

  • 可以通过@DefaultValue("true")来给enabled设默认值,如果application.properties中没有定义my.service.enabled,那么该值默认为true。
  • 如果没有配置my.service.security相关的值,同时没有使用@DefaultValue,那么security默认为null,如果加了@DefaultValue,则会为sucurity新建一个对象,username=null, password=null, roles则有默认值,为USER。
@ConfigurationProperties("my.service")
public class MyPropertiesConstructorConfig {
    private boolean enabled;
    private InetAddress remoteAddress;
    private final Security security;
    // 省略getter方法

    @ConstructorBinding
    public MyPropertiesConstructorConfig(@DefaultValue("true") boolean enabled, InetAddress remoteAddress, @DefaultValue Security security) {
        this.enabled = enabled;
        this.remoteAddress = remoteAddress;
        this.security = security;
    }

    public static class Security {
        private String username;
        private String password;
        private List<String> roles = new ArrayList<>(Collections.singleton("USER"));
        // 省略getter方法

        @ConstructorBinding
        public Security(String username, String password, @DefaultValue("USER") List<String> roles) {
            this.username = username;
            this.password = password;
            this.roles = roles;
        }
    }
}

关于该配置如何生效:

  • 上述的通过setter绑定配置类MyPropertiesConfig,可以直接用@Configuration来表示这是一个配置类,Spring在启动的时候会将想要的组件添加到容器中。如果用@Component,哪怕是@Service都可以。
  • 但是,通过构造函数绑定配置类,并不能以上述的方式。而是需要使用@EnableConfigurationProperties({MyPropertiesConstructorConfig.class})来声明需要enable当前的ConfigurationProperties类,或者用@ConfigurationPropertiesScan("com.xx.config")来告诉Spring需要扫描com.xx.configpackage下的所有类,需要将这些ConfigurationProperties类enable并绑定。
  • @EnableConfigurationProperties@ConfigurationPropertiesScan可以标记在其它的@Component类上,或是SpringBootApplication启动类上。

例如:

@SpringBootApplication
//@ConfigurationPropertiesScan("com.xx.config")
@EnableConfigurationProperties({MyPropertiesConstructorConfig.class})
public class SpringPropertiesApplication {
    // 略
}

通过构造函数绑定的配置类,可以当作一个bean使用:

    @Autowired
    private MyPropertiesConstructorConfig myPropertiesConstructorConfig;

3. 使用@Bean@ConfigurationProperties来引入配置类

配置如下:

config.my-mail.hostname=localhsot
config.my-mail.port=2525

想要注入为Email类,首先声明一个Email类:

@Data
public class Email {
    private String hostName;
    private int port;
}

通过@Bean注解标记为Spring bean,当然这个注解要放在@Configuration类中,另外还需要配置@ConfigurationProperties("config.my-mail")
如果没有定义config.my-mail.hostName,则hostName默认为null。

@Configuration
public class EmailConfig {
    @Bean
    @ConfigurationProperties("config.my-mail")
    public Email email() {
        return new Email();
    }
}

4. 宽松的绑定方式(Relaxed binding)

使用@ConfigurationProperties来绑定配置项时,规则是比较宽松的,比如上述的config.my-mail.hostName,也可以声明为config.my-mail.host-name,具体如下:

配置 说明
config.my-mail.host-name 全小写,遇到驼峰使用-来分隔,在properties或YAML中比较推荐使用,也是官方推荐的使用方式
config.my-mail.hostName 标准的驼峰形式
config.my-mail.host_name 下划线的形式
CONFIG_MYMAIL_HOSTNAME 全大写的形式,声明环境变量的时候比较推荐,注:官网上使用MY_MAINPROJECT_PERSON_FIRSTNAME等效于my.main-project.person.firstName,即使用下划线能代替点号,我本地没有测试成功。具体看官方文档

另外,注解@ConfigurationProperties("config.my-mail")中的配置项,不能以驼峰形式配置,必须是小写和中划线来表示。

5. 更为复杂的绑定

5.1 简单的Map绑定

将my.custom-map绑定为一个map对象,properties定义如下:

my.custom-map.key1=value1
my.custom-map.key2=value2

如果是yaml定义,则为:

my:
  custom-map:
    key1: value1
    key2: value2

对应的@ConfigurationProperties类:

@Data
@Configuration
@ConfigurationProperties("my")
public class MyMapConfig {
    Map<String, String> customMap;
}

输出结果是:customMap={/key1=value1, /key2=value2, key3=value3}
有[]包围,里面就是key值,如果没有[]中括号包围,则会忽略

5.2 复杂的Map绑定

可以映射key为枚举类,value为一个对象。
properties定义如下:

user.usertype-map.admin.id=1
user.usertype-map.admin.name=adminUserName
user.usertype-map.guest.id=2
user.usertype-map.guest.name=guestUserName

如果是yaml,则为:

user:
  usertype-map:
    admin:
      id: 1
      name: adminUserName
    guest:
      id: 2
      name: guestUserName

定义一个用来存放key的枚举类:

public enum UserType {
    ADMIN,GUEST;
}

再定义一个用来存放value的User类:

@Data
public class User {
    private int id;
    private String name;
}

开始绑定:

@Data
@Configuration
@ConfigurationProperties("user")
public class UserMapConfig {
    Map<UserType, User> userTypeMap;
}

输出:userTypeMap={ADMIN=User(id=1, name=adminUserName), GUEST=User(id=2, name=guestUserName)}

默认的Map实现是LinkedHashMap,如果上述userTypeMap没有对应的配置,则为null。可以声明为Map<UserType, User> userTypeMap = new LinkedHashMap<>();,这样在找不到配置的情况下,则为size=0的Map。

注,关于Map的绑定,还可以结合active profile(比如dev/prod)来使用,但个人觉得不是很实用,因为针对不同的环境分多个properties可能更好一些,详细参考官方文档

5.3 绑定为List对象

properties定义:

users.list[0].id=1
users.list[0].name=adminUser
users.list[1].id=2
users.list[1].name=guestUser

对应的yaml格式的定义为:

users:
  list:
    - id: 1
      name: adminUser
    - id: 2
      name: guestUser

绑定类为:

@Data
@Configuration
@ConfigurationProperties("users")
public class UserListConfig {
    private List<User> list = new ArrayList<>();
}

输出:list=[User(id=1, name=adminUser), User(id=2, name=guestUser)]

6. 配置类的转换

Spring提供的转换还是比较方便的,可以将配置转换为java.time.Durationjava.time.Period以及Spring自己的DataSize类,详细请参考官方文档

7. @ConfigurationProperties校验

关于这块,需要先加上validation相关的依赖:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>

默认情况下,@ConfigurationProperties的绑定允许配置不存在,绑定到类的属性则为null,但如果想要实现上述MyPropertiesConfig类中的remoteAddress必须要有配置,则可以使用@Validated配置@NotNull来使用。

@Data
@Configuration
@Validated
@ConfigurationProperties("my.service")
public class MyPropertiesConfig {
    @NotNull
    private InetAddress remoteAddress;
    // 其它略

那么如果没有定义my.service.remote-address,则会报错:Caused by: org.springframework.boot.context.properties.bind.validation.BindValidationException: Binding validation errors on my.service. Field error in object 'my.service' on field 'remoteAddress': rejected value [null];

@NotNull则为Bean Validation2.0的注解,是JSR-380规范,除了该注解,还可以使用@NotBlank@Min@Max等等,主要位于jakarta.validation-api-2.0.2.jar中。

8. @ConfigurationProperties vs. @Value

注解@ConfigurationProperties@Value都可以加载配置,并且不需要一起使用。用@ConfigurationProperties的方式加载配置,Spring会将配置加载为一个类,并且可以当作一个bean使用。而@Value则可以将配置加载成为某个对象的成员变量。如:

@Value("${thread-pool}")
private int threadPool;
配置 @ConfigurationProperties @Value
宽松的绑定方式(Relaxed binding) 支持 部分支持,如@Value("{demo.item-price}"),可以配置demo.item-price或demo.itemPrice,或DEMO_ITEMPRICE,但如果@Value("{demo.itemPrice}"),则不支持demo.item-price和DEMO_ITEMPRICE
支持使用Meta-data(Spring boot的jars中,附带了metadata.json结尾的文件,这个文件可以帮助使用IDE的开发人员快速的完成代码的补全。) 支持 不支持
SpEL表达式 不支持 支持

9. @NestedConfigurationProperty的作用?

官方文档:https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/context/properties/NestedConfigurationProperty.html

里面提到@NestedConfigurationProperty@ConfigurationProperties一起使用,这个注解并不是作用在绑定的这一步,而是在Meta-data支持相关的方面有所作用。即,在IDE开发过程中输入的配置可以被提示,和spring-boot-configuration-processor一起使用。

如:
image.png

具体参考:

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容