【参考】
- 官方文档:https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config.typesafe-configuration-properties
- https://www.baeldung.com/configuration-properties-in-spring-boot
【简介】
@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.config
package下的所有类,需要将这些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.Duration
,java.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(" |
支持使用Meta-data(Spring boot的jars中,附带了metadata.json结尾的文件,这个文件可以帮助使用IDE的开发人员快速的完成代码的补全。) | 支持 | 不支持 |
SpEL表达式 | 不支持 | 支持 |
9. @NestedConfigurationProperty
的作用?
里面提到@NestedConfigurationProperty
和@ConfigurationProperties
一起使用,这个注解并不是作用在绑定的这一步,而是在Meta-data支持相关的方面有所作用。即,在IDE开发过程中输入的配置可以被提示,和spring-boot-configuration-processor
一起使用。
具体参考:
- 关于Meta-data支持,官方文档中提到的
@NestedConfigurationProperty
部分:https://docs.spring.io/spring-boot/docs/current/reference/html/configuration-metadata.html#appendix.configuration-metadata.annotation-processor.automatic-metadata-generation.nested-properties - stackoverflow中关于
@NestedConfigurationProperty
和@Configuration
的区别:https://stackoverflow.com/questions/30985570/spring-nestedconfigurationproperty-list-in-configurationproperties - git中关于何时使用
@NestedConfigurationProperty
的例子:https://github.com/spring-projects/spring-boot/issues/33239