Spring Boot中@ConfigurationProperties注解实现原理源码解析
开源项目推荐
Pepper Metrics是我与同事开发的一个开源工具(https://github.com/zrbcool/pepper-metrics),其通过收集jedis/mybatis/httpservlet/dubbo/motan的运行性能统计,并暴露成prometheus等主流时序数据库兼容数据,通过grafana展示趋势。其插件化的架构也非常方便使用者扩展并集成其他开源组件。
请大家给个star,同时欢迎大家成为开发者提交PR一起完善项目。
- 概述
不用说大家都知道Spring Boot非常的方便,快捷,让开发的同学简单的几行代码加上几行配置甚至零配置就能启动并使用一个项目,项目当中我们也可能经常使用 @ConfigurationProperties将某个Bean与properties配置当中的prefix相绑定,使配置值与定义配置的Bean分离,方便管理。
那么,这个@ConfigurationProperties是什么机制,如何实现的呢?我们今天来聊聊这个话题
- 正文
2.1 从EnableConfigurationProperties说起
为什么从EnableConfigurationProperties讲? Spring Boot项目自身当中大量autoconfigure都是使用EnableConfigurationProperties注解启用XXXProperties功能,例如spring-data-redis的 这个RedisAutoConfiguration
@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class) //看这里
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
// ...
}
而RedisProperties中又带有注解@ConfigurationProperties(prefix = "spring.redis"),这样就将spring.redis这个前缀的配置项与RedisProperties 这个实体类进行了绑定。
2.2 EnableConfigurationProperties内部实现解析
说完来由,我们就来说说内部实现,先来看看
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EnableConfigurationPropertiesImportSelector.class)
public @interface EnableConfigurationProperties {
Class<?>[] value() default {};
}
@Import(EnableConfigurationPropertiesImportSelector.class)指明了这个注解的处理类EnableConfigurationPropertiesImportSelector, 查看EnableConfigurationPropertiesImportSelector源码
class EnableConfigurationPropertiesImportSelector implements ImportSelector {
private static final String[] IMPORTS = { ConfigurationPropertiesBeanRegistrar.class.getName(), ConfigurationPropertiesBindingPostProcessorRegistrar.class.getName() }; @Override public String[] selectImports(AnnotationMetadata metadata) { return IMPORTS; } // 省略部分其他方法
}
我们先看这块关键部分返回了一个IMPORTS数组,数组中包含ConfigurationPropertiesBeanRegistrar.class,ConfigurationPropertiesBindingPostProcessorRegistrar.class两个元素
根据@Import及ImportSelector接口的原理(其原理可以参考同事写的一篇文章:相亲相爱的@Import和@EnableXXX),我们得知spring会初始化上面两个Registrar到spring容器当中,而两个Registrar均实现了ImportBeanDefinitionRegistrar接口, 而ImportBeanDefinitionRegistrar会在处理Configuration时触发调用(其原理可以参考文章:这块找一篇文章),下面我们分别深入两个Registrar的源码:
ConfigurationPropertiesBeanRegistrar
ConfigurationPropertiesBindingPostProcessorRegistrar
2.2.1 ConfigurationPropertiesBindingPostProcessorRegistrar
直接看代码
public class ConfigurationPropertiesBindingPostProcessorRegistrar implements ImportBeanDefinitionRegistrar {
@Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { if (!registry.containsBeanDefinition(ConfigurationPropertiesBindingPostProcessor.BEAN_NAME)) { registerConfigurationPropertiesBindingPostProcessor(registry); registerConfigurationBeanFactoryMetadata(registry); } } private void registerConfigurationPropertiesBindingPostProcessor(BeanDefinitionRegistry registry) { GenericBeanDefinition definition = new GenericBeanDefinition(); definition.setBeanClass(ConfigurationPropertiesBindingPostProcessor.class); definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); registry.registerBeanDefinition(ConfigurationPropertiesBindingPostProcessor.BEAN_NAME, definition); } private void registerConfigurationBeanFactoryMetadata(BeanDefinitionRegistry registry) { GenericBeanDefinition definition = new GenericBeanDefinition(); definition.setBeanClass(ConfigurationBeanFactoryMetadata.class); definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); registry.registerBeanDefinition(ConfigurationBeanFactoryMetadata.BEAN_NAME, definition); }
}
可以看到注册了两个Bean到spring容器
ConfigurationPropertiesBindingPostProcessor
其实现如下接口: BeanPostProcessor, PriorityOrdered, ApplicationContextAware, InitializingBean
PriorityOrdered
Ordered.HIGHEST_PRECEDENCE + 1保证前期执行,且非最先
ApplicationContextAware
获取到ApplicationContext设置到内部变量
InitializingBean
afterPropertiesSet方法在Bean创建时被调用,保证内部变量configurationPropertiesBinder被初始化,这个binder类就是使prefix与propertyBean进行值绑定的关键工具类
BeanPostProcessor postProcessBeforeInitialization方法处理具体的bind逻辑如下:
@Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { ConfigurationProperties annotation = getAnnotation(bean, beanName, ConfigurationProperties.class); if (annotation != null) { bind(bean, beanName, annotation); } return bean; } private void bind(Object bean, String beanName, ConfigurationProperties annotation) { ResolvableType type = getBeanType(bean, beanName); Validated validated = getAnnotation(bean, beanName, Validated.class); Annotation[] annotations = (validated != null) ? new Annotation[] { annotation, validated } : new Annotation[] { annotation }; Bindable<?> target = Bindable.of(type).withExistingValue(bean).withAnnotations(annotations); try { // 在这里完成了,关键的prefix到PropertyBean的值绑定部分,所以各种@ConfigurationProperties注解最终生效就靠这部分代码了 this.configurationPropertiesBinder.bind(target); } catch (Exception ex) { throw new ConfigurationPropertiesBindException(beanName, bean, annotation, ex); } }
ConfigurationBeanFactoryMetadata
如果某些Bean是通过FactoryBean创建,则该类用于保存FactoryBean的各种原信息,用于ConfigurationPropertiesBindingPostProcessor当中的元数据查询,这里就不做展开
2.2.2 ConfigurationPropertiesBeanRegistrar
其实ConfigurationPropertiesBeanRegistrar是EnableConfigurationPropertiesImportSelector的静态内部类,在前面贴代码时被省略的部分,上代码
public static class ConfigurationPropertiesBeanRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { getTypes(metadata).forEach((type) -> register(registry, (ConfigurableListableBeanFactory) registry, type)); } private List<Class<?>> getTypes(AnnotationMetadata metadata) { MultiValueMap<String, Object> attributes = metadata .getAllAnnotationAttributes(EnableConfigurationProperties.class.getName(), false); return collectClasses((attributes != null) ? attributes.get("value") : Collections.emptyList()); } private List<Class<?>> collectClasses(List<?> values) { return values.stream().flatMap((value) -> Arrays.stream((Object[]) value)).map((o) -> (Class<?>) o) .filter((type) -> void.class != type).collect(Collectors.toList()); } private void register(BeanDefinitionRegistry registry, ConfigurableListableBeanFactory beanFactory, Class<?> type) { String name = getName(type); if (!containsBeanDefinition(beanFactory, name)) { registerBeanDefinition(registry, name, type); } } private String getName(Class<?> type) { ConfigurationProperties annotation = AnnotationUtils.findAnnotation(type, ConfigurationProperties.class); String prefix = (annotation != null) ? annotation.prefix() : ""; return (StringUtils.hasText(prefix) ? prefix + "-" + type.getName() : type.getName()); } private void registerBeanDefinition(BeanDefinitionRegistry registry, String name, Class<?> type) { assertHasAnnotation(type); GenericBeanDefinition definition = new GenericBeanDefinition(); definition.setBeanClass(type); registry.registerBeanDefinition(name, definition); } private void assertHasAnnotation(Class<?> type) {...} private boolean containsBeanDefinition(ConfigurableListableBeanFactory beanFactory, String name) {...} }
逻辑解读:
主逻辑入口(registerBeanDefinitions)
1 -> getTypes(metadata)拿到标注EnableConfigurationProperties注解的配置值,整理成List然后逐个处理 2 -> 对每个元素(Class)调用register方法处理
3 -> register方法通过类当中的ConfigurationProperties注解的prefix值加类名字作为beanName通过registry.registerBeanDefinition调用将Class<?>注册到registry当中
当标注注解@ConfigurationProperties的XXXProperties的BeanDefinition加入到registry中后,Bean的初始化就交给spring容器, 而这个过程中前面提到的ConfigurationPropertiesBindingPostProcessorRegistrar就完成一系列的后置操作帮助我们完成最终的值绑定
- 总结
@ConfigurationProperties的整体处理过程,本文已经基本讲述完毕,现在大体总结一下: EnableConfigurationProperties完成ConfigurationPropertiesBindingPostProcessorRegistrar及ConfigurationPropertiesBeanRegistrar的引入 其中:
ConfigurationPropertiesBeanRegistrar完成标注@ConfigurationProperties的类的查找并组装成BeanDefinition加入registry
ConfigurationPropertiesBindingPostProcessorRegistrar完成ConfigurationPropertiesBindingPostProcessor及ConfigurationBeanFactoryMetadata
ConfigurationPropertiesBindingPostProcessor完成所有标注@ConfigurationProperties的Bean到prefix的properties值绑定
ConfigurationBeanFactoryMetadata仅用于提供上面处理中需要的一些元数据信息
- 作者其他文章
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
阿里云企业服务器如何购买_阿里云企业服务器价格表_阿里云企业服务器价格查询入口(附价格对比表)
近期阿里云在服务器价格优惠方面频频发力,连续推出了多种服务器优惠活动。 阿里云2000优惠券免费领取(阿里云优惠券的作用:购买阿里云产品,最后支付结算的时候,阿里云优惠券可抵扣一部分费用。阿里云优惠券领取之后,直接购买阿里云即可,最后支付的时候,优惠券会自动抵扣。【无论你领取了多少张优惠券,一次只能使用一张优惠券】,因此我们我们是买多个阿里云产品,推荐使用购物车功能,这样购物车里面所有产品订单的总额更大,可以使用的优惠券金额也就越大了。) 阿里云建站主机3折限时优惠活动名称:主机爆款特惠:限时优惠 低至3折活动链接:http://t.cn/AiEOx15X点评:本优惠活动适合用于建站需求,宽带比较给力 品牌 型号 CPU 内存 宽带 硬盘 年限 折扣价 官网 阿里云 T5 1核 1G 1M 40G 1年 ¥313.20 推荐 链接 阿里云 轻量应用服务器 1核 2G 5M 40G 1年 ¥609.00推荐 链接 阿里云 轻量应用服务器 1核 1G 3M 40G 3年 ¥1197.00 推荐 链接 阿里云 独享云虚拟主机 1核 1G 5M 40G 1年 ¥206.00 链接 阿里云 独享...
- 下一篇
阿里云服务器(Ubuntu)--查看固定程序的进程
1.下面查看java和go语言的程序进程: ps -ef | grep java ps -ef | grep go 2.后台运行go语言程序,并查看go语言进程: nohup go run main.go & 发现进程多了,说明进程开启; 3.后台运行java语言程序,并查看java语言进程: nohup java -jar mp-1.0-SNAPSHOT.jar & 发现进程多了,说明进程开启;
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS8安装Docker,最新的服务器搭配容器使用
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- CentOS8编译安装MySQL8.0.19
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- SpringBoot2整合Redis,开启缓存,提高访问速度
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- Hadoop3单机部署,实现最简伪集群
- CentOS7,CentOS8安装Elasticsearch6.8.6
- CentOS6,7,8上安装Nginx,支持https2.0的开启