Spring Boot這隻怪物究竟是如何跑起來的?


不得不說 SpringBoot 太複雜了,我原本只想研究一下 SpringBoot 最簡單的 HelloWorld 程序是如何從 main 方法一步一步跑起來的,可是這倒是一個至關深的坑。你能夠試着沿着調用棧代碼一層一層的深刻進去,若是你不打斷點,你根本不知道接下來程序會往哪裏流動。這個不一樣於我研究過去的 Go 語言、Python 語言框架,它們一般都很是直接了當,設計上清晰易懂,代碼寫起來簡單,裏面的實現一樣也很簡單。可是 SpringBoot 不是,它的外表輕巧簡單,可是它的裏面就像一隻巨大的怪獸,這隻怪獸有千百隻腳把本身纏繞在一塊兒,把愛研究源碼的讀者繞的暈頭轉向。可是這 Java 編程的世界 SpringBoot 就是老大哥,你卻不得不服。即便你的心中有千萬頭草泥馬在奔跑,可是它就是天下第一。若是你是一個學院派的程序員,看到這種現象你會懷疑人生,你不得不接受一個規則 —— 受市場最歡迎的未必就是設計的最好的,裏面夾雜着太多其它的非理性因素。
java

通過了一番痛苦的折磨,我仍是把 SpringBoot 的運行原理摸清楚了,這裏分享給你們。程序員

1、Hello World

首先咱們看看 SpringBoot 簡單的 Hello World 代碼,就兩個文件 HelloControll.java 和 Application.java,運行 Application.java 就能夠跑起來一個簡單的 RESTFul Web 服務器了。
面試

當我打開瀏覽器看到服務器正常地將輸出呈如今瀏覽器的時候,我不由大呼 —— SpringBoot 真他媽太簡單了。
spring

可是問題來了,在 Application 的 main 方法裏我壓根沒有任何地方引用 HelloController 類,那麼它的代碼又是如何被服務器調用起來的呢?這就須要深刻到 SpringApplication.run() 方法中看個究竟了。不過即便不看代碼,咱們也很容易有這樣的猜測,SpringBoot 確定是在某個地方掃描了當前的 package,將帶有 RestController 註解的類做爲 MVC 層的 Controller 自動註冊進了 Tomcat Server。編程

小編分類整理了許多java進階學習材料和BAT面試題,須要資料的請加JAVA高階學習Q羣:8515318105;就能領取2019年java架構師進階學習資料和BAT面試題。瀏覽器

還有一個讓人不爽的地方是 SpringBoot 啓動太慢了,一個簡單的 Hello World 啓動竟然還須要長達 5 秒,要是再複雜一些的項目這樣龜漫的啓動速度那真是很差想象了。緩存

再抱怨一下,這個簡單的 HelloWorld 雖然 pom 裏只配置了一個 maven 依賴,可是傳遞下去,它一共依賴了 36 個 jar 包,其中以 spring 開頭的 jar 包有 15 個。說這是依賴地獄真一點不爲過。
springboot

批評到這裏就差很少了,下面就要正是進入主題了,看看 SpringBoot 的 main 方法究竟是如何跑起來的。服務器

2、SpringBoot 的堆棧

瞭解 SpringBoot 運行的最簡單的方法就是看它的調用堆棧,下面這個啓動調用堆棧還不是太深,我沒什麼可抱怨的。
架構

接下來再看看運行時堆棧,看看一個 HTTP 請求的調用棧有多深。不看不知道一看嚇了一大跳!

我經過將 IDE 窗口全屏化,並將其它的控制檯窗口源碼窗口通通最小化,總算勉強一個屏幕裝下了整個調用堆棧。

不過轉念一想,這也不怪 SpringBoot,絕大多數都是 Tomcat 的調用堆棧,跟 SpringBoot 相關的只有不到 10 層。

3、探索 ClassLoader

SpringBoot 還有一個特點的地方在於打包時它使用了 FatJar 技術將全部的依賴 jar 包一塊兒放進了最終的 jar 包中的 BOOT-INF/lib 目錄中,當前項目的 class 被統一放到了 BOOT-INF/classes 目錄中。

這不一樣於咱們平時常用的 maven shade 插件,將全部的依賴 jar 包中的 class 文件解包出來後再密密麻麻的塞進統一的 jar 包中。下面咱們將 springboot 打包的 jar 包解壓出來看看它的目錄結構。

這種打包方式的優點在於最終的 jar 包結構很清晰,全部的依賴一目瞭然。若是使用 maven shade 會將全部的 class 文件混亂堆積在一塊兒,是沒法看清其中的依賴。而最終生成的 jar 包在體積上兩也者幾乎是相等的。

