# Spring Boot中的JAR是怎樣的
## 引言
在現代Java開發中,Spring Boot已經成為構建企業級應用的首選框架。其核心特性之一就是能夠將應用程序打包成一個獨立的、可執行的JAR文件(通常稱為"fat JAR"或"uber JAR")。這種打包方式徹底改變了傳統Java應用的部署模式,使開發者能夠輕松地構建、發布和運行Spring Boot應用。本文將深入探討Spring Boot JAR的結構、工作原理、創建過程以及相關的高級主題,幫助開發者全面理解這一關鍵技術。
## 一、傳統JAR與Spring Boot可執行JAR的區別
### 1.1 標準JAR文件結構
傳統的Java JAR(Java Archive)文件遵循Zip格式,包含編譯后的.class文件、資源文件和可選的META-INF/MANIFEST.MF清單文件。典型結構如下:
myapp.jar ├── META-INF │ └── MANIFEST.MF ├── com │ └── example │ └── MyClass.class └── application.properties
清單文件中通常指定Main-Class屬性,指示Java虛擬機從哪個類開始執行。
### 1.2 Spring Boot可執行JAR的獨特之處
Spring Boot的可執行JAR在標準JAR基礎上進行了擴展和創新:
1. **嵌套JAR支持**:采用特殊布局允許JAR中包含其他JAR(依賴庫)
2. **自定義類加載器**:使用Launcher類實現獨特的嵌套JAR加載機制
3. **分層優化**:支持分層打包以加速容器環境中的啟動速度
4. **嵌入式服務器**:默認包含Tomcat/Jetty等Web服務器
這種設計使應用能夠真正做到"一次構建,到處運行",無需預先安裝服務器或管理復雜的類路徑。
## 二、Spring Boot可執行JAR的詳細結構
解壓一個典型的Spring Boot JAR,我們可以看到以下結構:
springboot-app.jar ├── BOOT-INF │ ├── classes │ │ └── com/example/MyApplication.class │ └── lib │ ├── spring-boot-2.7.3.jar │ ├── spring-core-5.3.22.jar │ └── … ├── META-INF │ ├── MANIFEST.MF │ └── maven/com.example/springboot-app/pom.properties ├── org │ └── springframework │ └── boot │ └── loader │ ├── JarLauncher.class │ ├── LaunchedURLClassLoader.class │ └── … └── application.properties
### 2.1 關鍵目錄解析
1. **BOOT-INF目錄**:
- classes:包含應用程序自身的編譯類文件
- lib:存放所有依賴的第三方庫JAR文件
2. **META-INF目錄**:
- MANIFEST.MF:包含特殊的啟動配置信息
- maven:包含從構建系統繼承的元數據
3. **org/springframework/boot/loader**:
- 包含Spring Boot的自定義類加載器實現
- 負責處理嵌套JAR的加載邏輯
### 2.2 清單文件(MANIFEST.MF)分析
Spring Boot生成的MANIFEST.MF包含以下關鍵屬性:
```manifest
Manifest-Version: 1.0
Spring-Boot-Version: 2.7.3
Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: com.example.MyApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Main-Class
指向Spring Boot的JarLauncher而非應用主類Start-Class
才是實際的應用程序入口當執行java -jar springboot-app.jar
時,JVM會按照以下順序工作:
Spring Boot的LaunchedURLClassLoader擴展了URLClassLoader,重寫了以下關鍵行為:
這種設計解決了標準URLClassLoader無法直接加載嵌套JAR中類的問題。
在pom.xml中配置spring-boot-maven-plugin:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.7.3</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
執行打包命令:
mvn clean package
在build.gradle中應用插件并配置:
plugins {
id 'org.springframework.boot' version '2.7.3'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
}
bootJar {
archiveFileName = 'myapp.jar'
layered {
enabled = true
}
}
執行打包:
gradle bootJar
Spring Boot 2.3+引入了分層JAR概念,優化Docker鏡像構建:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<layers>
<enabled>true</enabled>
</layers>
</configuration>
</plugin>
生成的JAR包含layers.idx文件定義分層:
- "dependencies":
- "BOOT-INF/lib/dependency1.jar"
- "BOOT-INF/lib/dependency2.jar"
- "spring-boot-loader":
- "org/"
- "snapshot-dependencies":
- "BOOT-INF/lib/snapshot1.jar"
- "application":
- "BOOT-INF/classes/"
- "BOOT-INF/libs/"
通過插件配置添加自定義屬性:
<plugin>
<configuration>
<manifest>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
<addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
<addBuildEnvironmentEntries>true</addBuildEnvironmentEntries>
</manifest>
</configuration>
</plugin>
使用exclude元素移除不需要的依賴:
<plugin>
<configuration>
<excludes>
<exclude>
<groupId>org.unwanted</groupId>
<artifactId>dependency</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
jlink --module-path $JAVA_HOME/jmods:mods \
--add-modules com.example.app \
--output customjre
<plugin>
<configuration>
<compress>true</compress>
</configuration>
</plugin>
@ComponentScan(basePackages = "com.myapp")
spring.main.lazy-initialization=true
mvn spring-boot:build-image
癥狀:NoClassDefFoundError或ClassNotFoundException
解決方案:
1. 檢查依賴是否真正打包進BOOT-INF/lib
2. 確認沒有不兼容的依賴版本
3. 使用mvn dependency:tree
分析依賴沖突
癥狀:getResourceAsStream返回null
原因:資源路徑需要相對于BOOT-INF/classes
修復:
// 使用ClassLoader加載資源
InputStream is = getClass().getClassLoader()
.getResourceAsStream("templates/index.html");
優化方案: 1. 使用分層JAR 2. 排除不必要的依賴 3. 拆分微服務 4. 考慮War部署
native-image -jar springboot-app.jar
更高效的分層機制
模塊化支持改進
FROM eclipse-temurin:17-jdk as builder
WORKDIR application
COPY . .
RUN ./gradlew bootJar
FROM eclipse-temurin:17-jre
COPY --from=builder application/build/libs/*.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
pack build myapp --builder paketobuildpacks/builder:base
Spring Boot的可執行JAR設計體現了”約定優于配置”的核心理念,通過創新的打包方式和啟動機制,極大地簡化了Java應用的部署和分發流程。理解其內部工作原理不僅有助于解決實際問題,還能啟發我們設計更加優雅的架構。隨著云原生技術的發展,Spring Boot的打包策略也在不斷進化,但核心思想始終不變:讓開發者專注于業務邏輯,而非基礎設施的搭建。
jar tf springboot-app.jar
jar xf springboot-app.jar BOOT-INF/lib/spring-core-5.3.22.jar
mvn dependency:tree -Dincludes=org.springframework
”`
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。