本文基于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的整个过程。