SpringBoot 好“吃”的启动原理

原创:西狩

编写日期 / 修订日期: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 启动过程中,还有很多精妙的细节需要继续推敲,我会在后续文章中,对它们进行剖析。当然,由于自身水平限制,有些比喻可能并不一定十分恰当,希望各位老板见仁见智地去理解。若发现不当之处,欢迎私信沟通交流!

(0)

相关推荐

  • SpringBoot运行原理

    pom.xml <parent> <groupId>org.springframework.boot</groupId> <artifactId>spr ...

  • Spring Boot启动流程简单解释

    Spring Boot程序有一个入口,就是main方法.main里面调用SpringApplication.run()启动整个Spring Boot程序,该方法所在类需要使用@SpringBootAp ...

  • SpringBoot启动流程总结

    一直很好奇SpringBoot这么一个大怪物,启动的时候做了哪些事情,然后看了很多老师讲的教学视频,然后自己想好好整理一下,做下学习笔记下次也方便自己阅读 1.执行main方法 public stat ...

  • SpringBoot启动流程解析

    写在前面: 由于该系统是底层系统,以微服务形式对外暴露dubbo服务,所以本流程中SpringBoot不基于jetty或者tomcat等容器启动方式发布服务,而是以执行程序方式启动来发布(参考下图ke ...

  • 五羊本田睿御无声启动原理与启动控制电路简单分析

    作者:木石雅玩 五羊本田睿御WH110T-6电喷摩托车有一个显著特色,就是配备了无声启动和怠速智能启停系统. 这款车的起动机构,由一个三相交流发电机和三相无刷起动电机集合在一起,也就是说它既是发电机, ...

  • 星三角降压启动原理电路图

    常规做法是M,△,Y三个一样大,容量取电机额定电流值相近的规格. 比如37kw电机,额定电流约72A左右,可选三个成品的65A的接触器.因为是双路供电,所以65A足够了. 省钱的做法有两种,一是把星形 ...

  • 电机星三角降压启动原理电路图分析及实物接线图,一步步详解

    下图所示为异步电动机.星三角起动控制电路图,此种接法只适合于电动机正常运行时为三角型联接. 打开搜狗搜索APP,查看更多精彩资讯 所需主要元器件:三个交流接触器,一个热继电器,一个时间继电器,启动.停 ...

  • “鸡兔同笼”“牛吃草”“抽屉原理”等35个常见奥数问题解答技巧

    在孩子的学习过程中,总会遇到很多奇妙的数学问题.像什么"鸡兔同笼""植树问题""牛吃草问题""烙饼问题",这些问题非常的 ...

  • 天天画着星三角,但对星三角降压启动原理了解吗?

    星三角降压启动是我们电气工作者必须要掌握的一个知识点,因为它涵盖的知识内容比较多,一些人只会用对于原理却很模糊. 1. 什么要进行星三角降压启动? 三相异步电动机启动瞬间通常数秒钟,一般也就1∼3秒, ...

  • 万字详文干货:从无盘启动volumio看Linux启动原理

    作者:bobyzhang,腾讯 IEG 运营开发工程师 0. 故事的开始 0.1 为什么和做什么 最近家里买了对音响,我需要一个数字播放器.一凡研究后我看上了 volumio(https://volu ...

  • 【案例讲解】星三角降压启动原理分析注意事项

    在工作现场接触的问题多了,总想写点什么! 笔者是早年毕业于武汉轻工大学自动化专业,毕业后供职于中建三局从事重工设备制造.在自动化领域摸爬滚打多年,跑过不少项目,迎来送往不少同事或刚毕业的专科技校的学生 ...

  • SpringBoot面试杀手锏——自动配置原理

    引言 不论在工作中,亦或是求职面试,Spring Boot已经成为我们必知必会的技能项.除了某些老旧的政府项目或金融项目持有观望态度外,如今的各行各业都在飞速的拥抱这个已经不是很新的Spring启动框 ...

  • 电源缓启动原理及作用

    现在大多数电子系统都要支持热插拔功能,所谓热插拔,也就是在系统正常工作时,带电对系统的某个单元进行插拔操作,且不对系统产生任何影响. 热插拔对系统的影响主要有两方面: 其一,热插拔时,连接器的机械触点 ...