原创:西狩

编写日期 / 修订日期: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. mini-web框架-元类-总结(5.4.1)

    @ 目录 1.说明 2.代码 关于作者 1.说明 python中万物都是对象 使用python中自带的globals函数返回一个字典 通过这个可以调取当前py文件中的所有东西 当定义一个函数,类,全局 ...

  2. Web服务器-服务器开发-返回浏览器需要的页面 (3.3.2)

    @ 目录 1.说明 2.代码 关于作者 1.说明 使用正则表达式,匹配客户端的请求头 获取到请求的路径 返回对应请求路径的文字 可以使用打开对应文件的方式去返回对应的文件 2.代码 from sock ...

  3. java_day_02

    一.return的两个作用 1.停止当前方法 2.将后面的结果数据返回值还给调用处 二.方法的三种调用格式 1.单独调用:方法名(参数): public class Method { public s ...

  4. Laravel笔记记录

    1.获取当前控制器和action名称 request()->route()->getAction()

  5. 关于python面试中的设计模式,搞懂这些就足够了

    1.什么是设计模式? 设计模式是经过总结.优化,对我们经常遇到的一些编程问题的可重用的解决方案.设计模式不同于类或库可直接作用于代码.相反,它更为的高级,是一种必须在特定的情形下实现的方法模版. 2. ...

  6. EF Core 三 、 骚操作 (导航属性,内存查询...)

    EF Core 高阶操作 本文之前,大家已经阅读了前面的系列文档,对其有了大概的了解 我们来看下EF Core中的一些常见高阶操作,来丰富我们业务实现,从而拥有更多的实现选择 1.EF 内存查找 wh ...

  7. grpc系列- protobuf详解

    Protocol Buffers 是一种与语言.平台无关,可扩展的序列化结构化数据的方法,常用于通信协议,数据存储等等.相较于 JSON.XML,它更小.更快.更简单,因此也更受开发人员的青眯. 基本 ...

  8. jQuery的data()方法

    jQuery文档对.data()方法的描述: As of jQuery 1.4.3 HTML 5 data- attributes will be automatically pulled in to ...

  9. YourBatman 2020年感悟关键词:科比、裁员、管理层、活着

    目录 本文提纲 ✍前言 版本约定 ✍正文 科比 裁员 如何避免被裁? 1.不要迷恋管理,一味追求"当官" 2.别以为裁员只裁一线,不裁管理层 3.即使步入管理,建议不要脱离技术 4 ...

  10. JVM——GC(垃圾回收)算法

    一.垃圾回收的基本概念 垃圾回收(GC,Garbage Collection),指内存中不会再被使用的对象清理掉. 垃圾回收有很多种算法:如引用计数法.标记压缩法.复制算法.分代/分区的思想 二.垃圾 ...