原创:西狩

编写日期 / 修订日期: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. c预处理和宏

    文件的预处理 #include "xxx.h" 1 首先查找当前源文件所在的路径 2 查找工程的头文件搜索路径 #include <xxxx.h> 查找工程的头文件搜索 ...

  2. Spark性能调优篇二之重构RDD架构及RDD持久化

    如果一个RDD在两个地方用到,就持久化他.不然第二次用到他时,会再次计算. 直接调用cache()或者presist()方法对指定的RDD进行缓存(持久化)操作,同时在方法中指定缓存的策略. 原文:h ...

  3. css做keylogger

    下载keylogger:https://github.com/maxchehab/css-keylogging 参考讲解:https://blog.csdn.net/weixin_34138139/a ...

  4. Docker Networks 笔记

    Docker Networks Bridge NetworksThe Docker bridge driver automatically installs rules in the host mac ...

  5. Django项目连接多个数据库配置

    1.设置数据库连接 pip install PyMySQL 2.在项目同名目录myproject/myproject下的__init__.py添加以下代码 import pymysql pymysql ...

  6. 如何从零开发一个NuGet软件包?

    作者:依乐祝 首发地址:https://www.cnblogs.com/yilezhu/p/14175019.html 我想目前每个.net开发人员都应该知道nuget.org和NuGet软件包吧.但 ...

  7. Viterbi算法

    clc;clear all;close all; Start_Pi = [-1,-1];State_k = ['H','L'];% 转移矩阵Transition_matrix = [-1,-1.322 ...

  8. RPC 核心,万变不离其宗

    微信搜 「yes的练级攻略」干货满满,不然来掐我,回复[123]一份20W字的算法刷题笔记等你来领. 个人文章汇总:https://github.com/yessimida/yes 欢迎 star ! ...

  9. 在Linux下面端口映射socat自动脚本

    这个sh脚本可以方面的端口映射,在使用本功能之前请确保socat已经放到了/usr/bin/socat #!/bin/bash cd `dirname $0` let listenport=`base ...

  10. Code Review 最佳实践

    ref: Code review Best Practices 文章将了以下内容: 3w:why.what.when 进行 code review code review 之前的准备 执行 code ...