原创:西狩

编写日期 / 修订日期:2020-12-30 / 2020-12-30

版权声明:本文为博主原创文章,遵循 CC BY-SA-4.0 版权协议,转载请附上原文出处链接和本声明。

不正经的前言

最近好朋友山治去面试了,晚上回来有些低迷地问我:“小江,你知道 SpringBoot 的启动流程吗?”

我说:“知道呀!从 SpringApplication.run() 方法开始,首先进行实例化,实例化里主要做了4件事:根据calsspath……”

山治抬腿就是一记“恶魔风脚”:SpringBoot 的启动步骤那么多,什么 1、2、3、4,谁能记得住啊!

在被乔巴施展”还我漂漂拳“以后,我痛定思痛,暗暗发誓一定要写篇比美女还好看的文章教会山治,让他吃透这道看似难啃的“菜”。

料理的二三事

选材说明

首先,做一份料理,一定要准备好采购清单。如果只有菜谱没有选材说明,最终做出来的味道可能并没有那么好。哪怕随便做一道家常菜,需要放大葱还是香葱也是有讲究的,而不同年份的葡萄酿制的酒就更不用说了。

正确的选材示例:山治的料理笔记。

错误的选材实例:路飞不看笔记误吃有毒鱼皮。

料理的主要流程

现在,咱们来聊聊吃货该聊的事情:想要做一道菜需要做些什么?

料理三要素

来看一下料理三要素:

  1. 做饭的场地
  2. 完美的食材
  3. 优秀的厨师

当然,虽然在家里一个人就可以做了,但是不要小看料理呀!咱们要聊就聊 big restaurant。比如一家让你难忘的餐厅:海上餐厅“BARATI”。你想要的东西——上面提到的三要素,餐厅后厨全都有。Ok!下面就可以准备料理了。

料理步骤

料理的步骤很简单,包括准备步骤和开始步骤。

料理准备

让我们来安排一场完美的料理。BARATI 料理的准备步骤:

  1. 选择储存食材的冰箱
  2. 选择料理的主食材
  3. 根据点菜单确定料理菜系
  4. 准备料理需要的菜谱
  5. 指定处理食材的厨师
  6. 指定做料理的主厨
料理开始

“高端的食材只需要简单的烹饪”。重头戏开始了!BARATI 料理的工作流程:

  1. 允许外卖
  2. 厨师待命
  3. 加载点菜单的要求(如:不要香菜)
  4. 准备料理所需的锅碗瓢盆,并通知厨师准备好了
  5. 忽略没必要了解的信息(如:食材的价格)
  6. 指定菜品装饰
  7. 根据菜系,获取对应菜谱
  8. 设置突发情况报告人(如:点的菜没有了)
  9. 厨师查看锅碗瓢盆、菜谱和点菜单的要求
  10. 处理食材
  11. 料理完成后,根据点菜单的要求定制
  12. 是否查看客人反馈
  13. 食材准备就绪
  14. 通知所有可以干活的厨师
  15. 准备开工
  • 突发报告人处理突发情况(点的菜没有了,需要告诉服务员)

就这样,一顿完美的料理就做好了。

欢迎来到“BARATI”

选材说明

学技术也是一样,版本说明就是料理的选材说明。遵循“就地取材”原则,本次选用的“主料”是平时项目上使用的 SpringBoot 2.1.5.RELEASE 版本。依赖如下:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.1.5.RELEASE</version>
</dependency>

“BARATI”后厨主要流程

以 SpringApplication.run() 方法为例

料理三要素

@SpringBootApplication
public class StartApplication { public static void main(String[] args) {
// 1. 做饭的场地
// 2. 完美的食材
// 3. 优秀的厨师
SpringApplication.run(StartApplication.class, args);
}
}
  1. 做饭的场地:SpringApplication
  2. 完美的食材:所有通过 SpringBoot 自动配置扫描,由 ClassLoader 加载的 Class
  3. 优秀的厨师:在启动过程中所有 ApplicationListener 和 ApplicationRunner

料理步骤

public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
// 1. 料理准备
// 2. 料理开始
return new SpringApplication(primarySources).run(args);
}
  1. 料理准备:new SpringApplication(primarySources) 方法,SpringApplication 的初始化
  2. 料理开始:SpringApplication.run(args) 方法,SpringBoot 实际启动的流程
