引言

Spring容器中提供很多方便的注解供我们在工作中使用,比如@Configuration注解,里面可以在方法上定义@Bean注解,将调用方法返回的对象交由Bean容器进行管理,那么Spring框架是如何处理@Configuration注解的呢

源码

/**
* 此类是一个后置处理器的类,主要功能是参与BeanFactory的建造,主要功能如下
* 1、解析加了@Configuration的配置类
* 2、解析@ComponentScan扫描的包
* 3、解析@ComponentScans扫描的包
* 4、解析@Import注解
*
* {@link BeanFactoryPostProcessor} used for bootstrapping processing of
* {@link Configuration @Configuration} classes.
*
* <p>Registered by default when using {@code <context:annotation-config/>} or
* {@code <context:component-scan/>}. Otherwise, may be declared manually as
* with any other BeanFactoryPostProcessor.
*
* <p>This post processor is priority-ordered as it is important that any
* {@link Bean} methods declared in {@code @Configuration} classes have
* their corresponding bean definitions registered before any other
* {@link BeanFactoryPostProcessor} executes.
*
* @author Chris Beams
* @author Juergen Hoeller
* @author Phillip Webb
* @since 3.0
*/
public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor,
PriorityOrdered, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {

通过阅读源码我们可以得知 ConfigurationClassPostProcessor 属于BFPP中的BDRPP,关于BFPP和BDRPP,请参考另一篇Spring扩展——BeanFactoryPostProcessor(BFPP)

其类结构图如下

通过对BFPP或BDRPP的理解,得出ConfigurationClassPostProcessor会在Spring容器启动流程中invokeBeanFactoryPostProcessors(beanFactory)方法执行过程中被调用,先会执行其postProcessBeanDefinitionRegistry(registry)方法,引入其它Bean定义信息,再执行BFPP的postProcessBeanFactory(beanFactory)方法。

ConfigurationClassPostProcessor作为Spring框架的内部BFPP/BDRPP,我们先看一下,它在什么时候什么情况下,会被自动加入到Spring容器中?

Spring创建ConfigurationClassPostProcessor

  1. 对于ClassPathXmlApplicationContext,它会在loadBeanDefinitions——》解析XML文档标签context:component-scan——》调用相应的Handler进行parse——通过AnnotationConfigUtils.registerAnnotationConfigProcessors()静态方法将ConfigurationClassPostProcessor加入到容器中。

  2. 对于ClassPathXmlApplicationContext,在解析context:annotation-config</context:annotation-config>的时候,也会通过AnnotationConfigUtils.registerAnnotationConfigProcessors将其加入到容器中来

  3. 对于AnnotationConfigApplicationContext,在新建的时候,会初始化内部的AnnotatedBeanDefinitionReader成员变量,在创建AnnotatedBeanDefinitionReader的时候,就会调用AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry)将ConfigurationClassPostProcessor加入到容器中来。

综上所述,在XML文件中使用component-scan 和 context:annotation-config 标签的时候会将ConfigurationClassPostProcessor加入到Spring容器中来,在基于纯属注解的方式,默认的情况下,在初始化上下文环境的时候,也会将其加入进来。

在Spring启动过程中,在率先执行BFPP的相关方法,而执行的时候,会先执行BDRPP的相关方法,因为可以发现更多的其它的BDRPP,保证容器中内部的 和 自定义的所有BFPP或BDRPP都能够得到执行。

执行postProcessBeanDefinitionRegistry()



在这个方法中处理流程比较复杂,经过了多次递归调用

对于每一个被Configuration注解标注的类而言,它可能被标注其它如ComponentScan或ComponentScans或Import注解,而这些注解的功能,是引入更多的Bean定义信息,而它引入的Bean也有可能被标注如Configuration、ComponentScan、ComponentScans、Import等注解,所以,这个地方为保证所有定义的Bean定义信息能够被识别到,就需要进行递归的调用。

