Spring Boot 源码分析 - @ConfigurationProperties 注解的实现
参考 知识星球 中 芋道源码 星球的源码解析,一个活跃度非常高的 Java 技术社群,感兴趣的小伙伴可以加入 芋道源码 星球,一起学习😄
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读
Spring Boot 版本:2.2.x
最好对 Spring 源码有一定的了解,可以先查看我的 《死磕 Spring 之 IoC 篇 - 文章导读》 系列文章
如果该篇内容对您有帮助,麻烦点击一下“推荐”,也可以关注博主,感激不尽~
该系列其他文章请查看:《精尽 Spring Boot 源码分析 - 文章导读》
概述
我们的 Spring Boot 应用经常会在 application.yml
配置文件里面配置一些自定义的配置,对于不同环境设置不同的值,然后可以通过 @ConfigurationProperties
注解将这些配置作为 Spring Bean 的属性值进行注入,那么本文来简单分析一下这个注解是如何将配置自动设置到 Spring Bean 的。
在开始之前,结合我前面的这么多 Spring 相关的源码分析文章,想必你会知道原理的,无非就是在 Spring Bean 的加载过程的某个阶段(大概率是初始化的时候)通过 BeanPostProcessor 解析该注解,并获取对应的属性值设置到其中。
先来看看这个注解
使用方式有两种:
@ConfigurationProperties
+@Component
注解(一个类)@EnableConfigurationProperties
(某个 Bean)+@ConfigurationProperties
注解(另一个普通类)
第二种方式和第一种原理都是一样的,不过第二种方式会注册一个 BeanPostProcessor 用于处理带有 @ConfigurationProperties
注解的 Spring Bean,同时会将指定的 Class 们解析出 BeanDefinition(Bean 的前身)并注册,这也就是为什么第二种不用标注 @Component
注解
那么第一种方式在哪注册的 BeanPostProcessor 呢?因为 Spring Boot 有一个 ConfigurationPropertiesAutoConfiguration
自动配置类,如下:
很简单,也是通过 @EnableConfigurationProperties
注解注册的这个 BeanPostProcessor 对象
这里有一个疑问,为什么
@ConfigurationProperties
注解上面不直接加一个@Component
注解呢?可能是因为这个注解的作用就是让 配置类 外部化配置吧
@EnableConfigurationProperties
org.springframework.boot.context.properties.EnableConfigurationProperties
,支持将指定的带有 @ConfigurationProperties
注解的类解析出 BeanDefinition(Bean 的前身)并注册,同时注册一个 BeanPostProcessor 去处理带有 @ConfigurationProperties
注解的 Bean
可以看到这个注解也是通过 @Import
注解来驱动某个功能的,是不是发现 @EnableXxx
驱动注解都是以这样的方式来实现的
那么关于 @Import
注解的实现原理我在很多地方都提到过,这里再提一下,模块驱动注解通常需要结合 @Configuration
注解一起使用,因为需要先被当做一个配置类,然后解析到上面有 @Import
注解后则进行处理,对于 @Import
注解的值有三种情况:
-
该 Class 对象实现了
ImportSelector
接口,调用它的selectImports(..)
方法获取需要被处理的 Class 对象的名称,也就是可以将它们作为一个 Bean 被 Spring IoC 管理- 该 Class 对象实现了
DeferredImportSelector
接口,和上者的执行时机不同,在所有配置类处理完后再执行,且支持@Order
排序
- 该 Class 对象实现了
-
该 Class 对象实现了
ImportBeanDefinitionRegistrar
接口,会调用它的registerBeanDefinitions(..)
方法,自定义地往 BeanDefinitionRegistry 注册中心注册 BeanDefinition(Bean 的前身) -
该 Class 对象是一个
@Configuration
配置类,会将这个类作为一个 Bean 被 Spring IoC 管理
对于 @Import
注解不熟悉的小伙伴可查看我前面的 《死磕Spring之IoC篇 - @Bean 等注解的实现原理》 这篇文章
这里的 @EnableConfigurationProperties
注解,通过 @Import
导入 EnableConfigurationPropertiesRegistrar 这个类(实现了 ImportBeanDefinitionRegistrar
接口)来实现该功能的,下面会进行分析
EnableConfigurationPropertiesRegistrar
org.springframework.boot.context.properties.EnableConfigurationPropertiesRegistrar
,实现了 ImportBeanDefinitionRegistrar
接口,是 @EnableConfigurationProperties
注解的核心类
注册 BeanDefinition(Bean 的前身)的过程如下:
-
先注册两个内部 Bean
-
注册一个 ConfigurationPropertiesBindingPostProcessor 类型的 BeanDefinition(内部角色),如果不存在的话
-
注册一个 ConfigurationBeanFactoryMetadata 类型的 BeanDefinition(内部角色),从 Spring 2.2.0 开始就被废弃了,忽略掉
-
-
创建一个
ConfigurationPropertiesBeanRegistrar
对象 -
获取
@EnableConfigurationProperties
注解指定的 Class 类对象们 -
调用
ConfigurationPropertiesBeanRegistrar
的register(Class<?> type)
方法,依次注册指定的 Class 类对应的 BeanDefinition,这样一来这个 Class 不用标注@Component
就可以注入这个配置属性对象了
ConfigurationPropertiesBeanRegistrar
org.springframework.boot.context.properties.ConfigurationPropertiesBeanRegistrar
,是 EnableConfigurationPropertiesRegistrar
的辅助类
过程如下:
-
先获取这个 Class 类对象的
@ConfigurationProperties
注解 -
调用
register(..)
方法,为这个 Class 对象注册一个 BeanDefinition- 生成一个 Bean 的名称,为
@ConfigurationProperties
注解的${prefix}-类全面
,或者类全名
- 如果没有该名称的 Bean,则注册一个
type
类型的 BeanDefinition
- 生成一个 Bean 的名称,为
registerBeanDefinition 方法
注册带有 @ConfigurationProperties
注解的 Class 对象
逻辑比较简单,就是将这个 @ConfigurationProperties
注解的 Class 对象生成一个 BeanDefinition 并注册
ConfigurationPropertiesBindingPostProcessor
org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor
,将配置绑定到 @ConfigurationProperties
注解的配置类中
setApplicationContext 方法
ApplicationContextAware 的回调
afterPropertiesSet 方法
InitializingBean 初始化方法
getOrder 方法
PriorityOrdered 优先级
1. postProcessBeforeInitialization 方法
BeanPostProcessor 的初始化前置操作
过程如下:
- 调用
ConfigurationPropertiesBean#get(..)
方法,尝试根据 Bean 解析出一个 ConfigurationPropertiesBean 对象,包含@ConfigurationProperties
注解信息 - 调用
bind(..)
方法,开始获取指定prefix
前缀的属性值,设置到这个 Bean 中 - 返回属性填充后的 Bean
4. bind 方法
可以看到最后是通过 ConfigurationPropertiesBinder
属性绑定器来将属性绑定到 bean
中的
ConfigurationPropertiesBean
org.springframework.boot.context.properties.ConfigurationPropertiesBean
,是 @ConfigurationProperties
注解对应的 Bean 的封装,用于将对应的属性值绑定到这个 Bean 中
参考上面的注释查看每个属性的描述
2. get 方法
获取某个 @ConfigurationProperties
注解对应的 Bean 的 ConfigurationPropertiesBean
过程如下:
- 找到这个
beanName
对应的工厂方法,例如@Bean
标注的方法就是一个工厂方法,不是@Bean
的话这里为空 - 调用
create(..)
方法,创建一个 ConfigurationPropertiesBean 对象,包含了这个 Bean 的@ConfigurationProperties
注解信息
3. create 方法
过程如下:
- 找到这个 Bean 上面的
@ConfigurationProperties
注解,如果是@Bean
标注的方法 Bean,也会尝试从所在的 Class 类上面获取 - 如果没有配置
@ConfigurationProperties
注解,则直接返回null
- 找到这个 Bean 上面的
@Validated
注解 - 将
@ConfigurationProperties
、Validated
注解信息,目标 Bean 以及它的 Class 对象,绑定到一个 Bindable 对象中 - 将
beanName
、目标 Bean、ConfigurationProperties
注解、第4
步的 Bindable 对象封装到一个 ConfigurationPropertiesBean 对象中
ConfigurationPropertiesBinder
org.springframework.boot.context.properties.ConfigurationPropertiesBinder
,对 ConfigurationPropertiesBean 进行属性绑定
5. bind 方法
对 ConfigurationPropertiesBean 进行属性绑定,如下:
过程如下:
-
获取这个 Bean 的 Bindable 对象(包含了
@ConfigurationProperties
、@Validated
配置信息和这个 Bean) -
获取这个 Bean 的
@ConfigurationProperties
注解信息 -
获取一个 BindHandler 绑定处理器
-
获取一个 Binder 对象,包含了 Spring 应用上下文的所有配置信息,占位符处理器,类型转换器
-
通过这个 Binder 将指定
prefix
前缀的属性值设置到这个 Bean 中,会借助 ConversionService 类型转换器进行类型转换
整个处理过程主要在第 5
步,有点复杂,借助于 Binder 绑定器实现的,这里就不讲述了,感兴趣的可以去研究研究😄
加餐
我们在编写 application.yml
文件时,当你输入一个字母时,IDE 是不是会提示很多选项供你选择,这个就要归功于 META-INF/spring-configuration-metadata.json
、META-INF/additional-spring-configuration-metadata.json
两个文件,在这两个文件里面可以定义你需要的配置的信息,例如 Spring Boot 提供的:
上面仅列出了部分内容,可以看到定义了每个配置的名称、类型、描述和来源,同时可以定义每个配置能够输入的值,这样一来,我们就能够在 IDE 中快速的输入需要的配置项。
这个文件是通过 Spring Boot 提供的 spring-boot-configuration-processor
工具模块生成的,借助于 SPI 机制配置了一个 ConfigurationMetadataAnnotationProcessor
注解处理器,它继承 javax.annotation.processing.AbstractProcessor
抽象类。也就是说这个处理器在编译阶段,会解析每个 @ConfigurationProperties
注解标注的类,将这些类对应的一些配置项(key)的信息保存在 META-INF/spring-configuration-metadata.json
文件中,例如类型、默认值,来帮助你编写 application.yml
的时候会有相关提示。
而且,当我们使用 @ConfigurationProperties
注解后,IDE 会提示我们引入这个工具类:
关于这部分内容可参考 Spring Boot 官方文档
总结
本文分析了 Spring Boot 中的 @ConfigurationProperties
注解的实现过程,原理就是通过注册的一个 BeanPostProcessor 会在加载 Spring Bean 初始化的时候进行前置处理,解析出 @ConfigurationProperties
注解相关信息,然后找到对应前缀的属性值绑定到这个 Bean 中。
使用这个注解有两种方式:
@ConfigurationProperties
+@Component
注解(一个类)@EnableConfigurationProperties
(某个 Bean)+@ConfigurationProperties
注解(另一个普通类)
关于 @EnableConfigurationProperties
注解的处理过程也比较简单,通过 @Import
注解的方法,注册一个 BeanPostProcessor 用于处理 @ConfigurationProperties
注解的 Bean,同时会将指定的带有 @ConfigurationProperties
注解的 Class 对象注册到 Spring IoC 容器中,这也就是为什么不用加 @Component
注解的原因
关于上面第一种方式是通过一个 ConfigurationPropertiesAutoConfiguration 自动配置类借助 @EnableConfigurationProperties
注解注册的这个 BeanPostProcessor 去处理 @ConfigurationProperties
注解的 Bean
学习完 Spring Boot 源码后,个人觉得是非常有帮助的,让自己能够清楚的了解 Sprig Boot 应用的运行原理,在处理问题以及调优等方面会更加轻松。另外,熟悉 Spring Boot 的自动配置功能后,编写一个 Spring Boot Starter 可以说是轻而易举。
至此,关于 Spirng 和 Spring Boot 两个流行的基础框架的源码已经全部分析完了,接下来笔者要开始学习其他的东西了,例如 MySQL、Dubbo 和 Spring Cloud,敬请期待吧,加油👨🎓
这里提一句,Apache Dubbo 3.0 正式发布,全面拥抱云原生,先深入学习一下 Dubbo ~
路漫漫其修远兮,吾将上下而求索
__EOF__

本文链接:https://www.cnblogs.com/lifullmoon/p/14957836.html
关于博主:本着学习与分享的目的,将持续不断的进行知识分享。望各位到访看客如有喜欢的文章,可以点击一下“推荐”,若有不同建议或者意见,也请不吝赐教,博主感激不尽。另外,欢迎转载博主的文章,请务必依据文章下方的版权声明转载。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义