Spring源码——ConfigurationClassPostProcessor类

引言

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注解标注了的类,生成其对应的代理对象。

posted @ 2021-04-27 17:50  心若向阳花自开  阅读(557)  评论(0编辑  收藏  举报