Spring Boot源码分析之 启动过程
本文基于Spring Boot 2.2.1.RELEASE
版本了解Spring Boot
如何启动
首先让我们看一下最简单的Spring Boot
启动代码
每一个使用过Spring Boot
的同学对于上面的代码应该都非常熟悉了,通过这段代码即可启动Spring Boot
应用。那么SpringApplication.run(DemoApplication.class, args)
内部到底做了什么事情呢?
在查看具体代码之前,我们先了解一下SpringApplication
内部大概的执行流程,如下图
从上图中可以看出run()
是整个应用的入口,接着初始化SpringApplicationRunListener
,Environment
等实例,然后创建应用上下文对象,“准备”并“刷新”上下文,到这里Spring
容器已基本启动完成,最后发送事件通知各个组件作出相应动作。
源码分析
在了解完大概的流程之后,下面开始深入源码分析Spring Boot
具体的启动过程,首先进入入口方法run
StopWatch
主要是用来统计每项任务执行时长,例如Spring Boot
启动占用总时长。
Started DemoApplication in 4.241 seconds (JVM running for 5.987)
getRunListeners()
完成了SpringApplicationRunListener
实例化工作,如何完成的呢?进入方法内部查看
SpringApplicationRunListeners
和SpringApplicationRunListener
不是同一个类,它们名称非常相似
查看SpringApplicationRunListeners
源码
它是
SpringApplicationRunListener
的一个集合
观察SpringApplicationRunListeners
所有方法,可以看出,它实际是一个用来发送SpringApplicationRunListener
相关事件的工具类
接着继续观察getSpringFactoriesInstances
源码,看它是如何实例化对象的(此方法后续多处使用)
这里通过SpringFactoriesLoader.loadFactoryNames
获取type
对应的FactoryNames
,不明白有什么用处?进入方法内部查看
继续进入loadSpringFactories
方法内部
看到这里可能会疑惑META-INF/spring.factories
文件在哪里?文件里面有什么内容?
其实这个文件存放在Spring Boot
和Spring Boot autoconfigure
的jar包内部(有兴趣的同学可以自行下载jar包并解压查看),Spring Boot
中的文件内容如下:
可以看到SpringApplicationRunListener
对应的值是EventPublishingRunListener
回到SpringFactoriesLoader.loadFactoryNames
方法内部,可以发现方法获取的值实际上是factoryClass
在META-INF/spring.factories
中对应的实现类的集合
明白这个方法之后,再回到getSpringFactoriesInstances
方法
到此为止getRunListeners
完成了SpringApplicationRunListener
对应实现类的实例化,并回调其starting
方法
从上面分析得知,实际上调用的是EventPublishingRunListener
的starting
方法,那么方法内部做了什么呢?
发送了一个ApplicationStartingEvent
事件
继续查找ApplicationStartingEvent
事件的消费者,从spring.factories
中可以找到所有预定义的事件消费者
接下来要做的就是从这些消费者中找出ApplicationStartingEvent
事件的消费者(查找过程省略),找到以下两个消费者
-
LoggingApplicationListener 初始化日志系统
-
LiquibaseServiceLocatorApplicationListener (参数liquibase.servicelocator.ServiceLocator)如果存在,则使用springboot相关的版本进行替代
了解完ApplicationStartingEvent
事件之后,回到run
方法继续往下探究prepareEnvironment
这里又发布了一个ApplicationEnvironmentPreparedEvent
事件,继续查找事件监听对象
- FileEncodingApplicationListener 检查系统文件编码格式是否符合环境变量中配置的文件编码格式(如果存在相关设置 - spring.mandatory-file-encoding),如果编码不符合,则抛出异常阻止
Spring
启动 - AnsiOutputApplicationListener 是否开启AnsiOutput
- DelegatingApplicationListener 代理context.listener.classes配置的监听者
- ClasspathLoggingApplicationListener 日志输出classpath
- LoggingApplicationListener 配置日志系统,logging.config, logging.level...等
- ConfigFileApplicationListener 这是一个比较重要的监听对象,具体的方法实现如下
通过spring.factories
,可以看到这里加载以下EnvironmentPostProcessor
对象
- CloudFoundryVcapEnvironmentPostProcessor
- SpringApplicationJsonEnvironmentPostProcessor
- SystemEnvironmentPropertySourceEnvironmentPostProcessor
- ConfigFileApplicationListener
很多同学可能会疑问ConfigFileApplicationListener
并不存在spring.factories
文件中,这里为什么会有它呢?
实际上ConfigFileApplicationListener
在onApplicationEnvironmentPreparedEvent
方法中,将自身添加到EnvironmentPostProcessor
对象列表中。
我们主要关注ConfigFileApplicationListener
的postProcessEnvironment
方法
ConfigFileApplicationListener
监听到ApplicationEnvironmentPreparedEvent
事件之后开始读取本地配置文件
关于Spring
如何读取本地配置文件,请前往Spring Boot源码分析-配置文件加载原理
创建ApplicationContext
对象
这里是根据webApplicationType
决定创建什么类型的ApplicationContext
对象,那么webApplicationType
是何时赋值的呢?
从上面可以看出是通过WebApplicationType.deduceFromClasspath
方法初始化的webApplicationType
,继续跟踪代码
从上面代码中可以看出Spring
是通过当前classpath
下是否存在相应的类,从而决定webApplicationType
类型
初始化ApplicationContext
对象
这里注册了DemoApplication
到Spring
容器中,为后续bean扫描做准备
接下来继续深入refreshContext
方法,可以发现实际上是执行了AbstractApplicationContext.refresh
方法
refresh
方法内部做了很多事情。比如:完成BeanFactory
设置,BeanFactoryPostProcessor
、BeanPostProcessor
接口回调,Bean
加载,国际化配置等。
到此为止Spring
基本完成了容器的初始化工作,最后在调用callRunners
方法,执行ApplicationRunner
、CommandLineRunner
接口。
整个启动过程的核心方法是refresh
,此方法内部承载大部分容器启动所需的工作。由于篇幅原因,后续再进行refresh
内部源码分析,了解Spring Boot
加载Bean
的整个过程。