在进入到parse后

1、处理@PropertySources注解,配置信息的解析

2、处理@ComponentScan注解:使用ComponentScanAnnotationParser扫描basePackage下的需要解析的类,并注册到BeanFactory中(这个时候bean并没有进行实例化,而是进行了注册。具体的实例化在finishBeanFactoryInitialization方法中执行)。对于扫描出来的类,递归解析

3、处理@Import注解:先递归找出所有的注解,然后再过滤出只有@Import注解的类,得到@Import注解的值。比如查找@SpringBootApplication注解的@Import注解数据的话,首先发现@SpringBootApplication不是一个@Import注解,然后递归调用修饰了@SpringBootApplication的注解,发现有个@EnableAutoConfiguration注解,再次递归发现被@Import(EnableAutoConfigurationImportSelector.class)修饰,还有@AutoConfigurationPackage注解修饰,再次递归@AutoConfigurationPackage注解,发现被@Import(AutoConfigurationPackages.Registrar.class)注解修饰,所以@SpringBootApplication注解对应的@Import注解有2个,分别是@Import(AutoConfigurationPackages.Registrar.class)和@Import(EnableAutoConfigurationImportSelector.class)。找出所有的@Import注解之后,开始处理逻辑:

   (1)、遍历这些@Import注解内部的属性类集合

   (2)、如果这个类是个ImportSelector接口的实现类,实例化这个ImportSelector,如果这个类也是DeferredImportSelector接口的实现类,那么加入ConfigurationClassParser的deferredImportSelectors属性中让第6步处理。否则调用ImportSelector的selectImports方法得到需要Import的类,然后对这些类递归做@Import注解的处理

   (3)、如果这个类是ImportBeanDefinitionRegistrar接口的实现类,设置到配置类ConfigurationClass的importBeanDefinitionRegistrars属性中

  

   (4)、其它情况下把这个类入队到ConfigurationClassParser的importStack(队列)属性中,然后把这个类当成是@Configuration注解修饰的类递归重头开始解析这个类

4、处理@ImportResource注解:获取@ImportResource注解的locations属性,得到资源文件的地址信息。然后遍历这些资源文件并把它们添加到配置类的importedResources属性中

5、处理@Bean注解:获取被@Bean注解修饰的方法,然后添加到配置类的beanMethods属性中

6、处理DeferredImportSelector:处理第3步@Import注解产生的DeferredImportSelector,进行selectImports方法的调用找出需要import的类,然后再调用第3步相同的处理逻辑处理

在以上步骤中,所解析到的注解

@Configuration

首先,他会获取@Configuration注解,他实际上是继承了@Component。然后再解析@Configuration类中的其他注解。所以我们经常在类上写的@Configuration之所以会注入到容器中,就是这里被解析的。

解析的过程是在ConfigurationClassParser的parser方法中,解析的结果存入BeanDefinition。parser最后调用比较重要的方法是doProcessConfigurationClass。

@Conditional

doProcessConfigurationClass方法是在processConfigurationClass方法中调用的,processConfigurationClass方法中有一个比较重要的注解判断,@Conditional,用于判断是否存入BeanDefinition。我们常用的@ConditionalOnBean、@ConditionalOnMissingBean、@ConditionalOnClass、@ConditionalOnMissingClass、@Conditional等就是ConditionEvaluator#shouldSkip方法来判断的,如果符合条件,才可以解析下面的几个注解。

@PropertySource

当需要引入资源配置文件的时候,经常用以下的写法,它能被注入到各个属性,就是在doProcessConfigurationClass这个方法中实现的。

@Configuration
@PropertySource("classpath:xxx.properties")
public class XXXConfig { }

@ComponentScans和@ComponentScan

这两个注解的basePackages下面的类,就是这里在这里扫描,由于可能扫描的类中,也有这两个注解,所以这个方法里会通过递归调用parse方法。

@Import

