Spring boot中BeanDefination扫描流程

本文目的

最近在看Spring boot中bean的初始化流程源码,其中有一些问题或想法特此记录一下。

一些个人的想法

网上有很多资料都详细的介绍了Spring boot启动的流程和bean初始化,许多大佬也是带着源码分析一步一步展示着,但是当我自己看spring boot启动源码时,会产生很多疑问,例如:扫描生成BeanDefination,何时扫描的?谁主导的扫描过程?扫描后有没有BeanFactoryPostProcessor去处理BeanDefination?有的话在哪里?可以自定义吗?

扫描流程

扫描流程开始于Spring boot启动阶段,在ApplicationContext.refresh方法中(这个方法非常重要,是Spring boot启动的核心,它包含了bean的扫描、初始化)。
在context的refresh方法中会调用PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors()),该方法是大部分bean扫描的的入口。接下来就是详细的扫描过程:

1.收集context中的BeanDefinitionRegistryPostProcessor和BeanFactoryPostProcessor。其实BeanDefination扫描过程,主要使用BeanDefinitionRegistryPostProcessor(从名字大概可以猜出来),至于BeanFactoryPostProcessor,Bean初始化流程中生成BeanDefination后会有一个观察点,BeanFactoryPostProcessor是这个观察点的观察者(意思是可以在这里魔改自己的BeanDefination, 后面会有示例)。
Spring boot中BeanDefination扫描流程_第1张图片
beanFacoryPostProcessors参数就是context中BeanFactoryPostProcessor。
2. 收集BeanFactory中的BeanDefinitionRegistryPostProcessor,且实现了PriorityOrdered接口。默认就一个org.springframework.context.annotation.internalConfigurationAnnotationProcessor,实体类是ConfigurationClassPostProcessor。
Spring boot中BeanDefination扫描流程_第2张图片
3. 利用2中收集的BeanDefinitionRegistryPostProcessor扫描各种@Configuration、@Bean、@Component。扫描过程比较复杂,分步骤讲。

a. 准备解析已经扫描得到的BeanDefination。解析之前会判断BeanDefination是不是有class类上@Configuration注解,或者以下4个注解:@Component、@ComponentScan、@Import、@ImportResource或者有@Bean注解的方法,有就解析它。最后筛出来结果其实就一个:就是你的启动类,含有@SpringBootApplication注解。事实上该类解析后,会把所有的这些注解里面的BeanDefination(或直接例如@Component,或间接例如@Import)
Spring boot中BeanDefination扫描流程_第3张图片
这里会判断是否符合扫描条件。
b. 使用ConfigurationClassParser.parse方法开始解析。这一步非常关键,会解析出大量的BeanDefination。
在这里插入图片描述
在这里插入图片描述
可以看出,扫描前在a中筛出来就一个:@SprinBootApplication。
在该步骤中,会真正去解析各种BeanDefination,包括@Component, @Service, @Configuration等等。基本分为以下几步:
1) 首先判断是否可以跳过解析,根据Condition判断,对,就是@ConditonOnMissingClass这些。
2) 看一下是否已经解析过了,如果解析过了,看有没有@Import,有就拉入import信息,没有就从已解析的Configurationclass池子里移除该类。
3) 不管2)怎么样,都会最终会把代用@Configuration注解的类作为sourceClass去解析。
4) 分别解析@ Component、@PropertySource、@ComponentScan、@Import、@ImportResource、@Bean方法。其中@Import只会解析出它包含的类,如ImportSelector,然后在最后的deferredImportSelectorHandler.process()处理ImportSelector里面逻辑。ImportSelector里面的逻辑过于复杂,而且有工具类的属性,所以最好去自己看源码(@Import注解是Spring boot能够自动化配置的关键,通过它解析出ImportSelector后,会通过SpringFactoriesLoader自动加载spring.factories文件中类,这里就是梦的开始)。
Spring boot中BeanDefination扫描流程_第4张图片
在4)步中,推荐自己去看源码,里面注释写的很好,会依次解析注解。而且最细节的解析过程就在里面,如果需要的话最好自己去看。

4.从前3步中扫描得到的BeanDefination中再拿出实现了Order的BeanDefinitionRegistryPostProcessor,然后重复第3步。
Spring boot中BeanDefination扫描流程_第5张图片
5.从前3、4步中扫描得到的BeanDefination中再拿出剩余BeanDefinitionRegistryPostProcessor(会对前面的去重),然后重复第3步。但是本人debug了一下,扫描解析过程主要在3步,用的是2步中ConfigurationClassPostProcessor。
Spring boot中BeanDefination扫描流程_第6张图片
6. 通过BeanDefinitionRegistryPostProcessor收集到了BeanDefination后,再从这些BeanDefination筛选出BeanFactoryPostProcessor,然后统一对BeanDefination进行处理。BeanFactoryPostProcessor的调用顺序和排序跟上面说的BeanDefinitionRegistryPostProcessor是一样的。顺序也是PriorityOrdered > Ordered > others。流程基本和上面的类似。

总结:大部分的BeanDefination扫描发生在contex.refresh()的invokeBeanFactoryPostProcessors阶段,BeanDefinitionRegistryPostProcessor承担扫描解析BeanDefination的任务,BeanFactoryPostProcessor承担BeanDefination的扫描后观察点的观察处理,这也是Bean初始化流程中第一个扩展点。其实在扫描过程中有很多细节代码,本人没有仔细的去研究是如何把BeanDefination解析出来的,如果需要魔改解析代码的话,可能要去仔细看源码。

写一个自己的BeanFactoryPostProcessor

这里需要说明的是,如果要魔改Spring boot,最好自己下一份Spring boot源码,导入自己的仓库,这样就可以随便玩了。
我们可以使用Spring boot源码中自带的sample,然后随便玩diy。选取spring-boot-sample-tomcat,新建一个类,PrintNameBeanFactoryPostProcessor,这个类只打印出BeanDefination的name和baenClassName。

@Component
public class PrintNameBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
     
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
     
		for (String name : beanFactory.getBeanDefinitionNames()) {
     
			BeanDefinition definition = beanFactory.getBeanDefinition(name);
			String className = definition.getBeanClassName();

			System.out.println(">>>>>>>>>>>>>>>>print BeanDefinitionName = " + name + " className=" + className);
		}
	}
}

根据上面的源码分析,我们可以知道,Spring boot会先自动扫描解析出我们的这个PrintNameBeanFactoryPostProcessor,然后在6步中,去处理所有的BeanDefination。运行结果:
Spring boot中BeanDefination扫描流程_第7张图片
这里结果没截完,太多了。但是注意有个问题,为什么有的BeanDefination的className是null,这个本人没有仔细去研究解析代码,但是如果需要的话去debug一下,应该可以找到答案。

总结

其实本文只讲了大概的流程,很多细节在Spring boot中的实现非常绕,封装很多层,需要自己去看,但是也大体上回答了开头心中的那些疑问,对于菜鸡如我,差不多够了,如果需要魔改更多的功能,可能就要去看细节代码了,但是至少也了解了大体的原理。

你可能感兴趣的:(spring源码,spring)