SpringBoot 好“吃”的啟動原理
原創:西狩
撰寫日期 / 修訂日期:2020-12-30 / 2020-12-30
著作權宣告:本文為博主原創文章,遵循 CC BY-NC-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 啟動程序中,還有很多精妙的細節需要繼續推敲,我會在后續文章中,對它們進行剖析,當然,由于自身水平限制,有些比喻可能并不一定十分恰當,希望各位老板見仁見智地去理解,若發現不當之處,歡迎私信溝通交流!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/242713.html
標籤:其他
