SpringBoot 源码
接上回, 这篇文章继续解读 SpringApplication 实例的 run 方法.
⚠️注意,不同版本的 SpringBoot 的源码行数可能不同, 这篇文章使用的是 2.4.1 版本
SpringApplication 实例的 run 方法
方法参数
- args, 传入的值为 java main 方法的参数
方法执行
311 行, 312 行: 新建一个StopWatch实例并调用start方法
这个
StopWatch主要是用来统计程序启动所花费的时间
-
313 行: 设置 JVM 系统属性java.awt.headless -
314 行: 创建一个SpringApplicationRunListeners实例- 创建这个实例时调用到了上一篇讲到的
getSpringFactoriesInstances方法, 这里不再详述. - 那么这个
SpringApplicationRunListeners是干啥的呢, 其实他只是一个非常简单的 Wrapper 类, 只有两个属性, 一个是Log, 另一个是一个SpringApplicationRunListener的列表.
- 创建这个实例时调用到了上一篇讲到的
接下来对于这个实例的操作, 基本上都相当于对这些列表里的对象的操作.
-
316 行: 调用listeners.starting()方法, 其实就是循环调用每一个SpringApplicationRunListener对象的starting方法- 这个
starting方法是一个接口中的默认(default)方法, 这里不再解释为什么接口里面还会有方法体. - 这个方法主要用来做一些程序启动早期的一些初始化动作, 比如它的一个实现类
EventPublishingRunListener就是在这个方法的实现中广播了一个ApplicationStartingEvent类型的事件.
- 这个
-
319 行: 将main方法的参数转换成一个ApplicationArguments对象, 用于更方便的解析和获取参数中的值 -
320 行: 创建并配置一个ConfigurableEnvironment对象- 根据当前应用类型(NONE / SERVLET/ REACTIVE)创建对应的
ConfigurableEnvironment实现类的对象-
配置这个对象的几个属性
- 实例化一个
ConversionService对象并赋值
- 实例化一个
-
配置 PropertySources 属性, 这一步实际做的工作主要还是将
main方法的参数作为一个 property source 添加到框架的 property source 列表中(注意这里其实还没有开始读 applicaiton.properties 或者 application.yml 文件)- 配置 Profile 属性, 这一步主要是读取程序启动参数中的
spring.profiles.active的值, 将参数中设置的的 profile 作为 当前已激活的 profile
- 配置 Profile 属性, 这一步主要是读取程序启动参数中的
-
Attach property source, 其实就是将 application.properties 或者 application.yml 作为一个 property source 加到框 架的 property source 列表中
-
listener.environmentPrepared(), 这一步一般就会广播一个 ApplicationEnvironmentPreparedEvent 类型的事件
-
- 将当前的 springappliaction 对象绑定到 environment 的属性
spring.main上 (这里之后会专门准备一篇文章讲解)- 接下来这一步有些奇怪, 对当前 environment 对象做了一个类型检查, 来确保 enviroment 对象符合当前的应用类型, 但是刚才的 environment 对象就是根据当前应用类型创建的, 难道说这个 environment 对象会被修改? (这里之后再仔细研究)
- 由于有了上面的这一步奇怪的操作, 还需要重复一下上面的一个操作, 即: Attach property source
- 根据当前应用类型(NONE / SERVLET/ REACTIVE)创建对应的
-
321 行: 设置 JVM 属性spring.beaninfo.ignore -
322 行: 打印 banner 信息- 这里就是打印 banner.txt 的内容了, 没什么好说的
-
323 行: 创建应用上下文- 根据应用类型( NONE / SERVLET / REACTIVE) 选择相应的上下文 class
- 通过反射机制实例化并初始化上下文对象
-
325 行: 准备应用上下文 (到了大头了)- 将 environment 对象赋给 context 对象的属性
- postProcessApplicationContext
- 注册一个单例的 bean name generator 对象
- 设置几个属性值: resouceLoader, classLoader, conversionService
- applyInitializers (我发现有些时候直接写方法名, 比自己翻译出来更精确)
- 先是做了一个看起来有点奇怪的类型转换, 然后遍历每个
ApplicationContextInitializer接口的实现类, 调用它们的initialize方法
- 先是做了一个看起来有点奇怪的类型转换, 然后遍历每个
-
326 行: 刷新应用上下文- 注册一个 shutdownHook 线程,如果 JVM 意外终止了,这个线程会把当前的上下文关闭
- 调用上下文对象的 refresh 方法
- 以 ServletWebServerApplicationContext 为例
- 方法中用 synchronized 关键字进行了加锁控制,保证 refresh 方法执行时的安全
- 刷新前的准备
- 初始化 property resource, 对于 servlet 类型的应用来说,就是初始化 servletContextInitParams 和 servletConfigInitParams
- 验证是否所有必须存在(required)的参数都存在
- 刷新的 bean factory
- 准备 bean factory
- 忽略一些 *Aware.class 接口类的依赖, 如 ApplicationContextAware 等
- 注册一些依赖到 bean factory 中, 如把当前的上下文对象注册为 ApplicationContext.class 的单例对象
- 添加 ApplicationListenerDetector 用于检测 ApplicationListener 的内部类
- 注册一些单例bean,如 Environment, SystemProperty 等
- postProcess bean factory
- 主要是创建 bean, 执行 bean post processors
- 初始化 message source 相关的 bean
- 初始化 ApplicationEventMulticaster 相关的 bean
- onRefresh
- 这是一个模板方法,默认是一个空实现,对于 Servlet 类型的应用来说,创建 Tomcat 服务器就是在这个方法里面执行的,这一步执行完,Tomcat 服务器就起来了
- 注册 ApplicationListener 的实现类
- 结束 bean factory 的初始化,到这里,所有的非延迟加载的单例bean都初始化完成了
- 完成应用上下文的刷新,主要是广播一个 ContextRefreshedEvent 类型的事件
-
327 行: 调用了一个空的 afterRefresh 的方法,应该是给之后可能要增加的操作留一个钩子 -
330 行: 打印一行日志,显示启动当前应用所花的时间Started SpringbootLearningApplication in 35.39 seconds (JVM running for 35.92)
-
332 行: 广播一个 ApplicationStartedEvent 类型的事件 -
333 行: 调用所有 ApplicationRunner 和 CommandLineRunner 对象的 run 方法- 先获取所有的 ApplicationRunner 和 CommandLineRunner 对象的集合
- 对这个集合中的对象进行排序,排序依据仍然是根据 Order 和 Priority 的值升序排列
- 遍历集合,调用每个对象的 run 方法
- 注意,这里是同步调用对象的 run 方法,而不是开启一个线程,所以不要这这些 run 方法里面做一个耗时的操作,否则会阻塞后面的的 run 方法的运行
- 再说一遍,不是异步执行的,是同步执行的
-
341 行: 如果上面最后一步 333 行执行完毕,没有异常,那么在这一行上,就会广播一个 ApplicationReadyEvent 类型的事件,到此,整个 run 方法执行完毕,程序启动完成
总结一下:
上一篇讲的是 SpringApplication 的构造方法,这一篇讲的是通过构造方法构造出来的实例的 run 方法。
这个 run 方法里面做了很多事情,大概如下:
- 读取运行环境相关的配置项,如系统的环境变量,启动参数,配置文件,servlet 初始化参数等等
- 创建并刷新上下文,这个是 Spring 的核心,在这一步中,会进行 bean 的实例化,初始化操作
- 创建 WebServer,一般情况下是 Tomcat
- 触发相应的生命周期事件,比如 ApplicationStartedEvent AppicationReadyEvent 等
- 调用所有 ApplicationRunner 和 CommandLineRunner 的对象方法
另外,这两篇,尤其是这一篇写的有些粗糙,目的在于快速的把程序的启动过程搞清楚,具体每个步骤里带的细节,会另外分专门的文章来解读。