源码 | SpringBoot 启动流程大揭秘
什么是 SpringBoot
日常开发中采用的是开源的若依框架,也就是 SpringBoot 框架,那么什么是 SpringBoot 框架呢?
SpringBoot 是一款开箱即用框架,提供各种默认配置来简化项目配置,让我们的 Spring 应用变的更轻量化、更快的入门,在主程序执行 main 函数就可以运行,也可以打包你的应用为 jar 并通过使用 java -jar 来运行你的 Web 应用。 使用 SpringBoot 只需很少的配置,大部分的时候直接使用默认的配置即可。同时后续也可以与 Spring Cloud 的微服务无缝结合。
SpringBoot 启动流程
SpringBoot 启动流程涉及到的步骤相对来说容易理解,这里我先准备一个启动类
启动类需要标注 @SpringBootApplication 的注解,然后就可以直接以 main 函数的方式执行 SpringApplication.run(DemoApplication.class, args);就可以启动项目,非常简单,下面我们再逐步分析每一步执行流程,main 函数代码
SpringApplication.run
首先我们跟进 SpringApplication.run(DemoApplication.class, args);方法可以看到
run 方法是一个 static 方法,继续点击 run(new Class[] { primarySource }, args);方法可以看到调用了另一个 run 方法
SpringApplication 初始化
在第二个 static 静态 run 方法中可以看到 new 了一个 SpringApplication 对象,同时继续调用其的 run 方法来启动 SpringBoot 项目,下面我们先来看一下 SpringApplication 对象是如何构建的,进入 new SpringApplication(primarySources)的构造方法可以看到
其中 primarySources 就是启动类的 class 对象,继续跟进可以看到 SpringApplication 构造类加载信息
WebApplicationType
在 SpringApplication 的构造类中通过 WebApplicationType.deduceFromClasspath();判断当前应用程序的容器,一共有三种容器,更进去可以看到 WebApplicationType 如图
可以看到包含三种容器 REACTIVE、NONE、SERVLET,默认用的是 WebApplicationType.SERVLET 容器。
加载 spring.factories
再回到 SpringApplication 对象继续往下看,可以看到 this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();方法 getBootstrapRegistryInitializersFromSpringFactories();从 spring.factories 中获取 BootstrapRegistryInitializer,源码
加入源码解析
继续回到 SpringApplication 对象继续往下看会看到
其中 getSpringFactoriesInstances(ApplicationContextInitializer.class)和 getSpringFactoriesInstances(ApplicationListener.class)分别表示从 spring.factories 中获取容器上下文相关初始化 ApplicationContextInitializer 和容器监听器相关初始化 ApplicationListener
获取完容器上下文初始化和监听器初始化器之后通过 setInitializers((Collection)和 setListeners((Collection)分别放入 List<ApplicationContextInitializer> initializers 和 List<ApplicationListener> listeners,这里我们来启动一下程序看一下是否是这样
可以看到已经将 spring.factories 中的配置加载进来了。
deduceMainApplicationClass
后面继续跟进可以看到 deduceMainApplicationClass()理解为推断推论主程序类,debug 可以看到获取了 main 函数所在的主程序类
自定义 spring.factories
增加一个类 ApplicationInit 实现上下文初始化接口 ApplicationContextInitializer,重写 initialize 方法,
同时增加项目 spring.factories 配置
启动应用程序可以看到
初始化完成 SpringApplication 之后就可以运行 run 方法了
SpringBoot 启动 run
初始化完成之后就可以正式进入 run 阶段了
stopWatch.start()
run 方法开始先初始化一个计时器,开启计时,之后会初始化一个上下文对象
createBootstrapContext
初始化上下文对象,这里将初始化 SpringApplication 是从 spring.factories 中获取的 bootstrapRegistryInitializers 进行初始化
之后继续向下看到 configureHeadlessProperty();该方法主要设置系统 headless 属性默认值是 true,查看源码
getRunListeners
从 spring.factories 中获取运行监听器 EventPublishingRunListener
debug 该方法可以看到从配置中加载的运行监听器方法
后续继续启动监听器 listeners.starting(),调用 starting()方法
prepareEnvironment
我们继续看 prepareEnvironment 方法,跟进去可以看到当前方法涉及 getOrCreateEnvironment、configureEnvironment、ConfigurationPropertySources、DefaultPropertiesPropertySource、bindToSpringApplication、convertEnvironment,
这里我们来 debug 看一下执行过程中环境加载情况
加载完成之后执行 configureIgnoreBeanInfo 方法配置忽略的 bean 信息,继续往下看
printBanner
打印配置的 banner 文本信息
banner 文件路径在\src\main\resources\banner.txt,可以通过更该文件内容展示不同的启动成功信息。
继续向下看,看到 createApplicationContext 方法创建应用程序的上下文容器,创建完之后,继续看 prepareContext,该方法主要配置容器的基本信息
prepareContext
prepareContext 也是一个重要的方法,我们来看一下源码方法
prepareContext 主要是为容器上下文设置容器环境变量、postProcessApplicationContext(context)设置容器的 ResourceLoader、ClassLoader、ConversionService,listeners.contextPrepared(context)触发所有 SpringApplicationRunListener 监听器的 contextPrepared 事件方法,getAllSources()Load the sources 加载所有的资源以及 load(context, sources.toArray(new Object[0]))Load beans into the application context 加载启动类 the context to load beans into 将启动类注入容器。
refreshContext
配置完容器基本信息后,刷新容器上下文 refreshContext 方法,
继续跟进去可以看到
这里我们看 web 容器的类,
源码如下,这里不再详细介绍里面的每一步加载了,先写主流程
刷新容器上下文 refreshContext 方法之后看到 afterRefresh 是一个空方法,主要用于开发者拓展使用
listeners.started(context)
容器配置都完成之后,这时监听应用上下文启动完成所有的运行监听器调用 started() 方法,发布监听应用的启动事件,如图
后续继续执行 callRunners 方法遍历所有 runner,调用 run 方法
上述都完成之后到了最后一步,执行 listener.running 方法
运行所有运行监听器,该方法执行以后 SpringApplication.run(DemoApplication.class, args)也就算执行完成了,那么 SpringBoot 的 ApplicationContext 也就启动完成了。
总结
SpringBoot 的执行流程整体上分为两个部分,也就是 SpringApplication 的初始化和 SpringApplication.run 方法,所有的启动加载过程都在这两个方法中,一篇文章写的太多不方便阅读,另外个人觉得太长的文章也没有人有耐心看完,所以中间一些细节没有细究,后面会继续补充里面细节的内容,感谢大家的阅读,欢迎有问题的评论区留言,共同学习共同成长。
版权声明: 本文为 InfoQ 作者【六月的雨在infoQ】的原创文章。
原文链接:【http://xie.infoq.cn/article/92734446ac2c4466c894cc46b】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论