SpringBoot 好“吃”的启动原理
原创:西狩
编写日期 / 修订日期:2020-12-30 / 2020-12-30
版权声明:本文为博主原创文章,遵循 CC BY-SA-4.0 版权协议,转载请附上原文出处链接和本声明。
不正经的前言
最近好朋友山治去面试了,晚上回来有些低迷地问我:“小江,你知道 SpringBoot 的启动流程吗?”
我说:“知道呀!从 SpringApplication.run() 方法开始,首先进行实例化,实例化里主要做了4件事:根据calsspath……”
山治抬腿就是一记“恶魔风脚”:SpringBoot 的启动步骤那么多,什么 1、2、3、4,谁能记得住啊!
在被乔巴施展”还我漂漂拳“以后,我痛定思痛,暗暗发誓一定要写篇比美女还好看的文章教会山治,让他吃透这道看似难啃的“菜”。
料理的二三事
选材说明
首先,做一份料理,一定要准备好采购清单。如果只有菜谱没有选材说明,最终做出来的味道可能并没有那么好。哪怕随便做一道家常菜,需要放大葱还是香葱也是有讲究的,而不同年份的葡萄酿制的酒就更不用说了。
正确的选材示例:山治的料理笔记。
错误的选材实例:路飞不看笔记误吃有毒鱼皮。
料理的主要流程
现在,咱们来聊聊吃货该聊的事情:想要做一道菜需要做些什么?
料理三要素
来看一下料理三要素:
- 做饭的场地
- 完美的食材
- 优秀的厨师
当然,虽然在家里一个人就可以做了,但是不要小看料理呀!咱们要聊就聊 big restaurant。比如一家让你难忘的餐厅:海上餐厅“BARATI”。你想要的东西——上面提到的三要素,餐厅后厨全都有。Ok!下面就可以准备料理了。
料理步骤
料理的步骤很简单,包括准备步骤和开始步骤。
料理准备
让我们来安排一场完美的料理。BARATI 料理的准备步骤:
- 选择储存食材的冰箱
- 选择料理的主食材
- 根据点菜单确定料理菜系
- 准备料理需要的菜谱
- 指定处理食材的厨师
- 指定做料理的主厨
料理开始
“高端的食材只需要简单的烹饪”。重头戏开始了!BARATI 料理的工作流程:
- 允许外卖
- 厨师待命
- 加载点菜单的要求(如:不要香菜)
- 准备料理所需的锅碗瓢盆,并通知厨师准备好了
- 忽略没必要了解的信息(如:食材的价格)
- 指定菜品装饰
- 根据菜系,获取对应菜谱
- 设置突发情况报告人(如:点的菜没有了)
- 厨师查看锅碗瓢盆、菜谱和点菜单的要求
- 处理食材
- 料理完成后,根据点菜单的要求定制
- 是否查看客人反馈
- 食材准备就绪
- 通知所有可以干活的厨师
- 准备开工
- 突发报告人处理突发情况(点的菜没有了,需要告诉服务员)
就这样,一顿完美的料理就做好了。
欢迎来到“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);
}
}
- 做饭的场地:SpringApplication
- 完美的食材:所有通过 SpringBoot 自动配置扫描,由 ClassLoader 加载的 Class
- 优秀的厨师:在启动过程中所有 ApplicationListener 和 ApplicationRunner
料理步骤
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
// 1. 料理准备
// 2. 料理开始
return new SpringApplication(primarySources).run(args);
}
- 料理准备:new SpringApplication(primarySources) 方法,SpringApplication 的初始化
- 料理开始: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();
}
选择储存食材的冰箱
可指定的类加载器,与 classpath 相关,默认为null,加载时使用 DefaultResourceLoader
选择料理的主食材
设置传入的主源类
根据点菜单确定料理菜系
通过加载的 class 判断web应用类型(NONE、SERVLET、REACTIVE)
准备料理需要的菜谱
通过 getClassLoader(),查找并加载所有 ApplicationContextInitializer
指定处理食材的厨师
通过 getClassLoader(),查找并加载所有 ApplicationListener
指定做料理的主厨
推断并设置 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;
}
允许外卖
主要允许服务器只提供服务,不提供显示器和界面展示的情况,类似只支持外带,不支持店内就餐
厨师待命
获取所有监听者,进入监听状态
加载点菜单的要求(如:不要香菜)
读取传入 args 参数
准备料理所需的锅碗瓢盆,并通知厨师准备好了
设置环境变量,通知监听者
忽略没必要了解的信息(如:食材的价格)
忽略 BeanInfo 信息,主要为了提高启动速度
指定菜品装饰
设置 Banner
根据菜系,获取对应菜谱
根据应用类型(是 Servlet,还是 Reactive),创建对应上下文
设置突发情况报告人(如:点的菜没有了)
加载SpringBoot异常上报类
厨师查看锅碗瓢盆、菜谱和点菜单的要求
根据环境变量、监听者、启动参数和 Banner,装载上下文
处理食材
刷新上下文
料理完成后,根据点菜单的要求定制
空操作,刷新上下文后的预留扩展点
是否查看客人反馈
设置日志信息打印
食材准备就绪
发布 ApplicationStartedEvent 事件,表示监听者任务完成
通知所有可以干活的厨师
调用 ApplicationRunner,CommandLineRunner 的 run 方法
准备开工
发布 ApplicationReadyEvent 事件,表示应用就绪
突发报告人处理突发情况(点的菜没有了,需要告诉服务员)
如果启动异常,处理 exceptionReporters 中的异常信息,并抛出异常
小结
本篇文章想达到的目的是:将源码映射到现实生活的事件,加深对源码的解读,希望将晦涩难度的源码变成一件有趣的事情。此文只是作为一个吃货的兴趣篇,并不是特别严谨,在 SpringBoot 启动过程中,还有很多精妙的细节需要继续推敲,我会在后续文章中,对它们进行剖析。当然,由于自身水平限制,有些比喻可能并不一定十分恰当,希望各位老板见仁见智地去理解。若发现不当之处,欢迎私信沟通交流!
SpringBoot 好“吃”的启动原理的更多相关文章
- springboot学习入门之三---启动原理
3启动原理 3.1启动类 @SpringBootApplication public class Application { public static void main(String[] args ...
- SpringBoot 2.1.6 启动原理解析(一)
小白第一次写博客,如果有不足之处烦请各位大佬指正. 用了好久的SpringBoot了,一直不清楚它内部的一些启动原理,如何加载yml文件.如何初始化bean的,今天就记录一下,新建了一个springb ...
- SpringBoot内置tomcat启动原理
前言 不得不说SpringBoot的开发者是在为大众程序猿谋福利,把大家都惯成了懒汉,xml不配置了,连tomcat也懒的配置了,典型的一键启动系统,那么tomcat在springb ...
- SpringBoot(1)—启动原理之SpringApplication对象的创建
创建SpringApplication对象 SpringBoot版本为 2.1.1.RELEASE @SpringBootApplication public class SpringbootDemo ...
- SpringBoot(二)启动原理
SpringBoot自动配置模块 该配置模块的主要使用到了SpringFactoriesLoader,即Spring工厂加载器,该对象提供了loadFactoryNames方法,入参为factoryC ...
- springboot之启动原理解析
前言 SpringBoot为我们做的自动配置,确实方便快捷,但是对于新手来说,如果不大懂SpringBoot内部启动原理,以后难免会吃亏.所以这次博主就跟你们一起一步步揭开SpringBoot的神秘面 ...
- SpringBoot之旅第六篇-启动原理及自定义starter
一.引言 SpringBoot的一大优势就是Starter,由于SpringBoot有很多开箱即用的Starter依赖,使得我们开发变得简单,我们不需要过多的关注框架的配置. 在日常开发中,我们也会自 ...
- SpringBoot启动原理及相关流程
一.springboot启动原理及相关流程概览 springboot是基于spring的新型的轻量级框架,最厉害的地方当属自动配置.那我们就可以根据启动流程和相关原理来看看,如何实现传奇的自动配置 二 ...
- 带着萌新看springboot源码12(启动原理 下)
先继续接上一篇,那个启动原理还有一点没说完. 6. afterRefresh(context, applicationArguments); 看这个名字就知道,应该就是ioc容器刷新之后的一些操作了, ...
随机推荐
- mini-web框架-元类-总结(5.4.1)
@ 目录 1.说明 2.代码 关于作者 1.说明 python中万物都是对象 使用python中自带的globals函数返回一个字典 通过这个可以调取当前py文件中的所有东西 当定义一个函数,类,全局 ...
- Web服务器-服务器开发-返回浏览器需要的页面 (3.3.2)
@ 目录 1.说明 2.代码 关于作者 1.说明 使用正则表达式,匹配客户端的请求头 获取到请求的路径 返回对应请求路径的文字 可以使用打开对应文件的方式去返回对应的文件 2.代码 from sock ...
- java_day_02
一.return的两个作用 1.停止当前方法 2.将后面的结果数据返回值还给调用处 二.方法的三种调用格式 1.单独调用:方法名(参数): public class Method { public s ...
- Laravel笔记记录
1.获取当前控制器和action名称 request()->route()->getAction()
- 关于python面试中的设计模式,搞懂这些就足够了
1.什么是设计模式? 设计模式是经过总结.优化,对我们经常遇到的一些编程问题的可重用的解决方案.设计模式不同于类或库可直接作用于代码.相反,它更为的高级,是一种必须在特定的情形下实现的方法模版. 2. ...
- EF Core 三 、 骚操作 (导航属性,内存查询...)
EF Core 高阶操作 本文之前,大家已经阅读了前面的系列文档,对其有了大概的了解 我们来看下EF Core中的一些常见高阶操作,来丰富我们业务实现,从而拥有更多的实现选择 1.EF 内存查找 wh ...
- grpc系列- protobuf详解
Protocol Buffers 是一种与语言.平台无关,可扩展的序列化结构化数据的方法,常用于通信协议,数据存储等等.相较于 JSON.XML,它更小.更快.更简单,因此也更受开发人员的青眯. 基本 ...
- jQuery的data()方法
jQuery文档对.data()方法的描述: As of jQuery 1.4.3 HTML 5 data- attributes will be automatically pulled in to ...
- YourBatman 2020年感悟关键词:科比、裁员、管理层、活着
目录 本文提纲 ✍前言 版本约定 ✍正文 科比 裁员 如何避免被裁? 1.不要迷恋管理,一味追求"当官" 2.别以为裁员只裁一线,不裁管理层 3.即使步入管理,建议不要脱离技术 4 ...
- JVM——GC(垃圾回收)算法
一.垃圾回收的基本概念 垃圾回收(GC,Garbage Collection),指内存中不会再被使用的对象清理掉. 垃圾回收有很多种算法:如引用计数法.标记压缩法.复制算法.分代/分区的思想 二.垃圾 ...