引言

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. 龙蜥利器:系统运维工具 SysAK的云上应用性能诊断 | 龙蜥技术

    ​简介:本文从大量的性能诊断实践出发,来介绍 SysAK 在性能诊断上的方法论及相关工具. ​ 文/张毅:系统运维SIG核心成员.SysAK 项目负责人:毛文安:系统运维 SIG 负责人. 系统运维既 ...

  2. Ollama是什么,为什么这么好用

    Ollama 是一个开源框架,专为在本地机器上便捷部署和运行大型语言模型(LLM)而设计. 以下是其主要特点和功能概述: 1. 简化部署:Ollama 目标在于简化在 Docker 容器中部署大型语言 ...

  3. 自制一个发送验证码的10秒倒计时js效果

    <template>     <div class="conten1">         <input class="code"  ...

  4. Unity热更学习笔记--AB包的依赖 0.98

    AB包的依赖 接上一小结. 在这里我们新建一个红色材质球,赋值给Cube预制体.此时不对材质球进行AB包分类,再次进行打包.运行脚本,发现红色cube成功的从AB包中加载出来.尽管我们没有将cube所 ...

  5. Ruby on Rails Active Record数据库常用操作

    目录 创建 批量插入 判断是否存在 Ruby on Rails 日期查询方法 文档地址: https://freed.gitee.io/rails-guides/active_record_query ...

  6. 08 ES基本的聚合查询

    目录 按protocol聚合 指定地区,按port聚合 指定地区和时间段,按ip聚合(独立ip 即ip去重) 并且 聚合再求独立ip数 聚合后将聚合结果进行分页的解决办法 子聚合 按protocol聚 ...

  7. Golang validate验证器

    目录 自定义验证规 单条验证 多条批量验证 其它验证包: gookit/validate 手册地址: https://godoc.org/gopkg.in/go-playground/validato ...

  8. VueJS和Javascript实现文字上下滚动效果

    一提到文字上下滚动,我们就会想到用不同的程序去实现,而且页面中有文字滚动会增加这个网页的互动和可信度. 1.Js最简单的方法是控制盒子的高度,使不断的重复添加 <html> <bod ...

  9. uni.request二次封装

    点击查看代码 const baseUrl = 'https://your-api-base-url.com'; function getAuthToken() { // 获取token的逻辑,可能会从 ...

  10. 化繁为简|AIRIOT智慧水务信息化建设解决方案

    ​ "生产自动化,管理信息化"是现代化水厂建设的目标之一,需要在水质要求.工艺.生产.管理.环境等监测方面达到精细化管理标准,这是一个高度智能化,实现化繁为简智慧进阶的工程.传统水 ...