當我們的執行 java -jar xxx.jar 的時候底層到底做了什麼?

2022-12-07 06:00:50

大家都知道我們常用的 SpringBoot 專案最終線上上執行的時候都是通過啟動 java -jar xxx.jar 命令來執行的。

那你有沒有想過一個問題,那就是當我們執行 java -jar 命令後,到底底層做了什麼就啟動了我們的 SpringBoot 應用呢?

或者說一個 SpringBoot 的應用到底是如何執行起來的呢?今天阿粉就帶大家來看下。

認識 jar

在介紹 java -jar 執行原理之前我們先看一下 jar 包裡面都包含了哪些內容,我們準備一個 SpringBoot 專案,通過在 https://start.spring.io/ 上我們可以快速建立一個 SpringBoot 專案,下載一個對應版本和報名的 zip 包。

下載後的專案我們在 pom 依賴裡面可以看到有如下依賴,這個外掛是我們構建可執行 jar 的前提,所以如果想要打包成一個 jar 那必須在 pom 有增加這個外掛,從 start.spring.io 上建立的專案預設是會帶上這個外掛的。

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

接下來我們執行 mvn package,執行完過後在專案的 target 目錄裡面我們可以看到有如下兩個 jar 包,我們分別把這兩個 jar 解壓一下看看裡面的內容,.original 字尾的 jar 需要把後面的 .original 去掉就可以解壓了。jar 檔案的解壓跟我們平常的 zip 解壓是一樣的,jar 檔案採用的是 zip 壓縮格式儲存,所以任何可以解壓 zip 檔案的軟體都可以解壓 jar 檔案。

解壓過後,我們對比兩種解壓檔案,可以發現,兩個資料夾中的內容還是有很大區別的,如下所示,左側是 demo-jar-0.0.1-SNAPSHOT.jar 右側是對應的 original jar

其中有一些相同的資料夾和檔案,比如 META-INFapplication.properties 等,而且我們可以明顯的看到左側的壓縮包中有專案需要依賴的所有庫檔案,存放於 lib 資料夾中。

所以我們可以大膽的猜測,左側的壓縮包就是 spring-boot-maven-plugin 這個外掛幫我們把依賴的庫以及相應的檔案調整了一下目錄結構而生成的,事實其實也是如此。

java -jar 原理

首先我們要知道的是這個 java -jar 不是什麼新的東西,而是 java 本身就自帶的命令,而且 java -jar 命令在執行的時候,命令本身對於這個 jar 是不是 SpringBoot 專案是不感知的,只要是符合 Java 標準規範的 jar 都可以通過這個命令啟動。

而在 Java 官方檔案顯示,當 -jar 引數存在的時候,jar 檔案資源裡面必須包含用 Main-Class 指定的一個啟動類,而且同樣根據規範這個資原始檔 MANIFEST.MF 必須放在 /META-INF/ 目錄下。對比我們上面解壓後的檔案,可以看到在左側的資原始檔 MANIFEST.MF 檔案中有如圖所示的一行。

![](/Users/silence/Library/Application Support/typora-user-images/image-20221206214011822.png)

可以看到這裡的 Main-Class 屬性設定的是 org.springframework.boot.loader.JarLauncher,而如果小夥伴更仔細一點的話,會發現我們專案的啟動類也在這個檔案裡面,是通過 Start-Class 欄位來表示的,Start-Class 這個屬性不是 Java 官方的屬性。

由此我們先大膽的猜測一下,當我們在執行 java -jar 的時候,由於我們的 jar 裡面存在 MANIFEST.MF 檔案,並且其中包含了 Main-Class 屬性且設定了 org.springframework.boot.loader.JarLauncher 類,通過呼叫 JarLauncher 類結合 Start-Class 屬性引匯出我們專案的啟動類進行啟動。接下來我們就通過原始碼來驗證一下這個猜想。

因為 JarLauncher 類是在 spring-boot-loader 模組,所以我們在 pom 檔案中增加如下依賴,就可以下載原始碼進行跟蹤了。

<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-loader</artifactId>
			<scope>provided</scope>
</dependency>

通過原始碼我們可以看到 JarLauncher 類的程式碼如下

package org.springframework.boot.loader;

import org.springframework.boot.loader.archive.Archive;
import org.springframework.boot.loader.archive.Archive.EntryFilter;

public class JarLauncher extends ExecutableArchiveLauncher {

	static final EntryFilter NESTED_ARCHIVE_ENTRY_FILTER = (entry) -> {
		if (entry.isDirectory()) {
			return entry.getName().equals("BOOT-INF/classes/");
		}
		return entry.getName().startsWith("BOOT-INF/lib/");
	};

	public JarLauncher() {
	}

	protected JarLauncher(Archive archive) {
		super(archive);
	}

	@Override
	protected boolean isPostProcessingClassPathArchives() {
		return false;
	}

	@Override
	protected boolean isNestedArchive(Archive.Entry entry) {
		return NESTED_ARCHIVE_ENTRY_FILTER.matches(entry);
	}

	@Override
	protected String getArchiveEntryPathPrefix() {
		return "BOOT-INF/";
	}

	public static void main(String[] args) throws Exception {
		new JarLauncher().launch(args);
	}

}

其中有兩個點我們可以關注一下,第一個是這個類有一個 main 方法,這也是為什麼 java -jar 命令可以進行引導的原因,畢竟 java 程式都是通過 main 方法進行執行的。其次是這裡面有兩個路徑 BOOT-INF/classes/BOOT-INF/lib/ 這兩個路徑正好是我們的原始碼路徑和第三方依賴路徑。

JarLauncher 類裡面的 main() 方法主要是執行 Launcher 裡面的 launch() 方法,這幾個類的關係圖如下所示

跟著程式碼我們可以看到最終呼叫的是這個 run() 方法

而這裡的引數 mainClasslaunchClass 都是通過通過下面的邏輯獲取的,都是通過資原始檔裡面的 Start-Class 來進行獲取的,這裡正是我們專案的啟動類,由此可以看到我們上面的猜想是正確的。

擴充套件

上面的類圖當中我們還可以看到除了有 JarLauncher 以外還有一個 WarLauncher 類,確實我們的 SpringBoot 專案也是可以設定成 war 進行部署的。我們只需要將打包外掛裡面的 jar 更換成 war 即可。大家可以自行嘗試重新打包解壓進行分析,這裡 war 包部署方式只研究學習就好了,SpringBoot 應用還是儘量都使用 Jar 的方式進行部署。

總結

通過上面的內容我們知道了當我們在執行 java -jar 的時候,根據 java 官方規範會引導 jar 包裡面 MANIFEST.MF 檔案中的 Main-Class 屬性對應的啟動類,該啟動類中必須包含 main() 方法。

而對於我們 SpringBoot 專案構建的 jar 包,除了 Main-Class 屬性外還會有一個 Start-Class 屬性繫結的是我們專案的啟動類,當我們在執行 java -jar 的時候優先引導的是 org.springframework.boot.loader.JarLauncher#main 方法,該方法內部會通過引導 Start-Class 屬性來啟動我們的應用程式碼。

通過上面的分析相比大家對於 SpringBoot 是如何通過 java -jar 進行啟動了有了一個詳細的瞭解,下次再有人問你 SpringBoot 專案是如何啟動的,請把這篇文章轉發給他。如果大家覺得我們的文章有幫助,歡迎點贊分享評論轉發,一鍵三連。


更多優質內容歡迎關注公眾號【Java 極客技術】,我準備了一份面試資料,回覆【bbbb07】免費領取。希望能在這寒冷的日子裡,幫助到大家。