@ConfigurationProperties 注解使用姿势,这一篇就够了

简介: @ConfigurationProperties 注解使用姿势,这一篇就够了

在编写项目代码时,我们要求更灵活的配置,更好的模块化整合。在 Spring Boot 项目中,为满足以上要求,我们将大量的参数配置在 application.properties 或 application.yml 文件中,通过 @ConfigurationProperties 注解,我们可以方便的获取这些参数值


使用 @ConfigurationProperties 配置模块


假设我们正在搭建一个发送邮件的模块。在本地测试,我们不想该模块真的发送邮件,所以我们需要一个参数来「开关」 disable 这个功能。另外,我们希望为这些邮件配置一个默认的主题,这样,当我们查看邮件收件箱,通过邮件主题可以快速判断出这是测试邮件

在 application.properties 文件中创建这些参数:


微信图片_20220509211215.png


我们可以使用 @Value 注解或着使用 Spring Environment bean 访问这些属性,是这种注入配置方式有时显得很笨重。我们将使用更安全的方式(@ConfigurationProperties )来获取这些属性


微信图片_20220509211258.png


@ConfigurationProperties 的基本用法非常简单:我们为每个要捕获的外部属性提供一个带有字段的类。请注意以下几点:


  • 前缀定义了哪些外部属性将绑定到类的字段上


  • 根据 Spring Boot 宽松的绑定规则,类的属性名称必须与外部属性的名称匹配


  • 我们可以简单地用一个值初始化一个字段来定义一个默认值


  • 类本身可以是包私有的


  • 类的字段必须有公共 setter 方法


Spring 宽松绑定规则 (relaxed binding)


Spring使用一些宽松的绑定属性规则。因此,以下变体都将绑定到 hostName 属性上:


微信图片_20220509211321.png


如果我们将 MailModuleProperties 类型的 bean 注入到另一个 bean 中,这个 bean 现在可以以类型安全的方式访问那些外部配置参数的值。


但是,我们仍然需要让 Spring 知道我们的 @ConfigurationProperties 类存在,以便将其加载到应用程序上下文中( 面试还不知道 BeanFactory 和 ApplicationContext 的区别?)


激活 @ConfigurationProperties


对于 Spring Boot,创建一个 MailModuleProperties 类型的 bean,我们可以通过下面几种方式将其添加到应用上下文中


首先,我们可以通过添加 @Component 注解让 Component Scan 扫描到


微信图片_20220509211345.png


很显然,只有当类所在的包被 Spring @ComponentScan 注解扫描到才会生效,默认情况下,该注解会扫描在主应用类下的所有包结构


我们也可以通过 Spring 的 Java Configuration 特性实现同样的效果:


微信图片_20220509211406.png


只要 MailModuleConfiguration 类被 Spring Boot 应用扫描到,我们就可以在应用上下文中访问 MailModuleProperties bean


我们还可以使用 @EnableConfigurationProperties 注解让我们的类被 Spring Boot 所知道,在该注解中其实是用了@Import(EnableConfigurationPropertiesImportSelector.class) 实现,大家可以看一下


微信图片_20220509211427.png


激活一个 @ConfigurationProperties 类的最佳方式是什么?


所有上述方法都同样有效。然而,我建议模块化你的应用程序,并让每个模块提供自己的@ConfigurationProperties 类,只提供它需要的属性,就像我们在上面的代码中对邮件模块所做的那样。这使得在不影响其他模块的情况下重构一个模块中的属性变得容易。


因此,我不建议在应用程序类本身上使用 @EnableConfigurationProperties,如许多其他教程中所示,是在特定于模块的 @Configuration 类上使用@EnableConfigurationProperties,该类也可以利用包私有的可见性对应用程序的其余部分隐藏属性。


无法转换的属性


如果我们在 application.properties 属性上定义的属性不能被正确的解析会发生什么?假如我们为原本应该为布尔值的属性提供的值为 'foo':


微信图片_20220509211453.png


默认情况下,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)


微信图片_20220509211537.png


这样,Spring Boot 将会设置 enabled 字段为我们在 Java 代码里设定好的默认值。如果我们没有设置默认值,enabled 将为 null,因为这里定义的是 boolean 的包装类

Boolean


未知的属性


和上面的情况有些相反,如果我们在 application.properties 文件提供了 MailModuleProperties 类不知道的属性会发生什么?


微信图片_20220509211559.png


默认情况下,Spring Boot 会忽略那些不能绑定到 @ConfigurationProperties 类字段的属性


然而,当配置文件中有一个属性实际上没有绑定到 @ConfigurationProperties 类时,我们可能希望启动失败。也许我们以前使用过这个配置属性,但是它已经被删除了,这种情况我们希望被触发告知手动从 application.properties 删除这个属性

