springboot的源码(spring)主要分为几个部分
项目概况如图:1、构造SpringApplication,完成spring.factories文件中Initializers与Listeners的加载
2、加载配置文件,通过ConfigFileApplicationListener
3、加载BeanDefinitionRegistryPostProcessor与BeanFactoryPostProcessor完成bean的定义包装(非生成实例)
4、生成bean实例以及初始化
本文主要针对第三点,解读bean的定义过程,该部分只是完成xml配置,或者注解的bean的相关属性解析,并没有生成相应的bean实例,后续第四点中将根据bean的定义获取实例。基于springboot2.1.4
项目地址:https://gitee.com/eshin/springbootdemo.git#autotest
- AutotestApplication为主类
- HelloController、HelloService为普通的注解类,HelloController注解@Import(HelloConfiguration3.class)
- HelloConfiguration是@Configuration标注的类,同时含有非静态内部类HelloConfigurationInner1和静态内部类HelloConfigurationInner10,配置在spring.factories中,包名与启动类不一致,不会被扫描到
- HelloConfiguration2是@Configuration标注的类,同时含有非静态内部类HelloConfigurationInner2和静态内部类HelloConfigurationInner20,并注解@Import(HelloConfiguration.class)
- HelloConfiguration3是@Configuration标注的类,同时含有非静态内部类HelloConfigurationInner3和静态内部类HelloConfigurationInner30
由于debug过程中有大量的bean的定义,设置断点的条件只debug自己代码中的bean的定义
部分参考如下:- eg1:(beanName != null) && (beanName.contains("autotestApplication") || beanName.contains("helloController") || beanName.contains("helloConfiguration") || beanName.contains("order")) || beanName.contains("getStr"))
- eg2:(configClass.getBeanName() != null) && (configClass.getBeanName().contains("autotestApplication") || configClass.getBeanName().contains("helloController") || configClass.getBeanName().contains("helloConfiguration") || configClass.getBeanName().contains("order")) || configClass.getBeanName().contains("getStr"))
启动执行线索:com.eshin.autotest.AutotestApplication#main---》org.springframework.boot.SpringApplication#run(java.lang.Class<?>, java.lang.String...)---》org.springframework.boot.SpringApplication#run(java.lang.String...)
一、创建上下文对象createApplicationContext();
在第一点构造SpringApplication,会通过org.springframework.boot.WebApplicationType#deduceFromClasspath设置应用类型
本次debug应用引入spring-boot-starter-web包,会被定义设置成WebApplicationType.SERVLET类型的应用,context为AnnotationConfigServletWebServerApplicationContext,构造改context对象时,主要生成AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner实例,同时创建DefaultListableBeanFactory实例
1.1 AnnotatedBeanDefinitionReader
1.2、ClassPathBeanDefinitionScanner
org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan,后续将使用该类的改方法进行扫描被注解的类和方法,解析成bean的定义注册到上下文中
二、prepareContext()
org.springframework.boot.SpringApplication#prepareContext---》org.springframework.boot.SpringApplication#load--》org.springframework.boot.BeanDefinitionLoader#load(java.lang.Object)
三、refreshContext()
org.springframework.context.support.AbstractApplicationContext#refresh
本文只关注与bean的定义相关的地方invokeBeanFactoryPostProcessors(beanFactory);
debug进入这个方法,直到org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors()
该方法会遍历1.1中已经定义的processor执行对应的postProcessBeanDefinitionRegistry方法,本文只关注org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry--》org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions
3.1 对configclass(各种注解生成bean的class)的解析
首个beanName autotestApplication ,既是main方法的classname
该bean的定义在二、中已完成定义,它是整个定义bean的过程的源头,解析configurationclass从它开始
进入parser.parse(candidates);
org.springframework.context.annotation.ConfigurationClassParser#parse(org.springframework.core.type.AnnotationMetadata, java.lang.String)---》org.springframework.context.annotation.ConfigurationClassParser#processConfigurationClass
图中1部分先放着,后面会讲到,先看2 doProcessConfigurationClass(configClass, sourceClass);
先整体看下这个方法
当某个配置类没有内部配置类、@ComponentScan注解,@Import注解、@ImportResource,那这个配置类在到执行该方法时是笔直通过的
、但当某个配置类含有内部配置类(eg:HelloConfigurationInner2)、有@ComponentScan注解(eg:AutotestApplication:@SpringBootApplication包含@ComponentScan)、有@import (eg:HelloConfiguration2、@EnableAutoConfiguration中的@Import(AutoConfigurationImportSelector.class))、有 @ImportResource,当存在这几种情况,便开始org.springframework.context.annotation.ConfigurationClassParser#processConfigurationClass方法的递归之路。
上面提到,首个进入该方法的configclass是autotestApplication,正是项目的启动类,在二、prepareContext()中已完成了定义注册
autotestApplication主要的注解
3.1.1、那首先是对autotestApplication的@ComponentScan注解的处理
3.1.1.1、条件注解处理,条件注解的实现类很多,此处仅以OnBeanCondition为例,不做详述,可尝试写自定义条件注解demo
注意:此处的条件注解只针对@configuration注解的类,作用在@bean上的条件注解在从configclass中定义@bean注解的方法时另外处理,请看3.2
3.1.1.2、扫描指定范围包并解析定义相关配置类
registerBeanDefinition最终执行到的org.springframework.beans.factory.support.DefaultListableBeanFactory#registerBeanDefinition,这个方法主要是将bean的定义放入beanDefinitionMap中,和(二、prepareContext())中对autoTestApplication定义时后面的处理是同一个地方。此时,静态内部类也会被扫描到并注册,但是非静态内部类不会被扫描到(有兴趣了解原因可以拉到最后看附录)
图中AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);会对@Scope的处理,如果ScopeProxy不是no或者default,会对其定义重新封装beanName变成下图所示
3.1.1.3、遍历2中注册的bean定义
继续执行parse方法,实则就是递归org.springframework.context.annotation.ConfigurationClassParser#processConfigurationClass方法,也就是说递归栈中,前一个(先进栈)configClass的@import @importresource,@bean 都要比后一个configClass的@import @importresource,@bean,要后处理,即autotestApplication中的@import @importresource,@bean要在扫描到的configClass都处理完之后,才会处理。例如,此demo中HelloConfiguration2中的@Import(HelloConfiguration.class)处理要比EnableAutoConfiguration中的@Import(AutoConfigurationImportSelector.class)(属于configClass:autotestApplication)先处理。
在org.springframework.context.annotation.ConfigurationClassParser#processConfigurationClass的最后一行this.configurationClasses.put(configClass, configClass);
中,configurationClasses是个LinkHashMap,是有顺序的,先put进来的configClass优先被解析其中配置的bean。完整map如下图
如图:以HelloConfiguration2为例,首先先处理了她的内部类(被扫描到),其次处理他的import操作,因此HelloConfiguration2本身put进configurationClasses中的顺序比她的内部类和import的类要后。
在处理configclass:HelloConfiguration2,又通过processMemberClasses()处理内部类,这个方法最终也是调用processConfigurationClass方法递归,此时,处理内部类不会删除原先的而重新put到configurationClasses,而是被标记为
importedBy HelloConfiguration2。(通过该方法处理的内部类都会被标记importedBy)
processMemberClasses中会使用到importStack用于防止循环导入,如果,在内部类中@import了某个类,这个类又@import了HelloConfiguration2,就会有循环处理。同时,在debug过程中,通过这个importStack也可以查看当前递归的深度及所处位置。
HelloConfiguration2同时使用了@Import注解,后面和autotestApplication的放一起查看源码,其他的以此类推。
同理,由于autotestApplication是递归的最开端,那么在整个scan的过程中,是最晚放入到configurationClasses中的
3.1.2、处理@Import processImports()
图中三种import类型的来源- @SpringBootApplication-->@EnableAutoConfiguration-->@AutoConfigurationPackage-->@Import(AutoConfigurationPackages.Registrar.class)
- @SpringBootApplication-->@EnableAutoConfiguration-->@Import(AutoConfigurationImportSelector.class)
- @EnableAsync-->@Import(AsyncConfigurationSelector.class)
另外还有一种就是HelloConfiguration2中直接import的configuration类@Import(HelloConfiguration.class)分别对应不同的处理方式,主要关注
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector)如何加载配置在配置文件中的配置类
此时,不管在何种情况下执行到handler.processGroupImports() 都表明,ComponentScan的部分已经处理完了。如上图,现在主要关注如何获取spring.factories中配置的配置类
group.getImports()由此可以看出,只有当使用org.springframework.boot.autoconfigure.AutoConfigurationImportSelector对spring.factories进行加载时,才有对加载出来的configClass进行排序,而ComponentScan扫描出来的并不参与此次排序
这也就是为何在demo的代码中配置的@AutoConfigureBefore和@AutoConfigureAfter并不会生效
processImports()方法,之前已经解读过了,但是还有个细节没有说到,在demo中,即在HelloConfiguration2中@Import(HelloConfiguration.class),又在spring.factories中配置了HelloConfiguration,那会不会重复加载呢?答案是不会的
processImports()-->org.springframework.context.annotation.ConfigurationClassParser#processConfigurationClass通过这段代码可知,当在configurationClasses中已经存在某个配置类时,如果是通过@import再次加载该类,就并不会再次加载,只是在importedBy列表中添加第二次import时使用@import的类
如图但是如果demo中@import的类又被扫描到的话则扫描的会覆盖@import的,所以通过@import可以被扫描的配置类来打到改变configclass的顺序是无法实现的
,例如本demo中,HelloConfiguration3,包含静态内部类和非静态内部类,HelloConfiguration3本身和其静态内部类都是会被@ComponentScan扫描到的,而非静态内部类则不会
那么,如果通过@import(HelloConfiguration3.class)可以改变非静态内部类在configurationclass列表中的位置,而对HelloConfiguration3本身和其静态内部类则无效
至此,configurationClasses中就包含了所有需要解析包含bean配置的configclass了,但还缺少一个beanMethods设置的说明每个configclass在org.springframework.context.annotation.ConfigurationClassParser#processConfigurationClass解析的时候都会将其中用@Bean注解的方法解析添加到beanMethods列表中
至此org.springframework.context.annotation.ConfigurationClassParser#parse(java.util.Set<org.springframework.beans.factory.config.BeanDefinitionHolder>)的解析过程就完成了。整个parse过程,就是按顺序往configurationClass列表中添加扫描到的和import的配置类。由此图可以,configurationClass中,通过spring.factories加载的是有排序过的,但是通过ComponentScan扫描的是没有排序的,但是有时候希望通过ComponentScan的也有排序,那可以通过内部类的配置或者import含有非静态内部类(配置类)的方法
=====================================================================================》
3.2、从configClasses中loadBeanDefinitions
进入this.reader.loadBeanDefinitions(configClasses);此时,有对@Scope的处理,如果ScopeProxy不是no或者default,会对其定义重新封装,封装后注册的definition如下图
在org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForBeanMethod方法中也有对@bean的条件注解:**
org.springframework.boot.autoconfigure.condition.OnBeanCondition#getMatchOutcome-->org.springframework.boot.autoconfigure.condition.OnBeanCondition#getMatchingBeans-->所以bean定义的顺序对@Bean上的onBeanConditional方面的条件注解会有影响,例如,在应用代码中,想要通过spring.factories中的bean作为条件注解判断条件,这时候是会失效的,因为此时,bean的定义中还没有spring.factories中的相关bean的定义,通过spring.factories加载到的configclass,在configurationClass有序集合中是排在比较后面的。
- 解析完configClass之后回到org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors()
继续调用其他的BeanFactoryPostProcessors对已经加载的beanDefinition进行包装修改,例如org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanFactory该方法会将所有的@Configuration注解的beanDefinition用enhancer包装一层代理
最开始传入的beanFactoryPostProcessor都是通过new出来的实例然后添加到org.springframework.context.support.AbstractApplicationContext#beanFactoryPostProcessors列表中的,后面的beanFactoryPostProcessor是通过从beanDefinitionNames查找到的,有各自的顺序,具体顺序代码注释有说明可参考此处
至此,bean的定义就基本完成了。
最终在org.springframework.context.support.AbstractApplicationContext#refresh-->org.springframework.context.support.AbstractApplicationContext#finishBeanFactoryInitialization--->org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons会按在beanDefinitionNames中的顺序实例化(如果有被autowired的bean定义会被提前实例化)实例化的流程将另外篇幅解读。beanDefinitionNames是一个有序列表,定义的bean的beanName按定义的顺序放入该列表
在org.springframework.context.support.AbstractApplicationContext#refresh的invokeBeanFactoryPostProcessors(beanFactory)与registerBeanPostProcessors(beanFactory)中会对已经定义的BeanFactoryPostProcessors实现类和BeanPostProcessors实现类的beandefinition提前实例化
总结:
- 1、AutotestApplication即启动类时最先被定义的,在实例化时,是除部分processor外最先实例化的,最早执行@postConstruct方法
- 2、@ComponentScan扫描到的configClass,要比import spring.factories的configclass先放入configurationClass列表,也就是解析的时候要先解析扫描到的configclass,从中解析@Bean方法注册beanDefinition, 静态内部类会被扫描到并注册beanDefinition,但是非静态内部类不会,静态内部类会比外部类更早扫到。
- 3、@ComponentScan扫描到的configClass,在被扫描到同时会吧整个configclass当成一个bean注册beanDefinition,@import的configclass则不会马上注册beanDefinition,要到将对configClass中的@Bean方法进行注册beanDefinition的时候才先进行configClass的beanDefinition注册。
(org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForConfigurationClass方法中注册beanDefinition)
- 4、@AutoConfigureBefore和@AutoConfigureAfter只有在使用org.springframework.boot.autoconfigure.AutoConfigurationImportSelector对spring.factories进行加载时,才有对加载出来的configClass进行排序,而ComponentScan扫描出来的并不参与排序,那么configurationClass中,通过spring.factories加载的是有排序过的,但是通过ComponentScan扫描的是没有排序的,但是有时候希望通过@ComponentScan的扫描到的configClass也有排序,那可以通过内部类的配置的方法改变一定的顺序,因为会优先处理内部类,不管静态非静态
- 5、如果在应用代码中import了某个配置类1,在spring.factories又配置了该配置类1,那么该配置类1是在扫描过程中,在扫描到使用了该import的配置类0,在解析时,首先将配置类1加入到configurationClass列表中,@import再次加载该配置类,就并不会再次加载,只是在importedBy列表中添加第二次import时使用@import的类
- 6、@import的类又被扫描到的话则扫描的会覆盖@import的,所以通过@import能够被扫描的配置类来打到改变configclass的顺序是无法实现的,由于静态内部类会被扫描到并注册beanDefinition,但是非静态内部类不会,那么可以通过import包含非静态内部类的配置类来达到改变顺序的效果。所有要特别注意引入的jar包的包名会不会跟应用的包名一样,尤其在同一个公司中,包名不能只到机构位置,eg:com.eshin不是一个好的包名,要到com.eshin.autotest,尽可能避免引入的jar包的配置类被扫描到
- 7、在应用代码中,想要通过spring.factories中的bean作为条件注解判断条件,这时候是会失效的,因为此时,bean的定义中还没有spring.factories中的相关bean的定义,通过spring.factories加载到的configclass,在configurationClass有序集合中是排在比较后面的。此时,若想提前加载spring.factories中某个configclass,可以在应用代码上import某个jar包中需要提前的configClass,但是要小心,提前的这个configClass,是否有依赖jar包中其他的bean的条件注解,或者是否有@AutoConfigureBefore和@AutoConfigureAfter,提前后会失效。
因此:bean定义顺序(即实例化顺序):
1、如果一个configClass被扫描到的话,先定义其被扫描的静态内部类,然后是它本身,接着是它的内部类(由于静态内部类已提前定义,此处可看成仅仅是非静态内部类),然后是import的配置类的定义,然后才是bean方法的定义,类的bean定义,总是先于bean方法的bean定义。
2、如果一个configClass是被import的,那首先是定义它的内部类,不管是静态非静态,然后是内部类的bean方法,最后才是它本身,及其bean方法。但是实例化时,实例化非静态内部类前需要先实例化外部类
3、如果一个configClass又被提前import(使用import的类,比这个configClass先扫描到,eg:hellocontroller比helloconfiguration3先执行),又是扫描的到,那就先定义被扫描到的静态内部类,它本身,然后是非静态内部类,及其bean方法,然后是静态内部类的bean方法,最后才是他本身的bean方法
4、最后实例化就按上述的顺序实例化,但是如果有前面的bean实例化过程中需要autowired后面的额bean,那后面的bean会被提前实例化,如果bean的定义有lazy属性为true,则延后到调用时才实例化
只要理解了configclass递归放入configurationClass列表中的顺序逻辑,就很好理解上面的定义顺序。
验证:
根据configurationClass列表和beanDefinitionMap(上面有关于configurationClass列表的图)1-7都是扫描后定义的;8-9能够提前是因为helloController的import,同时是非静态内部类;按configurationClass列表的顺序,15应当在16-17之后的,但是由于在实例化非静态内部类前,需要先实例化外部类,因此15提前。其余的都是按照configurationClass列表中的configClass,定义bean方法的顺序
附:静态内部类和非静态内部类扫描
org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#scanCandidateComponents-->org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#isCandidateComponent(org.springframework.beans.factory.annotation.AnnotatedBeanDefinition)
inner2是非静态的,independentInnerClass属性为false,扫描的时候略过,inner20是静态的,independentInnerClass属性true,会扫描到