我们在用SpringBoot进行项目开发的时候,基本上都使用过@ConfigurationProperties这个注解,我们在之前的文章中也说过ConfigurationPropertiesBindingPostProcessor会对标注@ConfigurationProperties注解的Bean进行属性值的配置,但是我们之前没有说ConfigurationPropertiesBindingPostProcessor这个Bean是什么时候注入到Spring容器中的。在Spring容器中如果没有这个Bean存在的话,那么通过@ConfigurationProperties注解对Bean属性值的配置行为将无从谈起。我们在本篇文章中将会说明SpringBoot在什么时机会将ConfigurationPropertiesBindingPostProcessor注入到SpringBoot容器中,但它不是我们这篇文章的主角,我们的主角是EnableConfigurationProperties注解。下面我们来请EnableConfigurationProperties登场。
关于EnableConfigurationProperties,在SpringBoot的注释中是这样说明的:为带有@ConfigurationProperties注解的Bean提供有效的支持。这个注解可以提供一种方便的方式来将带有@ConfigurationProperties注解的类注入为Spring容器的Bean。你之前可能听说过类似这样的说法@ConfigurationProperties注解可以生效是因为在SpringBoot中有一个类叫ConfigurationPropertiesAutoConfiguration,它为@ConfigurationProperties注解的解析提供了支持的工作,这种说法更准确一点的说法是在这个类上还存在了@Configuration和@EnableConfigurationProperties这两个注解!我们去ConfigurationPropertiesAutoConfiguration这个类中看一下这个类中的内容:

@Configuration
@EnableConfigurationProperties
public class ConfigurationPropertiesAutoConfiguration {}

我们会发现这个类没有任何内容,它其实只是一个标识性的类,用来标识ConfigurationProperties自动配置,重要的就是@Configuration和@EnableConfigurationProperties。这个类在SpringBoot中唯一被引用到的位置是在spring.factories中,

关于@EnableAutoConfiguration的内容我们先不展开,你现在先记着由于在@EnableAutoConfiguration中存在

@Import(EnableAutoConfigurationImportSelector.class)

