當(dāng)我們的執(zhí)行 Java -jar xxx.jar 的時候底層到底做了什么?
大家都知道我們常用的 SpringBoot? 項(xiàng)目最終在線上運(yùn)行的時候都是通過啟動 java -jar xxx.jar 命令來運(yùn)行的。
那你有沒有想過一個問題,那就是當(dāng)我們執(zhí)行 java -jar? 命令后,到底底層做了什么就啟動了我們的 SpringBoot 應(yīng)用呢?
或者說一個 SpringBoot 的應(yīng)用到底是如何運(yùn)行起來的呢?今天阿粉就帶大家來看下。
認(rèn)識 jar
在介紹 java -jar? 運(yùn)行原理之前我們先看一下 jar? 包里面都包含了哪些內(nèi)容,我們準(zhǔn)備一個 SpringBoot? 項(xiàng)目,通過在 https://start.spring.io/ 上我們可以快速創(chuàng)建一個 SpringBoot? 項(xiàng)目,下載一個對應(yīng)版本和報名的 zip 包。
下載后的項(xiàng)目我們在 pom? 依賴?yán)锩婵梢钥吹接腥缦乱蕾嚕@個插件是我們構(gòu)建可執(zhí)行 jar? 的前提,所以如果想要打包成一個 jar? 那必須在 pom? 有增加這個插件,從 start.spring.io 上創(chuàng)建的項(xiàng)目默認(rèn)是會帶上這個插件的。
接下來我們執(zhí)行 mvn package?,執(zhí)行完過后在項(xiàng)目的 target? 目錄里面我們可以看到有如下兩個 jar? 包,我們分別把這兩個 jar? 解壓一下看看里面的內(nèi)容,.original? 后綴的 jar 需要把后面的 .original? 去掉就可以解壓了。jar? 文件的解壓跟我們平常的 zip? 解壓是一樣的,jar? 文件采用的是 zip? 壓縮格式存儲,所以任何可以解壓 zip? 文件的軟件都可以解壓 jar 文件。
解壓過后,我們對比兩種解壓文件,可以發(fā)現(xiàn),兩個文件夾中的內(nèi)容還是有很大區(qū)別的,如下所示,左側(cè)是 demo-jar-0.0.1-SNAPSHOT.jar? 右側(cè)是對應(yīng)的 original jar。
其中有一些相同的文件夾和文件,比如 META-INF,application.properties? 等,而且我們可以明顯的看到左側(cè)的壓縮包中有項(xiàng)目需要依賴的所有庫文件,存放于 lib 文件夾中。
所以我們可以大膽的猜測,左側(cè)的壓縮包就是 spring-boot-maven-plugin 這個插件幫我們把依賴的庫以及相應(yīng)的文件調(diào)整了一下目錄結(jié)構(gòu)而生成的,事實(shí)其實(shí)也是如此。
java -jar 原理
首先我們要知道的是這個 java -jar? 不是什么新的東西,而是 java? 本身就自帶的命令,而且 java -jar? 命令在執(zhí)行的時候,命令本身對于這個 jar? 是不是 SpringBoot? 項(xiàng)目是不感知的,只要是符合 Java? 標(biāo)準(zhǔn)規(guī)范的 jar 都可以通過這個命令啟動。
而在 Java? 官方文檔顯示,當(dāng) -jar? 參數(shù)存在的時候,jar? 文件資源里面必須包含用 Main-Class? 指定的一個啟動類,而且同樣根據(jù)規(guī)范這個資源文件 MANIFEST.MF? 必須放在 /META-INF/? 目錄下。對比我們上面解壓后的文件,可以看到在左側(cè)的資源文件 MANIFEST.MF 文件中有如圖所示的一行。

可以看到這里的 Main-Class? 屬性配置的是 org.springframework.boot.loader.JarLauncher?,而如果小伙伴更仔細(xì)一點(diǎn)的話,會發(fā)現(xiàn)我們項(xiàng)目的啟動類也在這個文件里面,是通過 Start-Class? 字段來表示的,Start-Class? 這個屬性不是 Java 官方的屬性。
由此我們先大膽的猜測一下,當(dāng)我們在執(zhí)行 java -jar? 的時候,由于我們的 jar? 里面存在 MANIFEST.MF? 文件,并且其中包含了 Main-Class? 屬性且配置了 org.springframework.boot.loader.JarLauncher? 類,通過調(diào)用 JarLauncher? 類結(jié)合 Start-Class 屬性引導(dǎo)出我們項(xiàng)目的啟動類進(jìn)行啟動。接下來我們就通過源碼來驗(yàn)證一下這個猜想。
因?yàn)?nbsp;JarLauncher? 類是在 spring-boot-loader? 模塊,所以我們在 pom 文件中增加如下依賴,就可以下載源碼進(jìn)行跟蹤了。
通過源碼我們可以看到 JarLauncher 類的代碼如下
其中有兩個點(diǎn)我們可以關(guān)注一下,第一個是這個類有一個 main? 方法,這也是為什么 java -jar? 命令可以進(jìn)行引導(dǎo)的原因,畢竟 java? 程序都是通過 main? 方法進(jìn)行運(yùn)行的。其次是這里面有兩個路徑 BOOT-INF/classes/? 和 BOOT-INF/lib/ 這兩個路徑正好是我們的源碼路徑和第三方依賴路徑。
而 JarLauncher? 類里面的 main()? 方法主要是運(yùn)行 Launcher? 里面的 launch() 方法,這幾個類的關(guān)系圖如下所示:
跟著代碼我們可以看到最終調(diào)用的是這個 run() 方法。
而這里的參數(shù) mainClass? 和 launchClass? 都是通過通過下面的邏輯獲取的,都是通過資源文件里面的 Start-Class 來進(jìn)行獲取的,這里正是我們項(xiàng)目的啟動類,由此可以看到我們上面的猜想是正確的。
擴(kuò)展
上面的類圖當(dāng)中我們還可以看到除了有 JarLauncher? 以外還有一個 WarLauncher? 類,確實(shí)我們的 SpringBoot? 項(xiàng)目也是可以配置成 war? 進(jìn)行部署的。我們只需要將打包插件里面的 jar? 更換成 war? 即可。大家可以自行嘗試重新打包解壓進(jìn)行分析,這里 war? 包部署方式只研究學(xué)習(xí)就好了,SpringBoot? 應(yīng)用還是盡量都使用 Jar 的方式進(jìn)行部署。
總結(jié)
通過上面的內(nèi)容我們知道了當(dāng)我們在執(zhí)行 java -jar? 的時候,根據(jù) java? 官方規(guī)范會引導(dǎo) jar? 包里面 MANIFEST.MF? 文件中的 Main-Class? 屬性對應(yīng)的啟動類,該啟動類中必須包含 main() 方法。
而對于我們 SpringBoot? 項(xiàng)目構(gòu)建的 ja?r 包,除了 Main-Class? 屬性外還會有一個 Start-Class? 屬性綁定的是我們項(xiàng)目的啟動類,當(dāng)我們在執(zhí)行 java -jar? 的時候優(yōu)先引導(dǎo)的是 org.springframework.boot.loader.JarLauncher#main? 方法,該方法內(nèi)部會通過引導(dǎo) Start-Class 屬性來啟動我們的應(yīng)用代碼。
通過上面的分析相比大家對于 SpringBoot? 是如何通過 java -jar? 進(jìn)行啟動了有了一個詳細(xì)的了解,下次再有人問 ?SpringBoot 項(xiàng)目是如何啟動的,請把這篇文章轉(zhuǎn)發(fā)給他。