-
精尽Spring Boot源码分析 - SpringApplication 启动类的启动过程
概述
我们 Spring Boot 项目的启动类通常有下面三种方式
方式一和方式二本质上都是通过调用 SpringApplication#run(..)
方法来启动应用,不同的是方式二通过构建器模式,先构建一个 SpringApplication
实例对象,然后调用其 run(..)
方法启动应用,这种方式可以对 SpringApplication
进行配置,更加的灵活。
我们再来看到方式三,它和方式一差不多,不同的是它继承了 SpringBootServletInitializer
这个类,作用就是当你的 Spring Boot 项目打成 war
包时能够放入外部的 Tomcat 容器中运行,如果是 war
包,那上面的 main(...)
方法自然是不需要的,当然,configure(..)
方法也可以不要。
在上篇 《精尽 Spring Boot 源码分析 - Jar 包的启动实现》 文章中讲到,通过 java -jar
启动 Spring Boot 应用时,最终会调用我们启动类的 main(..)
方法,那么本文主要就是对 SpringApplication
这个类进行分析。至于上面 @SpringBootApplication
注解和方式三的相关内容在后续的文章会讲到。
SpringApplicationBuilder
org.springframework.boot.builder.SpringApplicationBuilder
,SpringApplication 的构建器,如下:
上面仅列出了 SpringApplicationBuilder
的部分代码,它支持对 SpringApplication
进行配置,底层还是通过 SpringApplication
这个类来启动应用的,不过多的讲述,感兴趣的可以去看看。
SpringApplication
org.springframework.boot.SpringApplication
,Spring 应用启动器。正如其代码上所添加的注释,它来提供启动 Spring 应用的功能。
相关属性
上面基本上列出了 SpringApplication
的所有属性,每个属性都比较关键,大家先有一个印象,后面也可以回过头来看
构造器
在我们自己的启动类中不管是通过哪种方式都是会先创建一个 SpringApplication
实例对象的,可以先看下它的 run(Class<?>, String...)
方法:
实例化的过程中做了不少事情,如下:
-
设置资源加载器,默认为
null
,可以通过SpringApplicationBuilder
设置 -
设置
primarySources
为主要的 Class 类对象,通常是我们的启动类 -
通过
classpath
判断是否存在相应的 Class 类对象,来决定当前 Web 应用的类型(REACTIVE、SERVLET、NONE),默认为 SERVLET,不同的类型后续创建的 Environment 类型不同很简单,就是依次判断当前 JVM 中是否存在相关的 Class 类对象,来决定使用哪种 Web 类型,默认是 SERVLET 类型
-
初始化所有
ApplicationContextInitializer
类型的对象,并保存至initializers
集合中 -
初始化所有
ApplicationListener
类型的对象,并保存至listeners
集合中,例如 ConfigFileApplicationListener 和 LoggingApplicationListener -
获取当前被调用的
main
方法所属的 Class 类对象,并设置(主要用于打印日志)
上面的第 4
和 5
步都是通过类加载器从 META-INF/spring.factories
文件中分别获取 ApplicationContextInitializer
和 ApplicationListener
类型的类名称,然后进行实例化,这个两种类型的对象都是对 Spring 的一种拓展,像很多框架整合 Spring Boot 都可以通过自定义的 ApplicationContextInitializer
对 ApplicationContext 进行一些初始化,通过 ApplicationListener
在 Spring 应用启动的不同阶段来织入一些功能
getSpringFactoriesInstances 方法
过程比较简单,如下:
-
获取类加载器
-
通过类加载器从所有
META-INF/spring.factories
文件中获取类型为type
的类名称,这里的 SpringFactoriesLoader 是 Spring 中的一个类 -
为上一步获取到的所有类名称创建对应的实例对象
-
通过
@Order
注解进行排序 -
返回排序后的
type
类型的实例对象
SpringApplication#run 方法
上面已经讲述了 SpringApplication
的实例化过程,那么接下来就是调用它的 run(String... args)
方法来启动 Spring 应用,该过程如下:
整个启动过程做了很多事情,主要过程如下:
-
创建 StopWatch 对象并启动,主要用于统计当前方法执行过程的耗时
-
设置
java.awt.headless
属性,和 AWT 相关,暂时忽略 -
调用
getRunListeners(..)
方法,初始化所有SpringApplicationRunListener
类型的对象,并全部封装到SpringApplicationRunListeners
对象中 -
启动所有的
SpringApplicationRunListener
监听器,例如EventPublishingRunListener
会广播 ApplicationEvent 应用正在启动的事件,它里面封装了所有的ApplicationListener
对象,那么此时就可以通过它们做一些初始化工作,进行拓展 -
创建一个 ApplicationArguments 应用参数对象,将
main(String[] args)
方法的args
参数封装起来,便于后续使用 -
调用
prepareEnvironment(..)
方法,准备好当前应用 Environment 环境,这里会加载出所有的配置信息,包括application.yaml
和外部的属性配置 -
调用
printBanner(..)
方法,打印 banner 内容 -
调用
createApplicationContext()
方法, 对context
(Spring 上下文)进行实例化,例如 Servlet(默认)会创建一个 AnnotationConfigServletWebServerApplicationContext 实例对象 -
获取异常报告器,通过类加载器从
META-INF/spring.factories
文件中获取 SpringBootExceptionReporter 类型的类名称,并进行实例化 -
调用
prepareContext(..)
方法,对 Spring 应用上下文做一些初始化工作,例如执行ApplicationContextInitializer#initialize(..)
方法 -
调用
refreshContext(..)
方法,刷新 Spring 应用上下文,在这里会完成所有 Spring Bean 的初始化,同时会初始化好 Servlet 容器,例如 Tomcat这一步涉及到 Spring IoC 的所有内容,参考 《死磕Spring之IoC篇 - Spring 应用上下文 ApplicationContext》
在
ServletWebServerApplicationContext#onRefresh()
方法中会创建一个 Servlet 容器(默认为 Tomcat),也就是当前 Spring Boot 应用所运行的 Web 环境 -
调用
afterRefresh(..)
方法,完成刷新 Spring 应用上下文的后置操作,空实现,扩展点 -
停止 StopWatch,统计整个 Spring Boot 应用的启动耗时,并打印
-
对所有的
SpringApplicationRunListener
监听器进行广播,发布 ApplicationStartedEvent 应用已启动事件,通常只有一个EventPublishingRunListener
对象 -
回调 IoC 容器中所有 ApplicationRunner 和 CommandLineRunner 类型的启动器,默认情况下没有,先暂时忽略
-
对所有的
SpringApplicationRunListener
监听器进行广播,发布 ApplicationReadyEvent 应用已就绪事件,通常只有一个EventPublishingRunListener
对象
启动 Spring 应用的整个主流程清晰明了,先准备好当前应用的 Environment 环境,然后创建 Spring ApplicationContext 应用上下文。
该方法的整个过程更多的细节在于上面每一步调用的方法,抽丝剥茧,对于非常复杂的地方会另起文章进行分析
3. getRunListeners 方法
getRunListeners(String[] args)
方法,初始化所有 SpringApplicationRunListener
类型的对象,并全部封装到 SpringApplicationRunListeners
对象中,如下:
这里同样调用上面讲过的 getSpringFactoriesInstances(..)
方法,通过类加载器从 META-INF/spring.factories
文件中获取 SpringApplicationRunListener
类型的类名称,并进行实例化
最后会将它们全部封装到 SpringApplicationRunListeners 对象中,就是把一个 List 封装到一个对象中,不过默认情况只有一个 EventPublishingRunListener
对象,其内部又封装了 SpringApplication
中的所有 ApplicationListener
应用监听器们,例如 ConfigFileApplicationListener 和 LoggingApplicationListener
6. prepareEnvironment 方法
prepareEnvironment(SpringApplicationRunListeners, ApplicationArguments)
方法,准备好当前应用 Environment 环境,加载出所有的配置信息,包括 application.yaml
和外部的属性配置,如下:
该过程如下:
-
根据 Web 应用的类型创建一个 StandardEnvironment 对象
environment
-
为
environment
配置默认属性(如果有)并设置需要激活的profile
们可以看到会将
main(String[] args)
方法入参中的--
开头的参数设置到 Environment 中 -
将当前
environment
的 MutablePropertySources 封装成 SpringConfigurationPropertySources 添加到 MutablePropertySources 首部 -
对所有的 SpringApplicationRunListener 广播 ApplicationEvent 应用环境已准备好的事件,这一步比较复杂,例如 Spring Cloud 的
BootstrapApplicationListener
监听到该事件会创建一个 ApplicationContext 作为当前 Spring 应用上下文的父容器,同时会读取bootstrap.yml
文件的信息这里会有一个
ConfigFileApplicationListener
监听到该事件然后去解析application.yml
等应用配置文件的配置信息 -
将
environment
绑定到当前 SpringApplication 上 -
如果不是自定义的 Environment 则需要根据 Web 应用类型转换成对应 Environment 类型
-
再次进行上面第
3
步的处理过程,防止上面几步对上面的 PropertySources 有修改 -
返回准备好的
environment
该方法准备好了当前应用 Environment 环境,主要在于上面第 4
步,是 ApplicationListener
监听器的一个扩展点,在这里会加载出所有的配置信息,包括 application.yml
和外部配置,解析配置的过程比较复杂,在后面的文章中单独分析
8. createApplicationContext 方法
createApplicationContext()
方法,对 context
(Spring 上下文)进行实例化,如下:
不同的应用类型创建不同的 Spring 应用上下文对象:
-
SERVLET(默认是这个):
AnnotationConfigServletWebServerApplicationContext
-
REACTIVE:
AnnotationConfigReactiveWebServerApplicationContext
-
DEFAULT:
AnnotationConfigApplicationContext
10. prepareContext 方法
prepareContext(ConfigurableApplicationContext, ConfigurableEnvironment, SpringApplicationRunListeners, ApplicationArguments, Banner)
方法,对 Spring 应用上下文做一些初始化工作,如下:
该过程如下:
-
为 Spring 应用上下文设置 Environment 环境
-
将一些工具 Bean 设置到 Spring 应用上下文中,供使用
-
通知 ApplicationContextInitializer 对 Spring 应用上下文进行初始化工作
-
对所有 SpringApplicationRunListener 进行广播,发布 ApplicationContextInitializedEvent 初始化事件
-
向 Spring 应用上下文注册
main(String[])
方法的参数 Bean 和 Banner 对象 -
获取
primarySources
(例如你的启动类)和sources
(例如 Spring Cloud 中的@BootstrapConfiguration
)源对象,没有的话会抛出异常 -
将上面的源对象加载成 BeanDefinition 并注册
-
对所有的 SpringApplicationRunListener 广播 ApplicationPreparedEvent 应用已准备事件,会把 ApplicationListener 添加至 Spring 应用上下文中
通过上面的第 6
步你就知道为什么我们的启动类里面一定得有一个入参为启动类的 Class 对象了
11. refreshContext 方法
refreshContext(ConfigurableApplicationContext)
方法,刷新 Spring 应用上下文,在这里会完成所有 Spring Bean 的初始化,同时会初始化好 Servlet 容器,例如 Tomcat,该方法涉及到 Spring IoC 的所有内容,参考 《死磕Spring之IoC篇 - Spring 应用上下文 ApplicationContext》
该方法主要是调用 AbstractApplicationContext#refresh()
方法,刷新 Spring 应用上下文,整个过程牵涉到 Spring 的所有内容,之前的一系列文章已经分析过,关于更多的细节这里不展开谈论,当然,这个过程会有对 @SpingBootApplication
注解的解析
根据 8. createApplicationContext 方法 方法中讲到,我们默认情况下是 SERVLET 应用类型,也就是创建一个 AnnotationConfigServletWebServerApplicationContext
对象,在其父类 ServletWebServerApplicationContext
中重写了 onRefresh()
方法,会创建一个 Servlet 容器(默认为 Tomcat),也就是当前 Spring Boot 应用所运行的 Web 环境,这部分内容放在后面的文章单独分析。
SpringApplicationRunListeners
org.springframework.boot.SpringApplicationRunListeners
,对 SpringApplicationRunListener 数组的封装
比较简单,就是封装了多个 SpringApplicationRunListener
对象,对于不同类型的事件,调用其不同的方法
可以在 META-INF/spring.factories
文件中看到,只有一个 EventPublishingRunListener 对象
EventPublishingRunListener
org.springframework.boot.context.event.EventPublishingRunListener
,实现了 SpringApplicationRunListener 接口,事件广播器,发布不同类型的事件
比较简单,关键在于内部的 SimpleApplicationEventMulticaster 事件广播器,里面包含了所有的 META-INF/spring.factories
文件中配置的 ApplicationListener
监听器,不同的方法发布不同的事件,进行广播
可以看到 Spring Boot 配置了许多个 ApplicationListener,后续文章会对 ConfigFileApplicationListener 和 LoggingApplicationListener 进行简单的分析
总结
Spring Boot 应用打成 jar
包后的启动都是通过 SpringApplication#run(String... args)
这个方法来启动整个 Spring 应用的,流程大致如下:
-
从
META-INF/spring.factories
文件中加载出相关 Class 对象,并进行实例化,例如ApplicationContextInitializer
,SpringApplicationRunListener
和ApplicationListener
对象 -
准备好当前 Spring 应用的 Environment 环境,这里会解析
application.yml
以及外部配置 -
创建一个 ApplicationContext 应用上下文对象,默认 SERVLET 类型下创建
AnnotationConfigServletWebServerApplicationContext
对象 -
调用
AbstractApplication#refresh()
方法,刷新 Spring 应用上下文,也就是之前一系列 Spring 相关的文章所讲述的内容
整个过程有许多个扩展点是通过监听器机制实现的,在不同阶段广播不同类型的事件,此时 ApplicationListener
就可进行相关的操作
在上面第 4
步中,SERVLET 应用类型下的 Spring 应用上下文会创建一个 Servlet 容器(默认为 Tomcat)
更多的细节在后续文章依次进行分析
__EOF__