Spring Boot 中,若某类只用 @ConfigurationProperties 注解,然后该类:
- 没有在扫描路径下
- 或没用 @Component 等注解
就会导致无法被扫描为 bean,须在配置类用 @EnableConfigurationProperties 注解去指定这个类,才能使 @ConfigurationProperties 生效,并作为一个 bean 添加进 Spring 容器。
@EnableConfigurationProperties 不能单独使用:
- @EnableConfigurationProperties 和 @ConfigurationProperties组合使用
- @EnableConfigurationProperties将@ConfigurationProperties所修饰的类添加到IoC容器
1 简介
Spring Boot外部化配置和轻松访问 Properties 文件中定义的属性。上文介绍了实现这一点的各种方法。
本文看Spring Boot 的 @ConfigurationProperties
。
2 项目设置
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.0</version>
<relativePath/>
</parent>
为验证文件中定义的属性,还需要一个 JSR-380 实现,hibernate-validator 就是其中之一,它由 spring-boot-starter-validation
依赖提供。
把它也添加到 pom.xml
中:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
更多详细信息可参阅 Hibernate Validator 入门。
3 示例属性
官方建议将配置属性隔离到单独 POJO,如:
@Configuration
@ConfigurationProperties(prefix = "mail")
public class ConfigProperties {
private String hostName;
private int port;
private String from;
}
通过 @Configuration
,Spring 会在 Application Context 中创建一个 Spring Bean。
@ConfigurationProperties
适合具有相同前缀的分层属性。
Spring使用标准 Java Bean Setter,因此必须为每个属性声明Setter。
如不在 POJO 用 @Configuration
,则需在 Spring Application
main 类中添加 @EnableConfigurationProperties(ConfigProperties.class)
以将属性绑定到 POJO:
@SpringBootApplication
@EnableConfigurationProperties(ConfigProperties.class)
public class EnableConfigurationDemoApplication {
public static void main(String[] args) {
SpringApplication.run(EnableConfigurationDemoApplication.class, args);
}
}
Spring 会自动绑定属性文件中定义的、前缀为 mail
且名称与 ConfigProperties
类中某个字段相同的任何属性。
Spring 对绑定属性使用宽松规则,以下各种变体都可绑定到属性 hostName
:
mail.hostName
mail.hostname
mail.host_name
mail.host-name
mail.HOST_NAME
可用如下 properties 文件设置所有字段:
# 示例 properties
mail.hostname=host@mail.com
mail.port=9000
mail.from=mailer@mail.com
3.1、Spring Boot 2.2
从 Spring Boot 2.2 开始,Spring 通过 classpath 扫描查找并注册 @ConfigurationProperties
类。对 @ConfigurationProperties
扫描需通过添加 @ConfigurationPropertiesScan
来明确选择。
因此,不必用 @Component
(及 @Configuration
等其他元注解)注解这种类,甚至也不必用 @EnableConfigurationProperties
:
@ConfigurationProperties(prefix = "mail")
@ConfigurationPropertiesScan
public class ConfigProperties {
private String hostName;
private int port;
private String from;
}
@SpringBootApplication
启用的 classpath Scanner 找到了 ConfigProperties
类,尽管我们没有用 @Component
对该类进行注解。
还可用@ConfigurationPropertiesScan扫描自定义位置的配置属性类:
@SpringBootApplication
@ConfigurationPropertiesScan("com.baeldung.configurationproperties")
public class EnableConfigurationDemoApplication {
public static void main(String[] args) {
SpringApplication.run(EnableConfigurationDemoApplication.class, args);
}
}
如上,Spring 将只在 com.baeldung.properties
包中查找配置属性类。
4 嵌套属性
可在 List
、Map
和 Class
中嵌套属性。
创建一个新的 Credentials
类,用于一些嵌套属性:
public class Credentials {
private String authMethod;
private String username;
private String password;
//Get、Set 方法
}
还需要更新 ConfigProperties
类,以便使用 List
、Map
和 Credentials
类:
public class ConfigProperties {
private String hostname;
private int port;
private String from;
private List<String> defaultRecipients;
private Map<String, String> additionalHeaders;
private Credentials credentials;
// Get、Set 省略
}
下面的属性文件将设置所有字段:
#Simple properties
mail.hostname=mailer@mail.com
mail.port=9000
mail.from=mailer@mail.com
#List properties
mail.defaultRecipients[0]=admin@mail.com
mail.defaultRecipients[1]=owner@mail.com
#Map Properties
mail.additionalHeaders.redelivery=true
mail.additionalHeaders.secure=true
#对象 properties
mail.credentials.username=john
mail.credentials.password=password
mail.credentials.authMethod=SHA1
5 @Bean 方法用 @ConfigurationProperties
当要将属性绑定到无法控制的第三方组件时,特别有用。
public class Item {
private String name;
private int size;
}
看咋在 @Bean
方法中使用 @ConfigurationProperties
将外部化属性绑定到 Item
实例:
@Configuration
public class ConfigProperties {
@Bean
@ConfigurationProperties(prefix = "item")
public Item item() {
return new Item();
}
}
任何 item
前缀的属性都将映射到 Spring Context 管理的 Item
实例。
6 属性校验
@ConfigurationProperties
使用 JSR-380 格式对属性进行验证。
例如,把 hostName
属性成为强制性属性:
@NotBlank
private String hostName;
接下来,把 authMethod
属性的长度设为 1 到 4 个字符:
@Length(max = 4, min = 1)
private String authMethod;
然后,port
属性是 1025 到 65536:
@Min(1025)
@Max(65536)
private int port;
最后,from
属性必须与电子邮件地址格式相匹配:
@Pattern(regexp = "^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,6}$")
private String from;
这可以帮助减少代码中大量的 if - else 判断条件,使代码看起来更简洁明了。
如果其中任何一项验证失败,应用将无法启动,并抛出 IllegalStateException
异常。
Hibernate Validation 框架使用标准的 Java Bean Getter 和 Setter,因此必须为每个属性声明 Getter 和 Setter 方法。
7 属性类型转换
@ConfigurationProperties
支持多种类型的转换,可将属性绑定到相应的 Bean。
7.1 Duration
先来看看如何将属性转换为 Duration
对象。
这里有两个 Duration
类型的字段:
@ConfigurationProperties(prefix = "conversion")
public class PropertyConversion {
private Duration timeInDefaultUnit;
private Duration timeInNano;
...
}
配置属性如下:
conversion.timeInDefaultUnit=10
conversion.timeInNano=9ns
如上,字段 timeInDefaultUnit
的值为 10 毫秒,而 timeInNano
的值为 9 纳秒。
支持的单位有 ns、us、ms、s、m、h 和 d,分别表示纳秒、微秒、毫秒、秒、分、小时和天。
默认单位是毫秒,这意味着如果不在数值旁边指定单位,Spring 就会将数值转换为毫秒。
还可以使用 @DurationUnit
来覆盖默认单位:
@DurationUnit(ChronoUnit.DAYS)
private Duration timeInDays;
对应的属性如下:
conversion.timeInDays=2
7.2、DataSize
同样,Spring Boot @ConfigurationProperties
也支持 DataSize
类型转换。
添加三个 DataSize
类型的字段:
private DataSize sizeInDefaultUnit;
private DataSize sizeInGB;
@DataSizeUnit(DataUnit.TERABYTES)
private DataSize sizeInTB;
相应的属性如下:
conversion.sizeInDefaultUnit=300
conversion.sizeInGB=2GB
conversion.sizeInTB=4
如上,sizeInDefaultUnit
的值是 300 字节,因为默认单位是字节。
支持的单位有 B、KB、MB、GB 和 TB。还可以使用 @DataSizeUnit
覆盖默认单位。
7.3、自定义 Converter
还可以添加自己的自定义 Converter
,以支持将属性转换为特定的类类型。
添加一个简单的 Employee
类:
public class Employee {
private String name;
private double salary;
}
然后,创建一个自定义 Converter
来转换该属性:
conversion.employee=john,2000
把它转换为 Employee
类型:
private Employee employee;
需要实现 Converter
接口,然后使用 @ConfigurationPropertiesBinding
注解注册自定义 Converter
:
@Component
@ConfigurationPropertiesBinding
public class EmployeeConverter implements Converter<String, Employee> {
@Override
public Employee convert(String from) {
String[] data = from.split(",");
return new Employee(data[0], Double.parseDouble(data[1]));
}
}
8 不可变的 @ConfigurationProperties 绑定
从 Spring Boot 2.2 开始,可以使用 @ConstructorBinding
注解来绑定配置属性,而不是老式的 Setter 注入。
这基本上意味着 @ConfigurationProperties
注解的类现在可以是不可变的了。
在 Spring Boot 3 中,如果只有一个带参构造函数,那么构造函数绑定就是隐式的,不需要使用注解。但如果有多个构造函数,必须注解首选的那个:
@ConfigurationProperties(prefix = "mail.credentials")
public class ImmutableCredentials {
private final String authMethod;
private final String username;
private final String password;
@ConstructorBinding
public ImmutableCredentials(String authMethod, String username, String password) {
this.authMethod = authMethod;
this.username = username;
this.password = password;
}
public ImmutableCredentials(String username, String password) {
this.username = username;
this.password = password;
this.authMethod = "Default";
}
public String getAuthMethod() {
return authMethod;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
}
如上,在使用 @ConstructorBinding
时,需要为构造函数提供想要绑定的所有参数。
注意,ImmutableCredentials
的所有字段都是 final
字段。此外,ImmutableCredentials
没有 Setter 方法。
此外,需要强调的是,要使用构造函数绑定,需要通过 @EnableConfigurationProperties
或 @ConfigurationPropertiesScan
明确启用配置类。
9 Java 16 Record
Java 16 引入 Record
类型(JEP 395)。Record
类是不可变数据的透明载体。这使它们成为配置持有者和 DTO 的理想选择。事实上,可以在 Spring Boot 中将 Java Record
定义为配置属性。例如,前面的示例可重写为:
@ConstructorBinding
@ConfigurationProperties(prefix = "mail.credentials")
public record ImmutableCredentials(String authMethod, String username, String password) {
}
显然,它比那些模板式的 Getter 和 Setter 更简洁。
Spring Boot 2.6 开始,对于单构造函数 Record
,可以不用 @ConstructorBinding
注解。但是,如果 Record
有多个构造函数,则仍应使用 @ConstructorBinding
来标识用于属性绑定的构造函数。
10 总结
本文介绍了如何在 Spring Boot 中使用 @ConfigurationProperties
来绑定配置属性,以及如何进行属性验证和属性转换。
关注我,紧跟本系列专栏文章,咱们下篇再续!
作者简介:魔都架构师,多家大厂后端一线研发经验,在分布式系统设计、数据平台架构和AI应用开发等领域都有丰富实践经验。
各大技术社区头部专家博主。具有丰富的引领团队经验,深厚业务架构和解决方案的积累。
负责:
- 中央/分销预订系统性能优化
- 活动&券等营销中台建设
- 交易平台及数据中台等架构和开发设计
- 车联网核心平台-物联网连接平台、大数据平台架构设计及优化
- LLM Agent应用开发
- 区块链应用开发
- 大数据开发挖掘经验
- 推荐系统项目
目前主攻市级软件项目设计、构建服务全社会的应用系统。
参考: