一個(gè) JAR 就能跑!Spring Boot 自運(yùn)行的秘密,你真的懂了嗎?
在 Java 世界里,應(yīng)用的部署一向被認(rèn)為是“繁瑣的代名詞”:依賴沖突、服務(wù)器配置、JAR 與 WAR 的混用問(wèn)題層出不窮。 而 Spring Boot 橫空出世后,一句簡(jiǎn)單的命令:
java -jar myapp.jar竟能讓整個(gè) Web 應(yīng)用“即裝即跑”。 你或許已經(jīng)無(wú)數(shù)次使用過(guò)這種方式啟動(dòng)服務(wù),但它為什么能做到“一包走天下”? 這篇文章,我們就帶你從底層打包機(jī)制、啟動(dòng)流程到類加載策略,完整解析 Spring Boot 的自運(yùn)行秘密。
Spring Boot Fat JAR 解剖:一個(gè)能自己跑的 JAR 是怎么生出來(lái)的?
Spring Boot 構(gòu)建的可執(zhí)行包,本質(zhì)上是一種特殊結(jié)構(gòu)的 Fat JAR(胖 JAR)。 它并不僅僅包含業(yè)務(wù)代碼,還把所有依賴、類加載器以及嵌入式服務(wù)器一并封裝進(jìn)去——就像把整個(gè)應(yīng)用系統(tǒng)塞進(jìn)了一個(gè)“集裝箱”。
當(dāng)我們解壓一個(gè)典型的 Spring Boot JAR,可以看到如下結(jié)構(gòu)(路徑采用 Linux 風(fēng)格):
/BOOT-INF/classes/        # 編譯后的業(yè)務(wù)代碼與配置文件
/BOOT-INF/lib/            # 所有項(xiàng)目依賴的第三方庫(kù)
/org/springframework/boot/loader/  # Spring Boot 啟動(dòng)加載器核心
/META-INF/MANIFEST.MF     # 指定應(yīng)用元數(shù)據(jù)和啟動(dòng)入口其中:
BOOT-INF/classes:包含你寫的 Controller、Service、Repository 等核心邏輯;BOOT-INF/lib:聚合項(xiàng)目依賴,如 Spring 框架核心庫(kù)、數(shù)據(jù)庫(kù)驅(qū)動(dòng)、日志組件;org/springframework/boot/loader:封裝了 Spring Boot 的引導(dǎo)器類(JarLauncher、LaunchedURLClassLoader 等);- 嵌入式 Tomcat/Jetty:被直接打入包內(nèi),無(wú)需外部服務(wù)器即可運(yùn)行。
 
這種結(jié)構(gòu)正是 Spring Boot 能“一鍵運(yùn)行”的核心所在:應(yīng)用邏輯 + 基礎(chǔ)依賴 + 容器環(huán)境一體化打包。
META-INF/MANIFEST.MF:JAR 的“啟動(dòng)指南”
Fat JAR 的真正“魔法開關(guān)”藏在 /META-INF/MANIFEST.MF 文件中。 這個(gè)文件就像 JAR 包的身份證,告訴 JVM 如何正確啟動(dòng)應(yīng)用。
打開它,你會(huì)看到關(guān)鍵配置:
Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: com.icoderoad.demo.DemoApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/解釋如下:
Main-Class:指定 Spring Boot 的啟動(dòng)加載器JarLauncher;Start-Class:你的主應(yīng)用類(帶@SpringBootApplication注解);Spring-Boot-Classes/Spring-Boot-Lib:定義了程序代碼與依賴庫(kù)的加載路徑;- 額外屬性如 
Spring-Boot-Layers-Index則用于支持 Docker 鏡像的分層優(yōu)化。 
這份元數(shù)據(jù)文件讓 JVM 能在執(zhí)行 java -jar 時(shí),準(zhǔn)確找到加載器與主類,實(shí)現(xiàn)自動(dòng)運(yùn)行。
Maven 打包背后的“魔術(shù)師”:spring-boot-maven-plugin
Spring Boot 的 Fat JAR 不是憑空生成的,而是 Maven 插件在構(gòu)建階段的“再加工”成果。 構(gòu)建流程核心在 spring-boot-maven-plugin 的 <goal>repackage</goal> 階段。
示例配置如下:
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <version>2.7.5</version>
            <executions>
                <execution>
                    <goals>
                        <goal>repackage</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>當(dāng)執(zhí)行:
mvn clean package時(shí),Maven 會(huì)先構(gòu)建普通 JAR,然后由該插件的 Repackager 類進(jìn)行“二次封裝”:
- 拷貝依賴庫(kù) → 
/BOOT-INF/lib/ - 編譯業(yè)務(wù)代碼 → 
/BOOT-INF/classes/ - 注入啟動(dòng)類與元信息 → 
/META-INF/MANIFEST.MF - 將加載器類寫入 
/org/springframework/boot/loader/ 
最終生成一個(gè)可直接運(yùn)行的單體包,即 Spring Boot 的 Fat JAR。
Repackager 內(nèi)幕:Spring Boot 如何“再包裝”普通 JAR?
Spring Boot 的 Repackager 類是整個(gè)打包過(guò)程的中樞。 它的職責(zé)類似“自動(dòng)搬運(yùn)工”,把項(xiàng)目產(chǎn)物重新組織成可執(zhí)行結(jié)構(gòu)。
打包核心邏輯如下:
- 從 Maven 原始 JAR 中讀取類文件;
 - 解析依賴樹,復(fù)制依賴至 