为了实现上述情况,我们仅需要将 ignoreUnknownFields 属性设置为 false (默认是 true)


微信图片_20220509211631.png


现在,应用启动时,控制台会反馈我们异常信息


Binding to target [Bindable@cf65451 type = com.example.configurationproperties.properties.MailModuleProperties, value = 'provided', annotations = array<Annotation>[@org.springframework.boot.context.properties.ConfigurationProperties(value=myapp.mail, prefix=myapp.mail, ignoreInvalidFields=false, ignoreUnknownFields=false)]] failed:
    Property: myapp.mail.unknown-property
    Value: foo
    Origin: class path resource [application.properties]:3:29
    Reason: The elements [myapp.mail.unknown-property] were left unbound.


弃用警告⚠️(Deprecation Warning) ignoreUnknownFields 在未来 Spring Boot 的版本中会被标记为 deprecated,因为我们可能有两个带有 @ConfigurationProperties 的类,同时绑定到了同一个命名空间 (namespace) 上,其中一个类可能知道某个属性,另一个类却不知道某个属性,这样就会导致启动失败

启动时校验 @ConfigurationProperties


如果我们希望配置参数在传入到应用中时有效的,我们可以通过在字段上添加 bean validation 注解,同时在类上添加 @Validated 注解


微信图片_20220509211728.png


如果我们忘记在 application.properties 文件设置 enabled 属性,并且设置 defaultSubject 为空


微信图片_20220509211750.png


应用启动时,我们将会得到 BindValidationException


Binding to target org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'myapp.mail' to com.example.configurationproperties.properties.MailModuleProperties failed:
    Property: myapp.mail.enabled
    Value: null
    Reason: must not be null
    Property: myapp.mail.defaultSubject
    Value: null
    Reason: must not be empty


当然这些默认的验证注解不能满足你的验证要求,我们也可以自定义注解

如果你的验证逻辑很特殊,我们可以实现一个方法,并用 @PostConstruct 标记,如果验证失败,方法抛出异常即可, 关于 @PostConstruct,可以查看 Spring Bean 的生命周期,我从哪里来?


复杂属性类型


多数情况,我们传递给应用的参数是基本的字符串或数字。但是,有时我们需要传递诸如 List 的数据类型


List 和 Set


假如,我们为邮件模块提供了一个 SMTP 服务的列表,我们可以添加该属性到 MailModuleProperties 类中


微信图片_20220509211835.png


我们有两种方式让 Spring Boot 自动填充该 list 属性


application.properties


在 application.properties 文件中以数组形式书写


微信图片_20220509211858.png


application.yml


YAML 本身支持 list 类型,所以可以在 application.yml 文件中添加:


微信图片_20220509211921.png


set 集合也是这种方式的配置方式,不再重复书写。另外YAML 是更好的阅读方式,层次分明,所以在实际应用中更推荐大家使用该种方式做数据配置


Duration


Spring Boot 内置支持从配置参数中解析 durations (持续时间),官网文档 给出了明确的说明


微信图片_20220509211943.png


我们既可以配置毫秒数数值,也可配置带有单位的文本:


微信图片_20220509212007.png


官网上已明确说明,配置 duration 不写单位,默认按照毫秒来指定,我们也可已通过 @DurationUnit 来指定单位:


微信图片_20220509212027.png


常用单位如下:


  • 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 单位指定:


微信图片_20220509212056.png


添加配置


微信图片_20220509212123.png


但是,我测试的时候打印出来结果都是以 B (bytes) 来显示


常见单位如下:


  • B for bytes


  • KB for kilobytes


  • MB for megabytes


  • GB for gigabytes


  • TB for terabytes


自定义类型


有些情况,我们想解析配置参数到我们自定义的对象类型上,假设,我们我们设置最大包裹重量:


微信图片_20220509212201.png


在 MailModuleProperties 中添加 Weight 属性


微信图片_20220509212231.png


我们可以模仿 DataSize 和 Duration 创造自己的 converter (转换器)


微信图片_20220509212316.png


将其注册到 Spring Boot 上下文中


微信图片_20220509212343.png



@ConfigurationPropertiesBinding 注解是让 Spring Boot 知道使用该转换器做数据绑定


使用 Spring Boot Configuration Processor 完成自动补全


我们向项目中添加依赖:


Maven


微信图片_20220509212409.png


Gradle


微信图片_20220509212437.png


重新 build 项目之后,configuration processor 会为我们创建一个 JSON 文件:


微信图片_20220509212459.jpg


这样,当我们在 application.properties 和 application.yml 中写配置的时候会有自动提醒:


微信图片_20220509212522.jpg


标记配置属性为 Deprecated