料理准备
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
// 1. 选择储存食材的冰箱
this.resourceLoader = resourceLoader;
// 2. 选择料理的主食材
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 3. 根据点菜单确定料理菜系
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 4. 准备料理需要的菜谱
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
// 5. 指定处理食材的厨师
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 6. 指定做料理的主厨
this.mainApplicationClass = deduceMainApplicationClass();
}
  1. 选择储存食材的冰箱

    可指定的类加载器,与 classpath 相关,默认为null,加载时使用 DefaultResourceLoader

  2. 选择料理的主食材

    设置传入的主源类

  3. 根据点菜单确定料理菜系

    通过加载的 class 判断web应用类型(NONE、SERVLET、REACTIVE)

  4. 准备料理需要的菜谱

    通过 getClassLoader(),查找并加载所有 ApplicationContextInitializer

  5. 指定处理食材的厨师

    通过 getClassLoader(),查找并加载所有 ApplicationListener

  6. 指定做料理的主厨

    推断并设置 main 函数所在的 class

料理开始
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 1. 允许外卖
configureHeadlessProperty();
// 2. 厨师待命
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
// 3. 加载点菜单的要求(如:不要香菜)
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
// 4. 准备料理所需的锅碗瓢盆,并通知厨师准备好了
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
// 5. 忽略没必要了解的信息(如:食材的价格)
configureIgnoreBeanInfo(environment);
// 6. 设置菜品装饰
Banner printedBanner = printBanner(environment);
// 7. 根据菜系,获取对应菜谱
context = createApplicationContext();
// 8. 设置突发情况报告人(如:点的菜没有了)
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 9. 厨师查看锅碗瓢盆、菜谱和点菜单的要求
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
// 10. 处理食材
refreshContext(context);
// 11. 料理完成后,根据点菜单的要求定制
afterRefresh(context, applicationArguments);
stopWatch.stop();
// 12. 是否查看客人反馈
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
// 13. 食材准备就绪
listeners.started(context);
// 14. 通知所有可以干活的厨师
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
// * 处理突发情况
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
} try {
// 15. 准备开工
listeners.running(context);
}
catch (Throwable ex) {
// * 处理突发情况
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
  1. 允许外卖

    主要允许服务器只提供服务,不提供显示器和界面展示的情况,类似只支持外带,不支持店内就餐

  2. 厨师待命

    获取所有监听者,进入监听状态

  3. 加载点菜单的要求(如:不要香菜)

    读取传入 args 参数

  4. 准备料理所需的锅碗瓢盆,并通知厨师准备好了

    设置环境变量,通知监听者

  5. 忽略没必要了解的信息(如:食材的价格)

    忽略 BeanInfo 信息,主要为了提高启动速度

  6. 指定菜品装饰

    设置 Banner

  7. 根据菜系,获取对应菜谱

    根据应用类型(是 Servlet,还是 Reactive),创建对应上下文

  8. 设置突发情况报告人(如:点的菜没有了)

    加载SpringBoot异常上报类

  9. 厨师查看锅碗瓢盆、菜谱和点菜单的要求

    根据环境变量、监听者、启动参数和 Banner,装载上下文

  10. 处理食材

    刷新上下文

  11. 料理完成后,根据点菜单的要求定制

    空操作,刷新上下文后的预留扩展点

  12. 是否查看客人反馈

    设置日志信息打印

  13. 食材准备就绪

    发布 ApplicationStartedEvent 事件,表示监听者任务完成

  14. 通知所有可以干活的厨师

    调用 ApplicationRunner,CommandLineRunner 的 run 方法

  15. 准备开工

    发布 ApplicationReadyEvent 事件,表示应用就绪

  • 突发报告人处理突发情况(点的菜没有了,需要告诉服务员)

    如果启动异常,处理 exceptionReporters 中的异常信息,并抛出异常

小结

本篇文章想达到的目的是:将源码映射到现实生活的事件,加深对源码的解读,希望将晦涩难度的源码变成一件有趣的事情。此文只是作为一个吃货的兴趣篇,并不是特别严谨,在 SpringBoot 启动过程中,还有很多精妙的细节需要继续推敲,我会在后续文章中,对它们进行剖析。当然,由于自身水平限制,有些比喻可能并不一定十分恰当,希望各位老板见仁见智地去理解。若发现不当之处,欢迎私信沟通交流!

SpringBoot 好“吃”的启动原理的更多相关文章

  1. springboot学习入门之三---启动原理

    3启动原理 3.1启动类 @SpringBootApplication public class Application { public static void main(String[] args ...

  2. SpringBoot 2.1.6 启动原理解析(一)

    小白第一次写博客,如果有不足之处烦请各位大佬指正. 用了好久的SpringBoot了,一直不清楚它内部的一些启动原理,如何加载yml文件.如何初始化bean的,今天就记录一下,新建了一个springb ...

  3. SpringBoot内置tomcat启动原理

    前言          不得不说SpringBoot的开发者是在为大众程序猿谋福利,把大家都惯成了懒汉,xml不配置了,连tomcat也懒的配置了,典型的一键启动系统,那么tomcat在springb ...

  4. SpringBoot(1)—启动原理之SpringApplication对象的创建

    创建SpringApplication对象 SpringBoot版本为 2.1.1.RELEASE @SpringBootApplication public class SpringbootDemo ...

  5. SpringBoot(二)启动原理

    SpringBoot自动配置模块 该配置模块的主要使用到了SpringFactoriesLoader,即Spring工厂加载器,该对象提供了loadFactoryNames方法,入参为factoryC ...

  6. springboot之启动原理解析

    前言 SpringBoot为我们做的自动配置,确实方便快捷,但是对于新手来说,如果不大懂SpringBoot内部启动原理,以后难免会吃亏.所以这次博主就跟你们一起一步步揭开SpringBoot的神秘面 ...

  7. SpringBoot之旅第六篇-启动原理及自定义starter

    一.引言 SpringBoot的一大优势就是Starter,由于SpringBoot有很多开箱即用的Starter依赖,使得我们开发变得简单,我们不需要过多的关注框架的配置. 在日常开发中,我们也会自 ...

  8. SpringBoot启动原理及相关流程

    一.springboot启动原理及相关流程概览 springboot是基于spring的新型的轻量级框架,最厉害的地方当属自动配置.那我们就可以根据启动流程和相关原理来看看,如何实现传奇的自动配置 二 ...

  9. 带着萌新看springboot源码12(启动原理 下)

    先继续接上一篇,那个启动原理还有一点没说完. 6. afterRefresh(context, applicationArguments); 看这个名字就知道,应该就是ioc容器刷新之后的一些操作了, ...

随机推荐

  1. js下 Day15、正则表达式

    一.正则表达式简介 什么是正则表达式 正则表达式,也叫规则表达式, 是对字符串操作的一种逻辑公式. 为什么要使用正则? 1.使用极简单的方式,去匹配字符串 2.速度快,代码少 3.在复杂的字符串中快速 ...

  2. C++模板元编程----快速排序

    目录 目录 简介 实现 数据结构定义 在数组前添加一个元素 判断 分堆 合并 快速排序的实现 总结 简介 上一篇使用C++模板模板实现了一个选择排序.这一次,更进一步的,实现了一个快速排序算法.关于快 ...

  3. kali2020 装不上docker

    问题描述 如图,不支持i386体系架构 所以百度了一下啥是i386,以及docker支持哪些体系架构 官网的截图如下: 然后我发现我一直以为我装的是64位debian,实际上我装的是32位的 补充一下 ...

  4. 数组单调性判断以及all和diff函数的用法

    clc;clear all;close all; n = 1 ;x = zeros(100,1);while n~= 0 n = input('请输入向量的长度n(0退出程序):'); for i = ...

  5. NET 调用人脸识别算法

    以前有个OpenCV 移植版EMCV可以用作图像识别等 https://github.com/emgucv/emgucv 现在有各种接口 比如虹软SDK  https://ai.arcsoft.com ...

  6. MySQL-5.7.29-winx64解压缩版安装

    1.下载压缩包 https://dev.mysql.com/downloads/file/?id=491809 2.解压下载的文件(路径放在哪都可以) 3.配置环境变量 添加变量到path中 4.准备 ...

  7. Java学习日报7.17

    控制台运行

  8. ADO.NET对SqlServer进行简单的增删改查

    对数据库进行增删改查,首先想到的应该就是连接字符串了. 我们的连接字符串是由"Server=地址(本机=local);Database=数据库名称;User Id=登陆用户名;Passwor ...

  9. ASP.NET Core Controller与IOC的羁绊

    前言 看到标题可能大家会有所疑问Controller和IOC能有啥羁绊,但是我还是拒绝当一个标题党的.相信有很大一部分人已经知道了这么一个结论,默认情况下ASP.NET Core的Controller ...

  10. 改进你的c#代码的5个技巧(一)

    亲爱的读者,在这篇文章中,我提供了一些c#编程的最佳实践. 你是否在用户输入验证中使用异常处理机制? 如果是,那么你就是那个把你的项目执行速度降低了62倍的人.你不相信我吗?等几分钟;我来教你怎么做. ...