揭秘!Spring Boot Jar 文件為何能直接運行?
在傳統(tǒng)的 Java 應(yīng)用程序開發(fā)和部署場景中,開發(fā)人員通常需要經(jīng)過一系列復(fù)雜的步驟,才能成功將應(yīng)用程序部署到生產(chǎn)環(huán)境中。
例如,對于基于 Servlet 規(guī)范的 Java Web 應(yīng)用程序,在開發(fā)完成后,通常會將其打包為 WAR 文件,然后部署到像 Apache Tomcat 或 Jetty 這樣的 Web 容器中。
在此過程中,開發(fā)人員需要管理應(yīng)用程序本身的已編譯工件,處理各種第三方依賴項的版本和加載順序,并適當配置服務(wù)器,以確保應(yīng)用程序正常運行。
隨著 Spring Boot 的出現(xiàn),它通過開箱即用和約定優(yōu)于配置的理念,徹底改變了 Java 應(yīng)用程序開發(fā)體驗。
Spring Boot 的一個標志性功能是,應(yīng)用程序可以被打包為直接可執(zhí)行的 JAR 文件,而無需外部容器。
當我們提到“Spring Boot JAR 可以直接運行”時,會引發(fā)一個問題:是什么機制使得一個簡單的命令行操作就能啟動一個完整的 Web 服務(wù)或任何類型的 Java 應(yīng)用程序?
本文將深入探討 Spring Boot 的打包過程及其運行原理,揭示其 JAR 文件如何巧妙地集成依賴項、嵌入 Web 容器并實現(xiàn)自動配置。
這使開發(fā)人員能夠快速將應(yīng)用程序部署到任何支持 Java 的環(huán)境中。
圖片
Spring Boot JAR 包的基本概念
Fat JAR(也稱為 Uber JAR,詼諧地被稱為“胖 JAR”)是一種特殊類型的 Java 存檔(JAR)文件,它將應(yīng)用程序所需的所有依賴項與應(yīng)用程序自身的類文件結(jié)合到一個 JAR 文件中。
在 Spring Boot 的上下文中,F(xiàn)at JAR 用于構(gòu)建一個完全自包含且獨立可執(zhí)行的應(yīng)用程序包。
這種 JAR 文件不僅包括項目的主代碼,還包括所有必要的第三方庫、資源文件以及運行時所需的所有其他組件。
Fat JAR 的核心特性是“自包含”,這意味著部署應(yīng)用程序只需分發(fā)這一個文件,而無需單獨處理眾多依賴庫。
這大大簡化了應(yīng)用程序的快速部署和遷移,尤其適合云部署或沒有網(wǎng)絡(luò)訪問環(huán)境的安裝。
相較之下,普通的 JAR 文件通常只包含應(yīng)用程序的一個模塊或部分,主要用于封裝和組織 Java 類及相關(guān)資源。
在 Java 生態(tài)系統(tǒng)中,普通的 JAR 文件可能只是一個庫或一組相關(guān)功能的集合,但不會包含其他依賴的 JAR 文件,運行時需要在類路徑中提供其他相關(guān)庫。
與此相比,F(xiàn)at JAR 通過將所有依賴項都包含在自身內(nèi)部,解決了依賴管理問題,避免了因類路徑設(shè)置不當導(dǎo)致的“缺少類”或“類未找到”問題。
在 Spring Boot 項目中,可以使用 Maven 或 Gradle 插件輕松構(gòu)建 Fat JAR,使最終的 JAR 文件成為一個真正的“一站式”解決方案,只需通過 java -jar 命令即可啟動整個應(yīng)用程序,而無需預(yù)先配置復(fù)雜的類路徑環(huán)境。
Spring Boot 應(yīng)用程序的打包機制
Spring Boot 應(yīng)用程序的打包機制充分利用了 Maven 或 Gradle 等構(gòu)建工具的強大功能,旨在簡化傳統(tǒng)的 Java 應(yīng)用程序構(gòu)建和部署過程。
這種機制的核心在于創(chuàng)建一個可執(zhí)行的 Fat JAR,使開發(fā)人員能夠輕松地將整個 Spring Boot 應(yīng)用程序及其依賴項打包成一個文件,從而實現(xiàn)一鍵啟動和便捷部署。
以 Maven 打包為例:
對于使用 Maven 構(gòu)建的 Spring Boot 應(yīng)用程序,spring-boot-maven-plugin 是負責處理 Fat JAR 構(gòu)建的關(guān)鍵插件。在 pom.xml 文件中,通常會看到以下配置:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<mainClass>${start-class}</mainClass>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
通過使用 mvn package 命令,Maven 將首先按照標準流程構(gòu)建項目。
隨后,spring-boot-maven-plugin 將執(zhí)行 repackage 目標,該目標會將生成的標準 JAR 文件重新打包為一個包含所有依賴項和適當啟動信息的 Fat JAR。
生成的 JAR 隨后可以直接通過 java -jar 命令啟動。
Spring Boot 應(yīng)用程序的打包機制確保生成的包不僅包括項目本身的類,還包括所有必要的運行時依賴項,以及特定的元數(shù)據(jù)(如 MANIFEST.MF 文件中的主類信息)。
這一功能大大簡化了部署過程,并有助于提高應(yīng)用程序的可移植性和可維護性。Fat JAR 的內(nèi)容包括:
圖片
META-INF/:包含 MANIFEST.MF 文件和其他元數(shù)據(jù),其中 Main-Class 屬性指向 Spring Boot 啟動類。
BOOT-INF/classes/:存儲項目的類文件和資源文件。
BOOT-INF/lib/:保存所有依賴的 JAR 文件,包括 Spring Boot 啟動依賴項和其他第三方庫。
(如果項目中有靜態(tài)資源文件,在 BOOT-INF 下還會有相應(yīng)的目錄,如 static、templates等。)
Spring Boot 啟動器與加載機制
Spring Boot 應(yīng)用程序 JAR 能夠直接運行,主要依賴于其啟動器和加載機制,這通過 MANIFEST.MF 文件和內(nèi)部的類加載邏輯來實現(xiàn)。
什么是 MANIFEST.MF 文件?
MANIFEST.MF 是 JAR 文件中的標準元數(shù)據(jù)文件,它包含了 JAR 包的基本信息和運行指令。
在 Spring Boot 應(yīng)用程序的 JAR 中,MANIFEST.MF 文件尤為重要,因為它設(shè)置了 Main-Class屬性,指明了用于啟動整個應(yīng)用程序的類。
這個類通常是 org.springframework.boot.loader.JarLauncher 或者 Spring Boot 提供的其他啟動類。
圖片
Main-Class 屬性指向的是 JarLauncher 類,這個類是 Spring Boot 自定義類加載系統(tǒng)的一部分。
JarLauncher 繼承了 org.springframework.boot.loader.Launcher,專門用于啟動以 Fat JAR 打包的 Spring Boot 應(yīng)用程序。
JarLauncher 負責創(chuàng)建類加載器 LaunchedURLClassLoader。
圖片
圖片
圖片
當使用 java -jar 命令執(zhí)行 Spring Boot JAR 文件時,JVM 使用 MANIFEST.MF 文件中的 Main-Class 屬性來啟動指定的啟動器。
圖片
JarLauncher 從源代碼中檢索 MainClass。
Spring Boot 啟動類加載器使用的 LaunchedURLClassLoader 首先讀取 MANIFEST.MF 中的其他屬性,例如 Start-Class(標識應(yīng)用程序的實際主類)和 Spring-Boot-Lib(指向內(nèi)部依賴庫的位置)。
圖片
圖片
啟動類加載器的工作流程
- 當啟動類加載器啟動時,它根據(jù) MANIFEST.MF 文件中的信息組織類路徑,確保所有內(nèi)部依賴庫被正確加載。
- 加載器區(qū)分 BOOT-INF/classes 中的應(yīng)用程序類和 BOOT-INF/lib 中的依賴庫,并分別處理它們,將它們添加到類加載器的搜索路徑中。
- 加載器加載并執(zhí)行實際的 Start-Class,即應(yīng)用程序的主類,從而觸發(fā) Spring Boot 框架的初始化和啟動過程。例如,在一個示例應(yīng)用程序中,主類可能是:com.springboot.base.SpringBootBaseApplication。
Spring Boot 的啟動器與加載器機制有效地管理并執(zhí)行自包含的 JAR 文件。
我們不再需要擔心復(fù)雜的類路徑配置和依賴加載,一個簡單的命令就可以啟動一個完整的獨立應(yīng)用程序。
嵌入式 Web 容器
Spring Boot 的一個關(guān)鍵功能是其無縫集成并嵌入各種輕量級 Web 容器,如 Apache Tomcat、Jetty、Undertow 和 Reactor Netty(用于響應(yīng)式編程模型)。
嵌入式 Web 容器的引入極大地簡化了 Web 應(yīng)用程序的部署過程,避免了單獨安裝和配置 Web 服務(wù)器的需求(如過去必須本地安裝 Tomcat)。
當 Spring Boot 應(yīng)用程序包含 spring-boot-starter-web 依賴時,嵌入式 Web 容器會自動配置并啟動。
在 Spring Boot 應(yīng)用程序啟動過程中,嵌入式容器被初始化并綁定到特定端口,以提供 HTTP 服務(wù)。
Spring Boot 嵌入式 Web 容器的優(yōu)勢包括將 Web 容器嵌入到應(yīng)用程序中,從而簡化了部署,僅需一個 JAR 文件即可在干凈的環(huán)境中運行應(yīng)用程序。
這避免了與現(xiàn)有 Web 服務(wù)器版本或配置不當?shù)臎_突。同時,它加速了啟動過程,特別是在開發(fā)和測試階段,實現(xiàn)了接近即時的熱重啟。
此外,它通過在開發(fā)和生產(chǎn)環(huán)境中使用相同的 Web 容器,提高了應(yīng)用程序的穩(wěn)定性,減少了由環(huán)境差異引起的問題。
盡管是嵌入式的,容器仍然可以完全配置,如端口設(shè)置、連接限制和 SSL 配置,以滿足各種需求。
嵌入式 Web 容器真正實現(xiàn)了 Spring Boot 的“開箱即用”理念。
自動配置和類路徑掃描
Spring Boot 的核心特性之一是其強大的自動配置能力,使應(yīng)用程序幾乎無需配置就能快速啟動和運行。
當應(yīng)用程序啟動時,Spring Boot 會讀取 resource/META-INF/spring.factories 文件,該文件列出了所有可用的自動配置類。
當它檢測到應(yīng)用環(huán)境中的相應(yīng)自動配置類時,這些類將生效,通過帶有 @Configuration 注解的類創(chuàng)建并注冊 Bean 到 Spring 容器中,從而實現(xiàn)自動 Bean 配置。
注意:從 Spring Boot 3.x 開始,自動配置類將從*org.springframework.boot.autoconfigure.AutoConfiguration.imports* 讀取,而不是 *resource/META-INF/spring.factories* 。有關(guān)更多詳細信息,請參閱文章: 華為面試:如何在 Spring Boot 中自定義 Starter?。
Spring Boot 還使用條件注解(如 @ConditionalOnClass、@ConditionalOnMissingBean 等)來智能地決定何時應(yīng)用特定的配置。
這些注解可以根據(jù)類路徑中是否存在特定的類、系統(tǒng)屬性或環(huán)境變量來激活特定的自動配置類。
這意味著只有在滿足某些條件時,才會創(chuàng)建并注入 Bean。
應(yīng)用程序的主類帶有 @SpringBootApplication 注解,這是一個組合注解,包含了 @SpringBootConfiguration、@EnableAutoConfiguration 和 @ComponentScan 的功能。具體來說:
- @SpringBootConfiguration:一個 Spring 配置類,能夠替代 @Configuration 注解,聲明當前類為 Spring 配置類,包含一系列 @Bean 方法或 @ConfigurationProperties 配置。
- @EnableAutoConfiguration:啟用自動配置功能,指示 Spring Boot 根據(jù)應(yīng)用程序類路徑中的依賴項自動配置 Bean。
- @ComponentScan:自動掃描和管理 Spring 組件,包括帶有 @Service、@Repository、@Controller 和 @Component 注解的類。此注解允許 Spring Boot 自動發(fā)現(xiàn)和管理應(yīng)用程序中的各種組件,并將其注冊為 Spring 容器中的 Bean。
通過這些機制,Spring Boot 能夠智能識別項目依賴,自動配置 Bean,并確保通過類路徑掃描正確初始化和管理所有相關(guān)組件和服務(wù)。
這使得我們可以專注于開發(fā)業(yè)務(wù)邏輯,而無需擔心基礎(chǔ)設(shè)施級別的配置問題。
結(jié)論
Spring Boot 應(yīng)用程序打包為 JAR 文件后,能夠通過 java -jar 命令直接運行,這得益于構(gòu)建過程中進行的特殊設(shè)計和配置。具體來說:
- Fat/Uber JAR:Spring Boot 使用 spring-boot-maven-plugin(或相應(yīng)的 Gradle 插件)將項目及其所有依賴項打包成一個自包含的 JAR 文件,稱為 "Fat JAR" 或 "Uber JAR"。這意味著它不僅包含自己的類文件,還包括運行應(yīng)用程序所需的所有第三方庫。
- Manifest.MF:在打包過程中,該插件修改了 JAR 內(nèi)的元數(shù)據(jù)文件 MANIFEST.MF。在 MANIFEST.MF 中,Main-Class 屬性被指定,指向內(nèi)置的 Spring Boot 啟動類(如 org.springframework.boot.loader.JarLauncher),該類知道如何正確啟動 Spring Boot 應(yīng)用程序。
- 嵌入式 Servlet 容器:Spring Boot 默認集成了嵌入式 Web 容器,如 Tomcat、Jetty 或 Undertow,使得 Web 應(yīng)用程序無需外部服務(wù)器環(huán)境即可運行。
- 啟動類加載器:當使用 java -jar 命令運行 Spring Boot 應(yīng)用程序時,JVM 會根據(jù) MANIFEST.MF 中的 Main-Class 屬性找到并運行指定的啟動類。該啟動類加載器能夠解壓并加載內(nèi)部依賴庫,并找到實際的主應(yīng)用程序類(無論是在 spring-boot-starter-parent中,還是帶有 @SpringBootApplication 注解的類),執(zhí)行其 main 方法。
- 類路徑掃描和自動配置:Spring Boot 應(yīng)用程序使用特定的類路徑掃描機制和自動配置功能,在啟動時識別并自動配置應(yīng)用程序所需的服務(wù)和組件,大大簡化了傳統(tǒng) Java 應(yīng)用程序的配置和部署過程。
通過精心設(shè)計的打包過程和啟動類,Spring Boot 實現(xiàn)了生成的 JAR 文件作為獨立應(yīng)用程序運行,顯著降低了部署和操作的復(fù)雜性。