configuration processor 允许我们标记某一个属性为 deprecated微信图片_20220509212548.png

微信图片_20220509212548.png


我们可以通过添加 @DeprecatedConfigurationProperty 注解到字段的 getter 方法上,来标示该字段为 deprecated,重新 build 项目,看看 JSON 文件发生了什么?


微信图片_20220509212611.png


当我们再编写配置文件时,已经给出了明确 deprecated 提示:


微信图片_20220509212632.jpg


总结


Spring Boot 的 @ConfigurationProperties 注解在绑定类型安全的 Java Bean 时是非常强大的,我们可以配合其注解属性和 @DeprecatedConfigurationProperty 注解获取到更友好的编程方式,同时这样让我们的配置更加模块化。


附加说明


以为 @ConfigurationProperties 注解满足我们的全部需要了吗?其实不然,Spring 官网明确给出了该注解和 @Value 注解的对比:


微信图片_20220509212718.jpg


如果使用 SpEL 表达式,我们只能选择 @Value 注解


另外我之前在阅读 RabbitMQ 源码时,发现 RabbitProperties 类充分的利用了 @ConfigurationProperties 注解特性:


  • deprecated


微信图片_20220509212740.jpg


Duration


微信图片_20220509212814.jpg


  • Enum


  • 嵌套属性


感觉自己后知后觉,最近在思考,为什么小时候要阅读和背诵古诗词,文言文等经典,因为这样写文章就可以轻松熟练的引用经典。技术也一样,各种框架的源码就是学生时代的古诗词和文言文,我们要多多查看阅读,甚至背诵编程思想,这样就可以写出越来越优雅的代码

关于 @ConfigurationProperties 注解的使用,这里推荐 RabbitMQ Github 源码,只需看这一个类就可以,知道怎样充分利用这个注解.


Demo 代码获取,回复公众号「demo」,打开链接查看对应的子文件夹即可


灵魂追问


  1. 在实际项目中, 你能够充分利用这些特性让你的配置更灵活和模块化吗?


  1. 阅读框架源码时,他们都是怎样配置的呢?


  1. @Value 注解怎样给出默认值?


以读侦探小说思维轻松趣味学习 Java 技术栈相关知识,本着将复杂问题简单化,抽象问题具体化和图形化原则逐步分解技术问题,技术持续更新,请持续关注......
相关文章
|
15天前
|
前端开发 Java 关系型数据库
SpringBootWebProject学习5、常用注解说明
SpringBootWebProject学习5、常用注解说明
11 0
|
11月前
|
XML 存储 Java
SpringBoot bean自动装配原理,这一篇就够了! 1
SpringBoot bean自动装配原理,这一篇就够了!
|
10月前
|
存储 Java 程序员
探秘Spring中Bean的注解宝典:解读存取Bean的相关注解及用法
探秘Spring中Bean的注解宝典:解读存取Bean的相关注解及用法
|
11月前
|
XML SQL Java
SpringBoot bean自动装配原理,这一篇就够了! 2
SpringBoot bean自动装配原理,这一篇就够了!
|
缓存 NoSQL 安全
Spring声明式基于注解的缓存(2-实践篇)
目录 一、序言 二、使用示例 1、配置 (1) application.properties (2) 基于Redis缓存的CacheManager配置 2、注解运用测试用例 (1) 指定key条件式缓存 (2) 返回值为Optional类型条件式缓存 (3) 不指定key条件式缓存 (4) 指定key删除缓存 (5) 指定key更新缓存 三、结语
Spring声明式基于注解的缓存(2-实践篇)
|
存储 设计模式 Java
Spring【五大类注解的存储和读取Bean方法注解】(下)
Spring【五大类注解的存储和读取Bean方法注解】(下)
Spring【五大类注解的存储和读取Bean方法注解】(下)
|
存储 XML Java
Spring【五大类注解的存储和读取Bean方法注解】(上)
Spring【五大类注解的存储和读取Bean方法注解】
Spring【五大类注解的存储和读取Bean方法注解】(上)
|
Java 数据库 开发者
@ConfigurationProperties 与 @Value区别 | 学习笔记
快速学习 @ConfigurationProperties 与 @Value 区别
157 0
@ConfigurationProperties 与 @Value区别 | 学习笔记
|
XML Java 数据格式
怎样用注解的方式配置Spring?
怎样用注解的方式配置Spring?
怎样用注解的方式配置Spring?
|
Java Spring
SpringBoot - @ConfigurationProperties 注解使用姿势,这一篇就够了(一)
SpringBoot - @ConfigurationProperties 注解使用姿势,这一篇就够了(一)
130 0
SpringBoot - @ConfigurationProperties 注解使用姿势,这一篇就够了(一)