0. 开源项目推荐

Pepper Metrics是我与同事开发的一个开源工具(https://github.com/zrbcool/pepper-metrics),其通过收集jedis/mybatis/httpservlet/dubbo/motan的运行性能统计,并暴露成prometheus等主流时序数据库兼容数据,通过grafana展示趋势。其插件化的架构也非常方便使用者扩展并集成其他开源组件。

请大家给个star,同时欢迎大家成为开发者提交PR一起完善项目。

1. 概述

不用说大家都知道Spring Boot非常的方便,快捷,让开发的同学简单的几行代码加上几行配置甚至零配置就能启动并使用一个项目,项目当中我们也可能经常使用

@ConfigurationProperties将某个Bean与properties配置当中的prefix相绑定,使配置值与定义配置的Bean分离,方便管理。

那么,这个@ConfigurationProperties是什么机制,如何实现的呢?我们今天来聊聊这个话题

2. 正文

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<Class<?>>然后逐个处理

2 -> 对每个元素(Class<?>)调用register方法处理

3 -> register方法通过类当中的ConfigurationProperties注解的prefix值加类名字作为beanName通过registry.registerBeanDefinition调用将Class<?>注册到registry当中

当标注注解@ConfigurationProperties的XXXProperties的BeanDefinition加入到registry中后,Bean的初始化就交给spring容器,

而这个过程中前面提到的ConfigurationPropertiesBindingPostProcessorRegistrar就完成一系列的后置操作帮助我们完成最终的值绑定

3. 总结

@ConfigurationProperties的整体处理过程,本文已经基本讲述完毕,现在大体总结一下:

EnableConfigurationProperties完成ConfigurationPropertiesBindingPostProcessorRegistrar及ConfigurationPropertiesBeanRegistrar的引入

其中:

  • ConfigurationPropertiesBeanRegistrar完成标注@ConfigurationProperties的类的查找并组装成BeanDefinition加入registry
  • ConfigurationPropertiesBindingPostProcessorRegistrar完成ConfigurationPropertiesBindingPostProcessor及ConfigurationBeanFactoryMetadata
    • ConfigurationPropertiesBindingPostProcessor完成所有标注@ConfigurationProperties的Bean到prefix的properties值绑定
    • ConfigurationBeanFactoryMetadata仅用于提供上面处理中需要的一些元数据信息

4. 作者其他文章

https://github.com/zrbcool/blog-public

5. 微信订阅号

Spring Boot中@ConfigurationProperties注解实现原理源码解析的更多相关文章

  1. Spring注解Component原理源码解析

    在实际开发中,我们经常使用Spring的@Component.@Service.@Repository以及 @Controller等注解来实现bean托管给Spring容器管理.Spring是怎么样实 ...

  2. spring boot 中@Autowired注解无法自动注入的错误

    版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/huihuilovei/article/de ...

  3. Spring Boot中@Scheduled注解的使用方法

    Spring Boot中@Scheduled注解的使用方法 一.定时任务注解为@Scheduled,使用方式举例如下 //定义一个按时间执行的定时任务,在每天16:00执行一次. @Scheduled ...

  4. Spring Boot中的注解

    文章来源:http://www.tuicool.com/articles/bQnMra 在Spring Boot中几乎可以完全弃用xml配置文件,本文的主题是分析常用的注解. Spring最开始是为了 ...

  5. Spring Boot中自定义注解+AOP实现主备库切换

    摘要: 本篇文章的场景是做调度中心和监控中心时的需求,后端使用TDDL实现分表分库,需求:实现关键业务的查询监控,当用Mybatis查询数据时需要从主库切换到备库或者直接连到备库上查询,从而减小主库的 ...

  6. Spring Boot使用@ConfigurationProperties注解获取配置文件中的属性值

    注意:这种方式要提供属性的getter/setter方法—— 如果idea报错,提示没有相应的执行器,就需要在maven中添加: (虽然不配置代码也能正常运行,作用在下面会说明) 配置了该执行器后,在 ...

  7. 【Spring实战】Spring注解配置工作原理源码解析

    一.背景知识 在[Spring实战]Spring容器初始化完成后执行初始化数据方法一文中说要分析其实现原理,于是就从源码中寻找答案,看源码容易跑偏,因此应当有个主线,或者带着问题.目标去看,这样才能最 ...

  8. 【转】【Spring实战】Spring注解配置工作原理源码解析

    一.背景知识 在[Spring实战]Spring容器初始化完成后执行初始化数据方法一文中说要分析其实现原理,于是就从源码中寻找答案,看源码容易跑偏,因此应当有个主线,或者带着问题.目标去看,这样才能最 ...

  9. Spring Boot 中 @SpringBootApplication注解背后的三体结构探秘

    概 述 SpringBoot 约定大于配置 的功力让我们如沐春风,在我之前写的文章<从SpringBoot到SpringMVC> 也对比过 SpringBoot 和 SpringMVC 这 ...

随机推荐

  1. appium+python自动化项目实战(一):引入nose和allure框架

    本文将介绍一套比较完整的appium自动化框架,以python为编写脚本语言,是因为python有强大的库,同时易学易懂. 最终的测试框架代码,将在jenkins项目中一键构建,执行自动化测试用例,并 ...

  2. 单元测试之NUnit一

    NUnit 分三篇文章介绍,入门者可阅读文章,有基础者直接参考官方文档.初次写博客,望大家指点. 导航: 单元测试之NUnit一 单元测试之NUnit二 单元测试之NUnit三 NUnit是什么? N ...

  3. 三维动画形变算法(Linear rotation-invariant coordinates和As-Rigid-As-Possible)

    在三维网格形变算法中,个人比较喜欢下面两个算法,算法的效果都比较不错, 不同的是文章[Lipman et al. 2005]算法对控制点平移不太敏感.下面分别介绍这两个算法: 文章[Lipman et ...

  4. nanopi NEO2 学习笔记 3:python 安装 RPi.GPIO

    如果我要用python控制NEO2的各种引脚,i2c 或 spi ,RPi.GPIO模块是个非常好的选择 这个第三方模块是来自树莓派的,好像友善之臂的工程师稍作修改移植到了NEO2上,就放在 /roo ...

  5. python 06 深浅拷贝

    目录 1. 小数据池 1.1 代码块 1.2 小数据池 1.3 执行顺序 (代码块--小数据池) 1.4 "=="和 "is" 2. 深浅拷贝 2.1 赋值 2 ...

  6. Java异常机制及异常处理建议

    1.Java异常机制 异常指不期而至的各种状况,如:文件找不到.网络连接失败.非法参数等.异常是一个事件,它发生在程序运行期间,干扰了正常的指令流程.Java通过API中Throwable类的众多子类 ...

  7. WTM重磅更新,LayuiAdmin and more

    从善如登,从恶如崩.对于一个开发人员来说,那就是做一个好的系统不容易,想搞砸一个系统很简单,删库跑路会还不会么. 对于我们开源框架的作者来说,做一个好的框架就像登山(也许是登天),我们一步一步往上走, ...

  8. 洛谷 P1640 【连续攻击游戏】

    question bank :luogu question Number :1640 title :Continuous attacking game link :https://www.luogu. ...

  9. 【qt】【QString的诸多操作】

    前言: qt的数据处理莫过于QString,QString对于字符串的操作多的数不胜数.下面博主就将常用的罗列出来,一起分享. 正文: 下面的操作具体为:追加,查找,删除,提取,分割,各种转换等等. ...

  10. poj 2117 Electricity(tarjan求割点删掉之后的连通块数)

    题目链接:http://poj.org/problem?id=2117 题意:求删除一个点后,图中最多有多少个连通块. 题解:就是找一下割点,根节点的割点删掉后增加son-1(son为子树个数),非根 ...