Import注解可以引入普通类,也可以引入ImportSelector接口的类,也可以引入ImportBeanDefinitionRegistrar接口的类

解析的时候,首先是解析ImportSelector接口,然后是ImportBeanDefinitionRegistrar接口,最后是普通类。

@Bean

@Bean会在解析完Configuration注解后,将解析其中被@Bean注解标注了的方法,通过方法也会返回一个Bean定义信息。

执行postProcessBeanFactory()

方法源码如下

	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
int factoryId = System.identityHashCode(beanFactory);
if (this.factoriesPostProcessed.contains(factoryId)) {
throw new IllegalStateException(
"postProcessBeanFactory already called on this post-processor against " + beanFactory);
}
this.factoriesPostProcessed.add(factoryId);
if (!this.registriesPostProcessed.contains(factoryId)) {
// BeanDefinitionRegistryPostProcessor hook apparently not supported...
// Simply call processConfigurationClasses lazily at this point then.
processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
} //对@Configuration标注了的类,进行动态代理
enhanceConfigurationClasses(beanFactory);
beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
}

通过postProcessBeanFactory这个方法,会对容器中所有的@Configuration注解标注了的类,进行动态代理。

大家可能会问,这个地方为什么需要动态代理?

首先我们@Configuration注解标注了的类,本身也是作为一个bean对象注册到容器中的,通过这个对象,我们可以调用被@Bean注解了的方法,如果不做动态代理的话,当调用@Bean注解了的方法时,会直接运行方法体内容,并返回一个对象。Spring为我们考虑得非常周到,当通过动态代理的方式去调用@Bean注解标注了的方法时,就会从容器中获取相应的Bean,确保Spring容器中的单例bean对象的单例性。所以在ConfigurationClassPostProcessor类中,这个方法最核心的作用就是为所有的Conguration注解标注了的类,生成其对应的代理对象。