在運行機制上,使用 FatJar 技術運行程序是須要對 jar 包進行改造的,它還須要自定義本身的 ClassLoader 來加載 jar 包裏面 lib 目錄中嵌套的 jar 包中的類。咱們能夠對比一下二者的 MANIFEST 文件就能夠看出明顯差別:

SpringBoot 將 jar 包中的 Main-Class 進行了替換,換成了 JarLauncher。還增長了一個 Start-Class 參數,這個參數對應的類纔是真正的業務 main 方法入口。咱們再看看這個 JarLaucher 具體幹了什麼:

從源碼中能夠看出 JarLaucher 建立了一個特殊的 ClassLoader,而後由這個 ClassLoader 來另啓一個單獨的線程來加載 MainClass 並運行。

小編分類整理了許多java進階學習材料和BAT面試題,須要資料的請加JAVA高階學習Q羣:8515318105;就能領取2019年java架構師進階學習資料和BAT面試題。

又一個問題來了,當 JVM 遇到一個不認識的類,BOOT-INF/lib 目錄裏又有那麼多 jar 包,它是如何知道去哪一個 jar 包里加載呢?咱們繼續看這個特別的 ClassLoader 的源碼:

這裏的 rootClassLoader 就是雙親委派模型裏的 ExtensionClassLoader ,JVM 內置的類會優先使用它來加載。若是不是內置的就去查找這個類對應的 Package。

ClassLoader 會在本地緩存包名和 jar包路徑的映射關係,若是緩存中找不到對應的包名,就必須去 jar 包中挨個遍歷搜尋,這個就比較緩慢了。不過同一個包名只會搜尋一次,下一次就能夠直接從緩存中獲得對應的內嵌 jar 包路徑。

深層 jar 包的內嵌 class 的 URL 路徑長下面這樣,使用感嘆號 ! 分割:

jar:file:/workspace/springboot-demo/target/application.jar!/BOOT-INF/lib/snakeyaml-1.19.jar!/org/yaml/snakeyaml/Yaml.class

不過這個定製的 ClassLoader 只會用於打包運行時,在 IDE 開發環境中 main 方法仍是直接使用系統類加載器加載運行的。

不得不說,SpringbootLoader 的設計仍是頗有意思的,它自己很輕量級,代碼邏輯很獨立沒有其它依賴,它也是 SpringBoot 值得欣賞的點之一。

4、HelloController 自動註冊

還剩下最後一個問題,那就是 HelloController 沒有被代碼引用,它是如何註冊到 Tomcat 服務中去的?它靠的是註解傳遞機制。

SpringBoot 深度依賴註解來完成配置的自動裝配工做,它本身發明了幾十個註解,確實嚴重增長了開發者的心智負擔,你須要仔細閱讀文檔才能知道它是用來幹嗎的。Java 註解的形式和功能是分離的,它不一樣於 Python 的裝飾器是功能性的,Java 的註解就比如代碼註釋,自己只有屬性,沒有邏輯,註解相應的功能由散落在其它地方的代碼來完成,須要分析被註解的類結構才能夠獲得相應註解的屬性。

那註解是又是如何傳遞的呢?

首先 main 方法能夠看到的註解是 SpringBootApplication,這個註解又是由ComponentScan 註解來定義的,ComponentScan 註解會定義一個被掃描的包名稱,若是沒有顯示定義那就是當前的包路徑。SpringBoot 在遇到 ComponentScan 註解時會掃描對應包路徑下面的全部 Class,根據這些 Class 上標註的其它註解繼續進行後續處理。當它掃到 HelloController 類時發現它標註了 RestController 註解。

而 RestController 註解又標註了 Controller 註解。SpringBoot 對 Controller 註解進行了特殊處理,它會將 Controller 註解的類當成 URL 處理器註冊到 Servlet 的請求處理器中,在建立 Tomcat Server 時,會將請求處理器傳遞進去。HelloController 就是如此被自動裝配進 Tomcat 的。

掃描處理註解是一個很是繁瑣骯髒的活計,特別是這種用註解來註解註解(繞口)的高級使用方法,這種方法要少用慎用。SpringBoot 中有大量的註解相關代碼,企圖理解這些代碼是乏味無趣的沒有必要的,它只會把你的原本清醒的腦殼搞暈。SpringBoot 對於習慣使用的同窗來講它是很是方便的,可是其內部實現代碼不要輕易模仿,那絕對算不上模範 Java 代碼。

最後老錢表示本身真的很討厭 SpringBoot 這隻怪獸,可是很無奈,這個世界人人都在使用它。這就比如老人們經常告誡年輕人的那句話:若是你改變不了世界,那就先適應這個世界吧!

小編分類整理了許多java進階學習材料和BAT面試題,須要資料的請加JAVA高階學習Q羣:8515318105;就能領取2019年java架構師進階學習資料和BAT面試題。

相關文章
相關標籤/搜索