Import这个注解,这个注解中又引入了EnableAutoConfigurationImportSelector这个类,而这个类会在某一个时机某一个地方(org.springframework.context.annotation.ConfigurationClassParser#processImports)被创建并调用它的selectImports方法,在它的selectImports方法中会获取到ConfigurationPropertiesAutoConfiguration这个类,

而在ConfigurationPropertiesAutoConfiguration这个类上存在@EnableConfigurationProperties这个注解,在EnableConfigurationProperties这个注解中又存在@Import这个注解,在这个注解中又引入了EnableConfigurationPropertiesImportSelector.class这个类,所以这个类同样会在某一个地方(org.springframework.context.annotation.ConfigurationClassParser#processImports)被实例化,然后调用它的selectImports方法。关于@Import这个注解中所引入的类可以分为三种类型,第一种是实现了ImportSelector接口的类,ImportSelector接口的实现类又可以分为两种,一种是直接实现ImportSelector接口的类,另一种是实现了DeferredImportSelector接口的类,DeferredImportSelector是ImportSelector接口的子类,也只有直接实现了ImportSelector接口的类,才会在ConfigurationClassParser#processImports中被调用它的selectImports方法;第二种是实现了ImportBeanDefinitionRegistrar接口的类,实现了ImportBeanDefinitionRegistrar接口的类将会调用它的registerBeanDefinitions方法,向Spring容器中注册Bean定义;最后一种是只要不属于上面那两种的都是第三种。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EnableConfigurationPropertiesImportSelector.class)
public @interface EnableConfigurationProperties {/*** Convenient way to quickly register {@link ConfigurationProperties} annotated beans* with Spring. Standard Spring Beans will also be scanned regardless of this value.* @return {@link ConfigurationProperties} annotated beans to register*/Class<?>[] value() default {};
}

我们在上面说了会调用EnableConfigurationPropertiesImportSelector这个类的selectImports方法,我们去看看这个方法的内容:

class EnableConfigurationPropertiesImportSelector implements ImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata metadata) {MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(EnableConfigurationProperties.class.getName(), false);Object[] type = attributes == null ? null: (Object[]) attributes.getFirst("value");if (type == null || type.length == 0) {return new String[] {ConfigurationPropertiesBindingPostProcessorRegistrar.class.getName() };}return new String[] { ConfigurationPropertiesBeanRegistrar.class.getName(),ConfigurationPropertiesBindingPostProcessorRegistrar.class.getName() };}
.........

在selectImports中会先通过

//false的意思是 不将 获取到的注解中的值转换为String
metadata.getAllAnnotationAttributes(EnableConfigurationProperties.class.getName(), false);

来获取类上的EnableConfigurationProperties注解中的value值,注意这里返回的值MultiValueMap,我们用过Map的都了解,通常Map中一个key对应一个value,而MultiValueMap是一个key对应多个value的map(底层是通过List搞定的)。这里为什么用MultiValueMap呢?因为我们的注解中的元数据可能是多个值的(好像一句废话。。。)。

//如果前面没有获取到EnableConfigurationProperties注解的话 则type为null,获取到EnableConfigurationProperties注解的则取value这个元数据的值
Object[] type = attributes == null ? null: (Object[]) attributes.getFirst("value");
//如果上一步获取到的type为null或者是一个空数组的话则返回ConfigurationPropertiesBindingPostProcessorRegistrar的全限定类名
if (type == null || type.length == 0) {return new String[] {ConfigurationPropertiesBindingPostProcessorRegistrar.class.getName() };
}
//如果在上一步中获取到了EnableConfigurationProperties注解中的value元数据的值,则返回ConfigurationPropertiesBeanRegistrar和ConfigurationPropertiesBindingPostProcessorRegistrar这两个类的全限定类名
return new String[] { ConfigurationPropertiesBeanRegistrar.class.getName(),ConfigurationPropertiesBindingPostProcessorRegistrar.class.getName() };

如果我们的类上只是标注了EnableConfigurationProperties注解,如ConfigurationPropertiesAutoConfiguration这个类的用法。那么就会返回ConfigurationPropertiesBindingPostProcessorRegistrar的全限定类名。按照我们之前说的,这个类将会在某一个地方(org.springframework.context.annotation.ConfigurationClassParser#processImports)被实例化,

public class ConfigurationPropertiesBindingPostProcessorRegistrarimplements ImportBeanDefinitionRegistrar 

请注意这个类是一个实现了ImportBeanDefinitionRegistrar接口的类,所以将会在某一个地方(org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitions)调用它的registerBeanDefinitions方法。我们先去看下这个类中registerBeanDefinitions方法的内容:

    @Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,BeanDefinitionRegistry registry) {//    BINDER_BEAN_NAME 的值是:' ConfigurationPropertiesBindingPostProcessor.class.getName()//如果在Spring Bean注册器中=不存在beanBame为org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor的Bean定义if (!registry.containsBeanDefinition(BINDER_BEAN_NAME)) {BeanDefinitionBuilder meta = BeanDefinitionBuilder.genericBeanDefinition(ConfigurationBeanFactoryMetaData.class);//ConfigurationPropertiesBindingPostProcessor bean定义的构造器BeanDefinitionBuilder bean = BeanDefinitionBuilder.genericBeanDefinition(ConfigurationPropertiesBindingPostProcessor.class);bean.addPropertyReference("beanMetaDataStore", METADATA_BEAN_NAME);//将beanName为org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor的bean定义放入到Springbean注册器//如果之前看过Spring的源码的话,看到这里你就会会心一笑了。//在这里就将我们开篇说的ConfigurationPropertiesBindingPostProcessor注入到Spring容器中了registry.registerBeanDefinition(BINDER_BEAN_NAME, bean.getBeanDefinition());registry.registerBeanDefinition(METADATA_BEAN_NAME, meta.getBeanDefinition());}}

我们再看如果我们的类上标注了@EnableConfigurationProperties注解,并在注解中value赋值的话,它会返回ConfigurationPropertiesBeanRegistrar 和ConfigurationPropertiesBindingPostProcessorRegistrar这两个类的全限定名,关于ConfigurationPropertiesBindingPostProcessorRegistrar我们已经说过了,下面说ConfigurationPropertiesBeanRegistrar 这个类。首先EnableConfigurationPropertiesImportSelector这个类是EnableConfigurationPropertiesImportSelector中的一个静态内部类,同样的它也实现了ImportBeanDefinitionRegistrar这个接口(ImportBeanDefinitionRegistrar这个接口注意是对注解的Bean定义进行处理)。

public static class ConfigurationPropertiesBeanRegistrarimplements ImportBeanDefinitionRegistrar
        @Overridepublic void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {//这里再次从metadata中获取EnableConfigurationProperties注解相关的东西//这里的metadata即是我们带有EnableConfigurationProperties类的Bean的元数据MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(EnableConfigurationProperties.class.getName(), false);//这个是用的attributes.get("value")获取value的值 而前面是用的attributes.getFirst("value")//将获取到的value转换为ListList<Class<?>> types = collectClasses(attributes.get("value"));for (Class<?> type : types) {//这里是获取EnableConfigurationProperties注解中value中的类的ConfigurationProperties的prefix//说的太绕了。。。。String prefix = extractPrefix(type);//如果ConfigurationProperties中的prefix有值的话,则将beanName拼接为prefix+"-"+类的全限定名String name = (StringUtils.hasText(prefix) ? prefix + "-" + type.getName(): type.getName());//如果Spring bean注册器中不存在这个beanName的bean定义的话,则注入到Spring bean注册器中if (!registry.containsBeanDefinition(name)) {registerBeanDefinition(registry, type, name);}}}private void registerBeanDefinition(BeanDefinitionRegistry registry,Class<?> type, String name) {//bean定义的构造器BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(type);//获取bean定义AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();//注入到Spring bean注册器中registry.registerBeanDefinition(name, beanDefinition);//获取EnableConfigurationProperties value引入的类上的ConfigurationProperties注解ConfigurationProperties properties = AnnotationUtils.findAnnotation(type,ConfigurationProperties.class);//如果EnableConfigurationProperties value引入的类上没有ConfigurationProperties注解的话则抛出异常//这个检测为什么不放到方法的开头处呢????Assert.notNull(properties,"No " + ConfigurationProperties.class.getSimpleName()+ " annotation found on  '" + type.getName() + "'.");}

通过这篇文章你应该了解了ConfigurationPropertiesAutoConfiguration这个类什么会使ConfigurationProperties注解生效了(因为这个类放到了spring.factories中,作为org.springframework.boot.autoconfigure.EnableAutoConfiguration的一个value值,在SpringBoot启动的时候会先被加载,确保能获取到ConfigurationPropertiesAutoConfiguration这个类,并解析它上面的注解!),也应该明白了@EnableConfigurationProperties这个注解的作用,同时也应该了解了ConfigurationPropertiesBindingPostProcessorRegistrar这个类是怎么被注入到Spring容器中的。下面以一个小例子结束这篇文章:

import com.zkn.springboot.analysis.domain.EnableConfigurationPropertiesDomain;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Component;/**** @author zkn* @date 2018/1/28*/
@Component
@EnableConfigurationProperties(value = EnableConfigurationPropertiesDomain.class)
public class EnableConfigurationPropertiesBean {}
/*** @author zkn* @date 2018/1/28*/
//这里只有ConfigurationProperties注解
@ConfigurationProperties(prefix = "configuration.properties")
public class EnableConfigurationPropertiesDomain implements Serializable {private static final long serialVersionUID = -3485524455817230192L;private String name;//省略getter setter@Overridepublic String toString() {return "EnableConfigurationPropertiesDomain{" +"name='" + name + '\'' +'}';}
}
configuration:properties:name: This is EnableConfigurationProperties!
    @Autowiredprivate EnableConfigurationPropertiesDomain propertiesDomain;@RequestMapping("index")public String index() {System.out.println(propertiesDomain);return "success";}

我们通过浏览器访问一下,输出结果如下:

SpringBoot之@EnableConfigurationProperties分析相关推荐

  1. SpringBoot源码分析(二)之自动装配demo

    SpringBoot源码分析(二)之自动装配demo 文章目录 SpringBoot源码分析(二)之自动装配demo 前言 一.创建RedissonTemplate的Maven服务 二.创建测试服务 ...

  2. SpringBoot源码分析之内置Servlet容器

    原文链接:http://fangjian0423.github.io/2017/05/22/springboot-embedded-servlet-container/ SpringBoot内置了Se ...

  3. SpringBoot常见功能特性分析

    SpringBoot常见功能特性分析: SpringBoot 应用启动入口 容器启动之后执行的操作 Profile环境配置 配置信息封装成实体类 定时任务 (1)SpringBoot应用启动入口 查看 ...

  4. SpringBoot启动流程分析(四):IoC容器的初始化过程

    SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...

  5. Springboot源码分析第一弹 - 自动装配实现

    Springboot就不用多了吧,解放Java开发双手的神器. 最显著的特点就是,去配置化,自动装配,自动配置.让开发人员只需要注重业务的开发 今天就来了解一下自动装配的源码是怎么实现的 预先准备 直 ...

  6. springboot启动原理分析

    目录 前言 起步依赖 自动配置 总结 前言 现如今我们使用java搭建工程的时候,使用过springboot的同学很清楚,有很多的默认配置springboot已经帮助我们配置好了,进一步的简化了我们的 ...

  7. springboot(四)——@EnableConfigurationProperties是如何起作用的你知道吗

    前言 用springboot开发的过程中,我们会用到@ConfigurationProperties注解,主要是用来把properties或者yml配置文件转化为bean来使用的,而@EnableCo ...

  8. springboot源码分析

    快速开发底层原理 SpringBoot核心理念 能够实现帮助开发者快速的整合第三方框架(Spring.Mybatis.hibernate) 原理:Maven依赖封装整合和自定义starter. 完全去 ...

  9. SpringBoot源码分析之@Scheduled

    Springboot写上注解@Scheduled就可以实现定时任务, 这里对其源码做一点分析 @Service public class MyScheduled {@Scheduled(cron=&q ...

最新文章

  1. 使用第三方SDK(如微信、qq、快看、头条等),调用接口405 Method Not Allowed
  2. JDK1.5 新特性
  3. spring计算方圆300km内其它城市(附完整代码)
  4. 如何搭建低延时、交互式的在线教育平台?(内附视频回放)
  5. android+建模工具,什么是适用于Android Studio的3D模型环境的最佳工具
  6. 为什么传值时加号变成了空格_URL的参数中有加号传值变为空格的问题(URL特殊字符)...
  7. 1014 福尔摩斯的约会 (20分)
  8. html5登录界面源代码_最新版本尤娜博客已更新源代码(速拿!!!)
  9. Apache+tomcat配置集群(1)
  10. python pySerial模块介绍
  11. 愿你不会像我一样,活成狗一般的生活!
  12. saveas中参数gcf的理解
  13. 问脉首创旁路云原生安全检测框架!
  14. 【SequoiaDB|巨杉数据库】HTAP混合负载Ⅰ
  15. 【论文笔记】:Enriched Feature Guided Refinement Network for Object Detection
  16. 伯禹公益AI《动手学深度学习PyTorch版》Task 06 学习笔记
  17. Linux c — void类型变量和指针
  18. 血氧仪方案功能及使用方法介绍
  19. 网络漫画书市场现状研究分析报告-
  20. MySQL 推荐书目

热门文章

  1. Javascript之旅——第一站:从变量说起
  2. 使用 TFDConnection 的 pooled 连接池
  3. session_id
  4. UITextField的详细使用
  5. ASP.NET2.0 GridView小技巧汇粹
  6. 从0开始搭建编程框架——主框架和源码
  7. C++11中weak_ptr的使用
  8. 【Qt】在QtCreator中编译log4cplus
  9. 【Qt】通过QtCreator源码学习Qt(一):pro文件
  10. 【Ubuntu】虚拟机VirtualBox安装win7完整步骤