/BOOT-INF/lib/; - 寫入應(yīng)用類至 
/BOOT-INF/classes/; - 生成新的 
MANIFEST.MF; - 最終寫入啟動(dòng)入口為 
org.springframework.boot.loader.JarLauncher。 
當(dāng)你遇到啟動(dòng)失敗或依賴未打入時(shí),通常是 Repackager 的布局定義或路徑出錯(cuò)。 理解這一機(jī)制能幫助你快速定位打包異常。
Spring Boot 啟動(dòng)原理:從 JarLauncher 到應(yīng)用啟動(dòng)
執(zhí)行命令:
java -jar app.jarJVM 會(huì)讀取 MANIFEST.MF,發(fā)現(xiàn)入口是 org.springframework.boot.loader.JarLauncher,于是從該類啟動(dòng):
public class JarLauncher extends ExecutableArchiveLauncher {
    public static void main(String[] args) throws Exception {
        new JarLauncher().launch(args);
    }
    protected JarLauncher() throws Exception { }
}這一啟動(dòng)鏈條如下:
- JarLauncher.main() 啟動(dòng);
 - 調(diào)用 ExecutableArchiveLauncher.launch();
 - 創(chuàng)建 LaunchedURLClassLoader;
 - 加載 
/BOOT-INF/lib下所有依賴; - 反射調(diào)用 
Start-Class(如com.icoderoad.demo.DemoApplication)的main(); - 啟動(dòng)嵌入式服務(wù)器(Tomcat/Jetty),初始化 Spring 容器。
 
整個(gè)過(guò)程實(shí)現(xiàn)了從“自引導(dǎo)加載器”到“應(yīng)用啟動(dòng)”的全自動(dòng)化封裝。
依賴加載與內(nèi)嵌服務(wù)器初始化
Spring Boot 的 LaunchedURLClassLoader 能突破 JVM 原生類加載限制,從嵌套的 JAR 中直接加載資源。 它通過(guò) Spring-Boot-Lib 屬性遍歷 /BOOT-INF/lib/,將所有依賴動(dòng)態(tài)加入類路徑。
加載完成后,Spring Boot 自動(dòng)配置模塊開始發(fā)揮作用:
- 讀取 
application.yml或application.properties; - 啟動(dòng)內(nèi)嵌 Tomcat;
 - 初始化 Spring 容器;
 - 執(zhí)行帶 
@SpringBootApplication注解的主類。 
例如:
server.port=8081
server.servlet.context-path=/myapp當(dāng)啟動(dòng)后,你的應(yīng)用即通過(guò) http://localhost:8081/myapp 直接訪問(wèn),無(wú)需額外部署。
JVM 類加載機(jī)制回顧:Spring Boot 如何打破常規(guī)?
JVM 類加載過(guò)程分為:加載 → 驗(yàn)證 → 準(zhǔn)備 → 解析 → 初始化。 傳統(tǒng)機(jī)制遵循 雙親委派模型,即子加載器先請(qǐng)求父加載器查找類,若未找到再自行加載。
而 Spring Boot 的可執(zhí)行 JAR 結(jié)構(gòu)中,所有依賴都打包在內(nèi)部 JAR 中。 如果繼續(xù)沿用雙親委派,會(huì)導(dǎo)致版本沖突或無(wú)法加載嵌套資源。 因此,Spring Boot 實(shí)現(xiàn)了一個(gè)自定義加載器 LaunchedURLClassLoader,改變了加載順序:
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    synchronized (getClassLoadingLock(name)) {
        Class<?> loadedClass = findLoadedClass(name);
        if (loadedClass == null) {
            try {
                // 優(yōu)先從自身 JAR 加載
                loadedClass = findClass(name);
            } catch (ClassNotFoundException ex) {
                // 加載失敗時(shí)再委派父加載器
                loadedClass = super.loadClass(name, false);
            }
        }
        if (resolve) {
            resolveClass(loadedClass);
        }
        return loadedClass;
    }
}這種機(jī)制讓 Spring Boot 優(yōu)先加載內(nèi)部依賴,防止與系統(tǒng)類庫(kù)沖突,也讓嵌套 JAR 的資源可被正常訪問(wèn)。
JarURLConnection:讀取嵌套 JAR 的秘密武器
在傳統(tǒng) JVM 中,類加載器無(wú)法直接讀取“JAR 中的 JAR”。 Spring Boot 通過(guò) JarURLConnection 實(shí)現(xiàn)了資源訪問(wèn)的橋梁,使得 /BOOT-INF/lib/*.jar 中的文件可被直接讀取。
這樣,配置文件、類、靜態(tài)資源等都能像普通文件一樣加載。 這一步,是 Spring Boot 能將整個(gè)世界“打包帶走”的最后一環(huán)。
結(jié)語(yǔ):一個(gè) JAR 的優(yōu)雅哲學(xué)
Spring Boot 的可執(zhí)行 JAR 之所以強(qiáng)大,不僅因?yàn)椤澳芘堋保?nbsp;更因?yàn)樗鼘?fù)雜的構(gòu)建、依賴、加載與運(yùn)行過(guò)程無(wú)縫整合在了一起:
- 打包層面:Maven 插件自動(dòng)封裝;
 - 運(yùn)行層面:自定義類加載器管理依賴;
 - 部署層面:內(nèi)嵌服務(wù)器開箱即用。
 
它讓 Java 應(yīng)用徹底告別傳統(tǒng)的 WAR 部署模式, 以“一包一世界”的理念,讓開發(fā)與運(yùn)維之間的鴻溝被徹底抹平。
所以,下次當(dāng)你敲下:
java -jar myapp.jar不妨想一想: 在這短短一秒鐘背后,Spring Boot 正默默完成了一個(gè) JVM 級(jí)的奇跡。















 
 
 











 
 
 
 