Spring源码——ConfigurationClassPostProcessor类的更多相关文章

  1. spring源码 RootBeanDefinition类的根接口AttributeAccessor

    /** * Interface defining a generic contract for attaching and accessing metadata * to/from arbitrary ...

  2. Spring源码解析之ConfigurationClassPostProcessor(二)

    上一个章节,笔者向大家介绍了spring是如何来过滤配置类的,下面我们来看看在过滤出配置类后,spring是如何来解析配置类的.首先过滤出来的配置类会存放在configCandidates列表, 在代 ...

  3. Spring源码分析——BeanFactory体系之抽象类、类分析(二)

    上一篇分析了BeanFactory体系的2个类,SimpleAliasRegistry和DefaultSingletonBeanRegistry——Spring源码分析——BeanFactory体系之 ...

  4. Spring源码分析——BeanFactory体系之抽象类、类分析(一)

    上一篇介绍了BeanFactory体系的所有接口——Spring源码分析——BeanFactory体系之接口详细分析,本篇就接着介绍BeanFactory体系的抽象类和接口. 一.BeanFactor ...

  5. Spring源码分析——资源访问利器Resource之实现类分析

    今天来分析Spring的资源接口Resource的各个实现类.关于它的接口和抽象类,参见上一篇博文——Spring源码分析——资源访问利器Resource之接口和抽象类分析 一.文件系统资源 File ...

  6. Spring源码情操陶冶-AOP之Advice通知类解析与使用

    阅读本文请先稍微浏览下上篇文章Spring源码情操陶冶-AOP之ConfigBeanDefinitionParser解析器,本文则对aop模式的通知类作简单的分析 入口 根据前文讲解,我们知道通知类的 ...

  7. spring源码分析系列 (8) FactoryBean工厂类机制

    更多文章点击--spring源码分析系列 1.FactoryBean设计目的以及使用 2.FactoryBean工厂类机制运行机制分析 1.FactoryBean设计目的以及使用 FactoryBea ...

  8. spring源码分析系列 (5) spring BeanFactoryPostProcessor拓展类PropertyPlaceholderConfigurer、PropertySourcesPlaceholderConfigurer解析

    更多文章点击--spring源码分析系列 主要分析内容: 1.拓展类简述: 拓展类使用demo和自定义替换符号 2.继承图UML解析和源码分析 (源码基于spring 5.1.3.RELEASE分析) ...

  9. Spring源码分析(三)容器核心类

    摘要:本文结合<Spring源码深度解析>来分析Spring 5.0.6版本的源代码.若有描述错误之处,欢迎指正. 在上一篇文章中,我们熟悉了容器的基本用法.在这一篇,我们开始分析Spri ...

  10. Spring源码解读:核心类DefaultListableBeanFactory的继承体系

    1 简介 我们常用的ClassPathXmlApplicationContext是AbstractRefreshableApplicationContext的子类,而DefaultListableBe ...

随机推荐

  1. 全方位事件监控管理,阿里云日志服务Kubernetes事件中心正式上线

    2020年2月21日,阿里云日志服务Kubernetes事件中心正式上线,为Kubernetes事件提供集中化采集.存储.分析.可视化.告警等能力,帮助Kubernetes使用者快速构建准实时.高可靠 ...

  2. [FAQ] Sortable 拖拽组件, 火狐浏览器中打开新窗口问题

      Q:用了 sortable 组件,在火狐浏览器中进行拖拽时,会打开新窗口 ? Sortable组件地址,https://github.com/SortableJS/Sortable 当前处理方式 ...

  3. [FAQ] web3js, Error: [number-to-bn] while converting number 0.1 to BN.js instance, error: invalid number value

    我们在调用合约方法时,都可以传一些参数的,比如转账金额 value. value 的单位是 wei,这是一个很小的单位,所以一般数值很大. 如果误把 ether 当成 wei 传参,就会报标题中的错误 ...

  4. 对C语言符号的一些冷门知识运用的剖析和总结

    符号 目录 符号 注释 奇怪的注释 C风格的注释无法嵌套 一些特殊的注释 注释的规则建议 反斜杠'\' 反斜杠有续行的作用,但要注意续行后不能添加空格 回车也能起到换行的作用,那续行符的意义在哪? 反 ...

  5. 经验之谈:我为什么选择了这样一个激进的缓存大Key治理方案

    一.引言 本文将结合我的一次Redis大Key的治理经验,来浅谈一下缓存大Key的治理方案选择.文中主要包括缓存大Key基础知识.大Key治理方案选择.大Key治理案例等,适合有一定开发经验的开发者阅 ...

  6. 【人脸识别】OpenCV获取自己的图像

    思路:先获取10000张自己的图像,然后通过CNN神经网络进行学习. 第一步:先获取自己的脸的数据.如何做? 代码如下: import cv2 import os import sys import ...

  7. Notion API中Internal Notion integrations和Public Notion integrations的区别

    Internal Notion integrations Internal Notion integrations与一个单一的.特定的工作区相联系,只有该工作区的成员可以使用这个integration ...

  8. Linux中典型的文件权限问题

    总结起来说,可以打个比方,目录就像一间上了锁有窗户的屋子.如果你只想看屋子里面有啥,那么只要拥有r权限,不必进入到屋子,透过屋子的窗户就能看到里面的东西:但是如果你想改变屋子里面的物件,或者从屋子里面 ...

  9. FileInputStream和FileOutputStream

    FileInputstream 字节输入流 用于文件内容的读取操作. 创建FileInputstream对象用于读取文件内容,使用后需要进行关闭操作 常用方法: read(); //每次仅读取一个字节 ...

  10. JDK源码阅读-------自学笔记(十七)(java.io.File类)

    File类简介 java.io.File类:抽象代表文件和目录. 使用此类,相当于获取了系统的文件,可以对其进行操作. 在开发中,读取文件.生成文件.删除文件.修改文件的属性时经常会用到本类 File ...