近期遇到了很奇怪的问题就是之前别人写好的代码,@ConfigurationProperties修饰的实体类被其他类注入之后可以正常使用,到我接手的时候就出现了奇怪的问题,wdnmd o(╥﹏╥)o 我们先看下这个类的使用方式,然后再讲问题的前因后果和处理方式。
ConfigurationProperties使用详解
在编写项目代码时,我们要求更灵活的配置,更好的模块化整合。在 Spring Boot 项目中,为满足以上要求,我们将大量的参数配置在 application.properties 或 application.yml 文件中,通过 @ConfigurationProperties 注解,我们可以方便的获取这些参数值。
先说下该注解的内部:
package org.springframework.boot.context.properties;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ConfigurationProperties {
@AliasFor("prefix")
String value() default "";
@AliasFor("value")
String prefix() default ""; //配置前缀,绑定配置文件中的配置该注解可以放在类上,也可以放在方法上
boolean ignoreInvalidFields() default false; // 忽略不可用的属性
boolean ignoreNestedProperties() default false; // 忽略嵌套属性
boolean ignoreUnknownFields() default true; //
/** @deprecated */
@Deprecated
boolean exceptionIfInvalid() default true; //该属性是,如果有不可用的属性会抛出异常
}
prefix
该属性是用来指定属性前缀,只需要指定一个前缀,就能绑定有这个前缀的所有属性值。
例如:
course:
num : 1
name : 语文
@Data
@Component
@ConfigurationProperties(prefix = "course")
public class Course {
private String name;
private int num;
}
@ConfigurationProperties 的基本用法非常简单:我们为每个要捕获的外部属性提供一个带有字段的类。请注意以下几点:
前缀定义了哪些外部属性将绑定到类的字段上
根据 Spring Boot 宽松的绑定规则,类的属性名称必须与外部属性的名称匹配
我们可以简单地用一个值初始化一个字段来定义一个默认值
类本身可以是包私有的
类的字段必须有公共 setter 方法
ignoreInvalidFields
如果我们在 application.yml 属性上定义的属性不能被正确的解析会发生什么?假如我们为原本应该为布尔值的属性提供的值为 ‘tru’:
course:
num : 1
name : 语文
isOK : tru
默认情况下,Spring Boot 将会启动失败,并抛出异常:
Failed to bind properties under 'myapp.mail.enabled' to java.lang.Boolean:
Property: myapp.mail.enabled
Value: foo
Origin: class path resource [application.properties]:1:20
Reason: failed to convert java.lang.String to java.lang.Boolean
当我们为属性配置错误的值时,而又不希望 Spring Boot 应用启动失败,我们可以设置 ignoreInvalidFields 属性为 true (默认为 false)
@Data
@Component
@ConfigurationProperties(prefix = "course",ignoreInvalidFields = true)
public class Course {
private String name;
private int num;
private boolean isOK;
}
ignoreUnknownFields
和上面的情况有些相反,如果我们在 application.yml 文件提供了 Course 类不知道的属性会发生什么?
course:
num : 1
name : 语文
iscxk : true
默认情况下,Spring Boot 会忽略那些不能绑定到 @ConfigurationProperties
类字段的属性
然而,当配置文件中有一个属性实际上没有绑定到 @ConfigurationProperties
类时,我们可能希望启动失败。也许我们以前使用过这个配置属性,但是它已经被删除了,这种情况我们希望被触发告知手动从 application.yml
删除这个属性
为了实现上述情况,我们仅需要将 ignoreUnknownFields
属性设置为 false (默认是 true)
现在,应用启动时,控制台会反馈我们异常信息
Binding to target [Bindable@cf65451 type = com.example.configurationproperties.Course, value = 'provided', annotations = array<Annotation>[@org.springframework.boot.context.properties.ConfigurationProperties(value=course, prefix=course, ignoreInvalidFields=false, ignoreUnknownFields=false)]] failed:
Property: course.iscxk
Value: true
Origin: class path resource [application.properties]:3:29
Reason: The elements [course.iscxk] were left unbound.
弃用警告⚠️(Deprecation Warning)
ignoreUnknownFields 在未来 Spring Boot 的版本中会被标记为 deprecated,因为我们可能有两个带有 @ConfigurationProperties 的类,同时绑定到了同一个命名空间 (namespace) 上,其中一个类可能知道某个属性,另一个类却不知道某个属性,这样就会导致启动失败。
启动时校验 @ConfigurationProperties
如果我们希望配置参数在传入到应用中时有效的,我们可以通过在字段上添加 bean validation 注解,同时在类上添加 @Validated 注解
@Data
@Component
@ConfigurationProperties(prefix = "course",ignoreInvalidFields = true)
public class Course {
private String name;
private int num;
private boolean isOK;
}
如果我们忘记在 application.yml 文件设置 一个enabled 属性,并且设置对应值为空
应用启动时,我们将会得到 BindValidationException
Binding to target org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under ‘course’ to com.example.configurationproperties.Course failed:
Property: course.enabled
Value: null
Reason: must not be null
当然这些默认的验证注解不能满足你的验证要求,我们也可以自定义注解
如果你的验证逻辑很特殊,我们可以实现一个方法,并用 @PostConstruct 标记,如果验证失败,方法抛出异常即可, 关于 @PostConstruct,可以查看 Spring Bean 的生命周期,我从哪里来?
复杂属性类型
多数情况,我们传递给应用的参数是基本的字符串或数字。但是,有时我们需要传递诸如 List 的数据类型
List 和 Set
假如,我们班级添加几个学生的名字,我们可以添加该属性到 Course 类中
我们有两种方式让 Spring Boot 自动填充该 list 属性
@Data
@Component
@ConfigurationProperties(prefix = "course")
public class Course {
private String name;
private int num;
private List<String> students;
}
application.properties
在 application.properties 文件中以数组形式书写
course.students[0]=cxk1
course.students[1]=cxk2
application.yml
YAML 本身支持 list 类型,所以可以在 application.yml 文件中添加:
course:
num : 1
name : 语文
students :
- cxk1
- cxk2
当然我们还可以把学生摘出来,单独成一个类:
@Data
public class Student {
private String name;
private int age;
}
然后把Course改造下,让课程里面有几个学生。
@Data
@Component
@ConfigurationProperties(prefix = "course")
public class Course {
private String name;
private int num;
private List<Student> students;
}
application.properties
在 application.properties 文件中以数组形式书写
course.students[0].name=cxk1
course.students[1].name=cxk2
course.students[0].age=22
course.students[1].age=21
application.yml
YAML 本身支持 list 类型,所以可以在 application.yml 文件中添加:
course:
num : 1
name : 语文
students :
- name : cxk1
age : 22
- name : cxk2
age : 21
Duration
Spring Boot 内置支持从配置参数中解析 durations (持续时间),官网文档 给出了明确的说明
我们既可以配置毫秒数数值,也可配置带有单位的文本:
@Data
@Component
@ConfigurationProperties(prefix = "course")
public class DurationTest {
private Duration pauseTimes;
}
course.pause-times=5s
官网上已明确说明,配置 duration 不写单位,默认按照毫秒来指定,我们也可已通过 @DurationUnit 来指定单位:
我们既可以配置毫秒数数值,也可配置带有单位的文本:
@Data
@Component
@ConfigurationProperties(prefix = "course")
public class DurationTest {
@DurationUnit (ChronoUnit.SECONDS)
private Duration pauseTimes;
}
常用单位如下:
ns
for nanoseconds (纳秒)
us
for microseconds (微秒)
ms
for milliseconds (毫秒)
s
for seconds (秒)
m
for minutes (分)
h
for hours (时)
d
for days (天)
DataSize
与 Duration 的用法一毛一样,默认单位是 byte (字节),可以通过 @DataSizeUnit 单位指定:
@Data
@Component
@ConfigurationProperties(prefix = "course")
public class DurationTest {
@DataSizeUnit(DataUnit.MEGABYTES)
private DataSize maxAttachmentSize = DataSize.ofMegabytes(2);
}
添加配置
course.max-attachment-size=2MB
但是,我测试的时候打印出来结果都是以 B (bytes) 来显示
常见单位如下:
B
for bytes
KB
for kilobytes
MB
for megabytes
GB
for gigabytes
TB
for terabytes
该注解修饰的实体bean被注入后所有属性为null问题解决
然后我来讲述一下我在项目中遇到的有Setter Getter方法的实体bean 被注入后get不到属性的问题。
首先我代码是这样的,因为是公司的代码,所以我就不贴出来了,我大概还原下代码哈。
@Data
@Component
@ConfigurationProperties(prefix = "course")
public class CourseConfig {
private String courseName;
private Integer courseNum;
private String COURSE_DESC_DETAIL;
}
然后配置文件呢,配置都没有任何问题,因为之前是可以使用的,后续到我手里就不能用了,get不到COURSE_DESC_DETAIL
这个属性。并且DEBUG的时候发现,下面清一色的null。我焦头烂额、抓破头皮都没想到到底是哪里出了问题,然后我在每个set方法语句里面打印了一句话,输出set进去的值,发现每个都set进去值了,但是为什么get不到呢?当时我没有重写toString方法。然后我就把toString方法重写了。发现打印是正常的。这就奇怪了,然后继续往下找,我把每个属性都打印了一遍,发现就唯独这个COURSE_DESC_DETAIL
打印不出来。是个null,然后我就思来想去也不知道是什么原因,最后在网上发现一篇帖子,说可能是由于框架版本引起的,带有_
的实体属性不能get到。然后我就抱着试试的心态发现雀食是酱紫,然后芜湖~~~(这个驼峰不是我写的 (#.#) )
总结:
前缀定义了哪些外部属性将绑定到类的字段上
根据 Spring Boot 宽松的绑定规则,类的属性名称必须与外部属性的名称匹配
我们可以简单地用一个值初始化一个字段来定义一个默认值
类本身可以是包私有的
类的字段必须有公共 setter 方法
我再在这里补充一条:请使用标准驼峰命名法定义属性。请使用标准驼峰命名法定义属性。请使用标准驼峰命名法定义属性。