以debug形式了解SpringBoot启动自动配置的原理
在debug时,由于我们是对源码进行分析,因此时常会忘记自己走到哪一步(千万不要自信于自己的记性),尤其有时候我们对源码debug深度很深,再返回时一脸懵逼,因此我们最好用思维导图整理一下。后续我也会整理整合至本片文章中。
目录
1、创建SpringApplication对象
我们对主程序类先进行debug,来看看整个流程,之后step into
step into
step into:会发现返回了一个参数,里面new了一个SpringApplication(),即创建SpringApplication对象
这一步的启动流程:
- 创建SpringApplication对象
- 运行run方法
我们来看看是如何创建SpringApplication对象的:
从上一步我们force step into进去看看
进入后我们一路step over(被我们step over的这些步骤都会被系统赋上默认值)
走到setInitializers(),可以看出它主要是做赋值:
同时我们可以看到setInitializers是getSpringFactoriesInstances(),它是传送了一个ApplicationContextInitializer.class
getSpringFactoriesInstances()我们点进去看看,这里面有一个SpringFactoriesLoader.loadFactoryNames():
我们再点进loadFactoryNames看看:
其实也能看到FACTORIES_RESOURCE_LOCATION的值,这表示从这个路径下加载指定的文件
从这个路径找ApplicationContextInitializer,我们可以在debug过程中发现ApplicationContextInitializer有7个值
拿到Initializers后,进入到 setListeners():
我们发现这个方法里面的参数和上一行的形式一致,说明也得从SpringFactories中取值。我们也可以从依赖中看看:
我们依旧可以看看Listeners的值【需要在debug时进行赋值动作之后,才能看到值】
以下是旧版源码,因此有较大差异:
最后这一行,是用来决定哪个ApplicationClass是主程序类:
我们点deduceMainApplicationClass()进去看看:
它是从我们传入的所有配置类中,判断哪个有main方法
2、运行run方法
以上的流程是走完了SpringApplication(primarySources),接下来,我们将要走run(args)
我们继续step into,进入run(String... args)这个方法里面:
接下来我们看一些关键步骤。前两行是停止监听的操作,可以暂时不看。接下来是声明了一个IOC容器:
接下来是this.configureHeadlessProperty():
我们step into看看:
可以看出这里是做AWT应用的
我们step over一下,接下来是声明一个SpringApplicationRunListeners,这个监听器在前面的注意getRunListeners()
点击getRunListeners()进去看看:
这里面有个关键方法getSpringFactoriesInstances()
继续step over,可以查看这个listeners的值:
查看方式是右键:
接下来我们force step into
Step over我们开始进入到try体内。首先第一行是封装命令行参数:
下一行是准备环境,即ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments):
准备环境都准备一些什么呢?我们可以进去看看,这里我们force step into:
在这里,我们创建了一个“环境”,环境的值如果有就是get,如果没有就是create。所谓的“环境”其实是指创建IOC容器运行时所需要的环境。
我们可以在step over之后,即完成第一行代码后,可以在第二行查看environment的值:
接下来是环境配置,不是重点。下一步是创建环境完成后,回调SpringApplicationRunListener.environmentPrepared(),用以表示环境准备完成:
接下来一路放行,再次回到上一步,此时环境已经创建成功。下一步是在debug控制台打印springboot的banner,下图是执行之前的情况:
下图是执行后的情况:
下一步比较关键,是创建IOC容器。即创建ApplicationContext,它是用来决定创建web的ioc还是普通的ioc:
我们force step into进去看看
createApplicationContext()主要是利用switch-case语句来判断到底是创建web的IOC(又分为SERVLET还是REACTIVE类型)还是普通的IOC。最后,再利用BeanUtils的反射机制创建出IOC容器。继续放行,接下来的一步是做异常处理报告的,暂时不管。在旧版源码1.x中,这部分为
而在2.x版本里已经变为如下代码
下一步是准备上下文,我们进入一探究竟:
进入prepareContext()方法体中,其第一步是将我们之前创建的environment放入到IOC容器中:
第二步是IOC容器的后置处理:
再进入到其中,发现是注册一些小组件:
第三步关键,是this.applyInitializers(context):
它是干嘛的呢,我们进去看看:
第一步是获取到所有的Initializers,第二步就是调用initialize(),是否有想过,这些Initializers是哪里有的呢?实际上就是前文提到的在创建SpringApplication对象时,从类路径下找。
于是我们总结出来,applyInitializers()是回调之前保存的所有的ApplicationContextInitializer的initialize方法。
接下来我们step out,回到上一层:
走到这一步,我们发现还要回调所有的SpringApplicationRunListener的contextPrepared(),进入:
我们step out,继续走。下一步就是开始进行日志记录:
下一步是从IOC容器中取值:
这个beanFactory先把命令行参数对象applicationArguments注册进来,之后再把springBootBanner和allowBeanDefinitionOverriding注册进来。最后一步是判断这次的初始化是不是为“延迟初始化”,是的话,给上下文addBeanFactoryPostProcessor。不过我们可以看到这里为false:
接下来的一步就是获取主类:
当prepareContext()走到最后一步,所有的listeners开始回调contextLoaded()即说明我们的环境已经准备好了:
于是我们总结出: prepareContext运行完成以后回调所有的SpringApplicationRunListener的contextLoaded();
走完以上的流程,我们才把prepareContext()走完。
是否感觉整个流程是比较乱的呢?这是因为我们在debug时,进入源码的深度时深时浅,因此思维上容易乱掉,最好是画思维导图整理一下
此时我们再看看控制台,发现环境也已经准备好了
下一步就是刷新容器,对IOC容器初始化(如果是web应用还会创建嵌入式的Tomcat)
我们进入看看:
我们再进入到第一行的refresh()中
我们走到下一行后进入看看:
再进入其中:
再进入:(就是这么绕!)
这个就是我们的IOC容器的整个的初始化过程。接下来我们一路step out 跳回到refreshContext()之初
接下来一步就是afterRefresh(),我们进入看看:
其中有一个callRunners()方法,第2、3行分别是获取了服务器的ApplicationRunner和CommandLineRunner
最后再进行回调。而且注意,这里的两个Runner是有优先级的:ApplicationRunner先回调,CommandLineRunner再回调
于是我们总结出:
afterRefresh()是从ioc容器中获取所有的ApplicationRunner和CommandLineRunner进行回调。注意!不是从SpringFactory配置中获取的
接下来我们继续回到afterRefresh,走下一步listeners.started(context):
进入:
我们看到在这个方法里面,主要是调用SpringApplicationRunListener.最后我们让所有的SpringApplicationRunListener回调finished方法:
并且再返回这个IOC容器:
于是整个流程就结束了,我们对整个项目的debug也就结束了
3、总结
整个流程有2个核心:
一个是监听机制的使用,重要的有两个:
ApplicationContextInitializer和SpringApplicationRunListener,这两个是需要先配置在META-INF/spring.factories
还有另外两个:ApplicationRunner和CommandLineRunner,这两个只需要放在ioc容器中
第二个是 refreshContext(context),是用以扫描,创建,加载所有组件的地方(包括配置类,组件,自动配置)
4、关于后续
简单的启动过程中,就让人感叹自动配置的强大之所在。后续我也会对整个流程重新梳理整理成流程图放在本篇文章中,同时不断完善其中的诸多细节。