SpringBoot执行流程梳理
书接上回,上回说到JarLaunch通过反射调用,进入到了我们的Spring项目main方法中。本节将会大致的梳理Spring启动的执行流程,并把我在阅读源码时碰到的问题做一个简要的记录。为后续引出我们常常叨叨的Spring自动配置打下基础。
文章目录
- SpringBoot执行流程梳理
- 关键流程
- 老天爷视角
- 一、SpringApplication对象创建
- 二、项目的启动前置处理
- 三、环境变量包装
- 四、banner打印
- 五、容器创建和初始化
- 六、容器内容填充
- 七、项目启动的后置处理
关键流程
老天爷视角
我把SpringBoot项目启动的流程按顺序主要分为了7块内容:
1、SpringApplication对象创建
2、项目启动的前置处理
3、环境变量包装
4、banner打印
5、容器创建和初始化
6、容器内容填充
7、项目启动的后置处理
接下来我将对这7块内容及其大致流程以及涉及到的一些作用做一个大致的介绍
一、SpringApplication对象创建
对象的创建对于我们的web项目来说,比较重要的内容:
1、红框里的设置应用类型,这关乎后面环境变量对象创建、容器创建的类型选择;
2、其次是黄框里的引导初始化器、容器初始化器以及监听器,他们会在后续的run方法中被调用;
我觉得这里比较有意思,也有必要一说的就是这些初始化器、监听事件以及后续的各种后处理器的加载。他们都是通过加载META-INF/spring.factories
文件,拿到对应的配置,然后通过反射获取到的对象,进而把他们加载到容器或相应对象当中的。
上图红框中的application就是我们创建的application对象,for循环里拿到的listener就是我们黄框里addListeners()添加的监听器。可以看到,当后续的run方法中的各种事件进行发布时,他就会被调用。
3、最后就是主类推断,目前我个人理解它的主要作用就是用于banner打印时把启动类打出来(此处我的理解大概率有误,如有请指出)。
二、项目的启动前置处理
接下来我们就进入到run方法里看看初始化在做的事情。
1.创建引导容器
图1-1
首先是createBootstrapContext()
,他的作用其实是呼应的application对象创建时的加载初始化器。他会在方法里for循环地调用初始化方法,并将引导容器返回。说起这个引导容器的作用,我的理解是我们的一些自定义拓展数据需要有一个集中存放的地方,而这个地方就是引导容器,他会把我们的数据一致缓存到容器对象创建完成,然后触发关闭事件。
2.创建事件监听器对象
再然后就是创建spring应用的事件监听器对象,该对象会在后续的各种事件发布时调用。
application对象创建时加载的监听器,跟这里的对象之间是存在关系的。当后者的监听事件被出发了,他会通过EventPublishingRunListener
通过application对象获取到并调用。我个人是这样理解的:前者是被加载进来的具体事件,后者是一个对象,在被调用时,可以触发前者里面的具体操作,后续我会通过一个例子做简单的介绍。其实我一开始看源码的时候都蒙了。
三、环境变量包装
首先会将我们通过java -jar启动时传递的参数作为args,包装成一个应用变量对象,该对象在环境变量包装以及后续的项目启动后处理阶段会在一个runner接口中被用到。需要提一嘴的是,这个args是作为优先级最高的环境变量而存在的,这也同步解释了java -jar 敲的参数为什么能起作用,而且能覆盖内部配置文件所设置的信息的原因。
然后就进入我们获取环境变量的方法里。
在这个方法中主要做的事情只有一个:将我们的系统环境变量、系统属性、yml配置文件等信息,通过KV的方式进行收集和包装,然后封装到一个环境变量对象当中返回,而这个对象是根据早期确定好的容器类型进行创建。
他的主要流程:
1、创建环境对象
2、将args添加进环境对象中
3、通过attach()方法对配置文件的key进行统一化,方便后续取值
4、触发environmentPrepared
事件,在这个事件里,会执行一个环境后处理器,取做一些环境变量信息的增强,例如让yml等配置文件得以被读取。有意思的是,这个触发后处理器的事件本身,就是在application对象创建时获取到的。稍微填一下创建对象时我挖的坑,红框里的key就对应着application对象创建时,setListeners()方法里加载的监听器事件。大家可以比对一下相应的代码。
后处理器对应的key
下方的红框跟上方的key是对应的
5、通过bindToSpringApplication()
将我们在配置文件中设置的“spring.main”打头的key中的值,赋值到我们的application对象对应成员变量当中
四、banner打印
光看名字,其实我当时是一脸懵的,但是我把下面的图放出来可能就恍然大悟了
我的理解,他主要就是把图标打印了出来,假如我们想打印自己的,那就重写他的方法就好,下面是他的具体代码
五、容器创建和初始化
1、容器创建
该方法主要是调用createApplicationContext()
方法,根据容器类型判断需要new出来的对象类型,然后返回
2、容器初始化
这个阶段主要的任务:
1、将前期创建的环境对象绑定到创建的容器中
2、调用创建application对象时加载的容器初始化器的初始化方法
3、发布容器就绪事件
4、发布引导容器关闭事件
5、添加一系列的后处理器(重点),并对一些额外添加进来的资源,按照来源顺序(Clazz、XML文件、包、CharSequence)先行加载bean定义
关键就是这个方法里创建的对象。一直沿着下面红框的对象进来
就会看到下图的这三个bean / bean工厂后处理器。这三个后处理器是自动配置和自动装配的关键
6、发布容器加载完成事件
六、容器内容填充
这个方法就是整个spring框架的关键。前面所有的对象创建、后处理器加载都是为了这一步的执行。
这一步做的事情主要是:
1、通过prepareRefresh()
对环境变量中的参数进行必要性校验,若缺少必要的参数就报错
2、通过prepareBeanFactory()
,给bean工厂增加一些额外配置:
- 对一些aware接口的自动装配行为进行忽略(这是个人理解,不一定对)
- 对个别接口实现类对应的bean进行指定(用于避免接口多实现时不知道应该添加那个bean到容器里的问题)
- 添加基础的bean后处理器,如后续会用到的
ApplicationListenerDetector
,它将在bean初始化后,通过对每个bean进行后处理,主要目的是判断当前bean是否是ApplicationListener的实现类对象,是就添加进事件监听器集合中,供后续的registerListeners()
使用。 - 将例如环境对象等基础对象直接加入到bean容器当中
3、执行我们期待已久的bean工厂后处理器,在这里面将会对容器初始化时加载的后处理器,然后通过加载的bean工厂后处理器,将更多的自定义后处理器加载进来,最终共同将项目中所有标记为需要被容器管理的bean的bean定义收集好并保存到beanDefinitionMap中。
接下来就是按实现了PriorityOrdered接口、Ordered接口以及其他,作为后处理器的执行顺序,进行自定义后处理器的执行。
4、添加spring框架自带的bean后处理器,并根据执行bean工厂后处理器阶段获取的bean定义,通过getBean()
获取所有bean后处理器对象,并加载到容器当中。
5、通过initMessageSource()
做一些类似国际化之类的处理。
6、初始化事件广播器。针对这个广播器跟前面的事件发布器区别的理解,我个人是这么看的,前者是我丢一个广播事件进来,然后谁要执行什么操作,我不关注;后者是我固定了几个动作,当我触发了特定动作后(比如环境就绪事件),所有实现了这个动作的监听器就都来执行。当然,从代码的逻辑来看,他们最后都是要到SimpleApplicationEventMulticaster#multicastEvent
中进行事件处理和广播。
7、执行onRefresh()
创建我们配置的web服务器,一般是默认的tomcat,假如不在配置文件中自定义连接数等配置项,将加载默认的配置。完成一系列的服务器初始化后,将调用start方法启动服务器。(看这块逻辑,一开始我被接口里的空方法给坑的一愣一愣的囧)
8、registerListeners()
将2.3步ApplicationListenerDetector
的后处理方法执行结果(即项目中的各种监听器bean),加入到刚才初始化完成的事件广播器当中。
9、通过大名鼎鼎的finishBeanFactoryInitialization()
,对bean工厂后处理器处理完的bean定义,进行单例bean的实例化操作,即反射创建对象或创建代理对象。在此期间还会基于各种aware接口、bean后处理器,对bean对象进行增强和操作,希望下下篇能记录下我对这块的学习笔记。
拿到bean定义后就开始循环获取bean
最后在对实现了SmartInitializingSingleton
接口的bean进行后置处理
10、刷新容器。主要的功能其实是初始化生命周期处理器,然后对实现了生命周期处理接口的bean进行start方法的循环执行。最后,借助第6步初始化的广播器,发布容器刷新完成事件。
七、项目启动的后置处理
对于这个阶段,我个人其实就是把他定性为给开发人员更多的拓展点去增强我们的项目内容。例如发布started事件、调用Runner接口的实现类进行相关拓展。
最后的最后,发布就绪事件。至此整的spring项目主要的启动流程就基本结束了。