SpringBoot打包部署解析:jar包的生成和結(jié)構(gòu)
SpringBoot打包部署解析
SpringBoot項目支持兩種常見的打包形式: jar 包和 war 包。默認情況下創(chuàng)建的 Spring Boot項目是采用 jar 包形式,如果項目需要 war 包,可通過修改配置打成 war 包。
本章我們將圍繞 jar 包和 war 包的運作原理及相關(guān)操作進行講解。
SpringBoo的jar 包
Spring Boot 的 jar 包項目發(fā)布形式簡單、快捷且內(nèi)置 web 容器,因此 Spring Boot 將其作為默認選項。在享受便利的同時,我們也需要多少了解一下 Spring Boot 的 jar 包是如何生成的,以及如何通過 jar 包啟動運行。本節(jié)從 jar 包的生成、結(jié)構(gòu)、運作原理來分析 Spring Boot的實現(xiàn)。
jar包的生成
Spring Boot 的可執(zhí)行 jar 包又稱作 fat jar”,是包含所有三方依賴的 jar。它與傳統(tǒng) jar 包最大的不同是包含了一個 lib 目錄和內(nèi)嵌了 web 容器(以下均以 tomcat 為例)。
jar 包通 常是由集成在 pom.xml 文件中的 maven 插件來生成的。
配置在 pom 文件 build 元素中的 plugins 內(nèi)。
<build>
<plugins>
<plugin>
<groupId>org . springframework. bootgroupId>
<artifactId>spring- boot-maven- plugin artifactId>
plugin>
plugins>
build>spring-boot-maven-plugin 項目存在于 spring-boot-tools 目錄中。spring-boot-maven-plugin默認有 5 個 goals: repackage、 run、 start、 stop、 build-info。在打包的時候默認使用的是 repackage。
spring-boot-maven-plugin 的 repackage 能夠?qū)?mvn package 生成的軟件包,再次打包為可執(zhí)行的軟件包,并將 mvn package 生成的軟件包重命名為*.original。
這就為什么當執(zhí)行 maven clean package 時,spring-boot-maven-plugin 會在 target 目錄下生成兩個 jar 文件。
spring - learn-0.0.1- SNAPSHOT. jarspring - learn-0.0.1- SNAPSHOT . jar . original其中我們可以將 spring-learn-0.0. 1-SNAPSHOTjar.original 文件的后綴 original 去掉,生成的新jar包便是包含業(yè)務(wù)代碼的包(普通的jar包) 。另外的spring-learn-0.0. 1-SNAPSHOTjar包則是在 Spring Boot 中通過 jar jar 啟動的包,它包含了應(yīng)用的依賴,以及 spring boot 相關(guān) class。
spring-boot-maven-plugin 的 repackage 在代碼層面調(diào)用了 RepackageMojo 的 execute 方法。RepackageMojo 類就是 提供重新打包現(xiàn)有的 jar 或 war 包文件,使得它們可以使用 javajar 來進行啟動。
RepackageMojo 的 execute 方法如下。
@Override
public void execute() throws MojoExecut ionException, MojoFailureException
if (this. project . getPackaging() . equals("pom")) {
getLog() . debug("repackage goal could not be applied to pom project.");
return;
if (this.skip) {
getLog() . debug("skipping repackaging as per configuration.");
return;
repackage();
}在 execute 方法中判斷了是否為 pom 項目和是否跳過,如果是,則打印 debug 日志并返回;否則繼續(xù)執(zhí)行 repackage 方法。RepackageMojo 中的 repackage 方法相關(guān)源代碼及操作解析如下。
private void repackage() throws MojoExecutionException {
// maven 生成的 jar, 最終的命名將加上. original 后綴
Artifact source = getSourceArtifact();
//最終為可執(zhí)行 jar,即 fat jar
File target = getTargetFile();
//獲取重新打包器,將 maven 生 成的 jar 重新打包成可執(zhí)行 jar
Repackager repackager = getRepackager(source . getFile());
//查找并過濾項目運行時依賴的 jar
Set artifacts = filterDependenc ies(this . project. getArtifacts(),
getFilters(getAdditionalFilt
ers()));
//將 artifacts 轉(zhuǎn)換成 L ibraries
Libraries libraries = new ArtifactsLibraries(artifacts, this . requiresUnpa
k,
getLog());ry {
/獲得 Spring Boot 啟動腳本
LaunchScript launchScript = getLaunchScript();
//執(zhí)行重新打包,生成 fat jar
repackager . repackage(target, libraries, launchScript);
catch (IOException ex) {
throw new MojoExecut ionException(ex. getMessage(), ex); }
將 maven 生成的 jar 更新成 original 文件
updateArtifact(source, target, repackager . getBackupFile());
} 關(guān)于整個 repackage 方法的操作流程在上面代碼中已經(jīng)進行相應(yīng)注釋說明,其基本過程為:獲得 maven 生成的普通 jar 包、獲得目標 File 對象、獲得重新打包器、獲得依賴 jar 包、 獲得啟動腳本,最后通過重新打包器進行重新打包為可通過 java -jar 執(zhí)行的 jar 包。
其中我們重點看獲取 Repackager 的方法 getRepackager 的源代碼。
private Repackager getRepackager(File source) {
Repackager repackager = new Repackager(source, this . layoutFactory);
repackager . addMainClassTimeoutWarningL istener(
new LoggingMainClassTimeoutWarningl istener());
//設(shè)置 main class 的名稱,如果不指定, 則會查找第一個包含 main 方法的類
// repackage 最后將會設(shè)置 org. springframework . boot . Loader. JarLauncher
repackager . setMainClass(this . mainClass);
if (this.layout != null) {
getLog(). info("Layout: "+
this. layout);
//比如,layout 返@org. springframework. boot. loader. tools. Layouts . Jar
repackager . setLayout(this . layout. layout());
}
return repackager;
}getRepackager 方法主要是根據(jù)將要被轉(zhuǎn)換的文件(jar 或 war) 創(chuàng)建了 Repackager 對象,并設(shè)置啟動用的 MainClass 為 org.
springframework.boot.loader.JarLauncher,該配置對應(yīng)于 jar 包中 Manifest.MF 文件內(nèi)的 MainClass 值。
同時,如果 layout 不為 null, 通過內(nèi)部枚舉類 L ayoutType 提供的 layout 方法獲取對應(yīng)的重新打包的實現(xiàn)類,比如針對 jar 包的 org.springframework.
boot.loader.tools.Layouts.Jar 類。
枚舉類 LayoutType 的定義如下。
public enum LayoutType {
JAR(new Jar()),WAR(new War()),
ZIP(new Expanded()),
DIR(new Expanded()),
NONE(new None());
}從 LayoutType 的定義可以看出,Spring Boot 其實是支持多種類型的 archive ( 即歸檔文件) : jar 類型、war 類型、zip 類型、 文件目錄類型和 NONE。很顯然,使用了相同的實現(xiàn)類來處理 ZIP 文件和 DIR 文件。
jar 類型為 Layouts 類的內(nèi)部類,可以簡單看一下 jar 類型的處理類都包含 了哪些內(nèi)容。
public static class Jar implements RepackagingLayout {
//獲取具體的 L ancher 類全路徑
@Override
public String getLauncherClassName() {
return "org. springframework. boot . loader.Jarlauncher";
/獲得具體的依賴 jar 包路徑
@Override
public String getL ibraryDestination(String libraryName, LibraryScope scop
return "BOOT - INF/lib/";
//獲取重新打包的 class 文件路徑
@Override
public String getRepackagedClassesLocation() {
return "BOOT -INF/classes/";}
}通過源代碼可以看出,jar 類型的歸檔文件(jar 包) 中包含了 jar 包啟動的 Main-class ( JarLauncher )BOOT-INF/lib/目錄和 BOOT-INF/classes/目錄。如果看 Expanded 和 None 類,會發(fā)現(xiàn)它們又繼承自 jar。
最后,我們簡單看一下 RepackageMojo 中的 repackage 調(diào)用所獲取的 Repackager 的repackage 方法。Repackager 中 repackage 方法源碼如下。
public void repackage(File destination, Libraries libraries, LaunchScript 1aunch
Script) throws IOException {
//校驗?zāi)繕宋募?/span>
F (destination == null| | destination. isDirectory())
throw new illegalArgumentException("Invalid destination");
//校驗依賴庫f (libraries = null) {
throw new IllegalArgumentException("Libraries must not be nu1l");
//校驗是否存在對應(yīng)的 Layout, 如果不存在則創(chuàng)建
if (this.layout == null) {
this. layout = getL ayoutFactory() . getLayout(this . source);
destination = destination. getAbsoluteFile();
File workingSource = this. source;
//檢查是否已經(jīng)重新打包
if (alreadyRepackaged() && this. source.equals(destination)) {
//如果目標文件和 source 相同, 則刪除原有備份文件( . original 結(jié)尾的), 重新備份 so
urce 文件
if (this. source . equals(destination)) {
workingSource = getBackupFile();
workingSource. delete();
renameFile(this. source, workingSource);
destination. delete();
try {
try (JarFile jarFileSource = new JarFile(workingSource)) {
//核心功能就是創(chuàng)建 JarWriter 向文件指定文件中寫入內(nèi)容
repackage(jarFileSource, destination, libraries, launchScript); }
finally {
if (!this . backupSource && !this. source . equals (workingSource)) {
deleteFile(workingSource);
}
}
}上述代碼的核心業(yè)務(wù)邏輯如下。
.校驗各類參數(shù)(文件和路徑是否存在)。
.備份待重新打包的文件以.original 結(jié)尾, 如果已經(jīng)存在備份文件則先執(zhí)行刪除操作。
:生成目標文件之前,先清除一下目標文件。
調(diào)用重載的 repackage 方法,進行具體(jar 包)文件的生成和 MANIFESTMF 的信息寫入。
.最后,判斷并執(zhí)行 workingSource 的清除操作。
用一句話總結(jié)上述過程:當符合條件時,對原有 jar 包文件進行備份,并生成新的可以通過 jar-jar 啟動的文件。
關(guān)于重新打包的 jar 的目錄結(jié)構(gòu)及 MANIFEST.MF 文件中的信息,我們將在下一節(jié)進行講解。
?jar包的結(jié)構(gòu)
在上一節(jié)中,通過 spring-boot-maven-plugin 生 成了可執(zhí)行的 jar 包,下面分析-下 jar 包
spring-learn-0.0.1-SNAPSHOT.jar的目錄結(jié)構(gòu)。

在上述結(jié)構(gòu)中,BOOT-INF/classes 目錄中存放業(yè)務(wù)代碼,BOOT-INF/ib 目錄中存放了除java 虛擬機之外的所有依賴; org 目 錄中存放了 Spring Boot 用來啟動 jar 包的相關(guān) class文件; META-INF 目錄中存放了 MANIFEST.MF、maven 信息和 spring factories 文件。
其中,Manifest.MF 文件通常被用來定義擴展或檔案打包相關(guān)數(shù)據(jù),它是一個元數(shù)據(jù)文件,數(shù)據(jù)格式為名/值對。一個可執(zhí)行的 jar 文件需要通過該文件來指出該程序的主類。
Manifest-Version: 1.0
Implementation-Title: spring-learn
Implementation-Version: 0. 0.1-SNAPSHOT
Start-Class: com. secbro2. learn. SpringLearnApplication
Spring - Boot-Classes: B0OT-INF/classes/
Spring-Boot-Lib: B0OT-INF/lib/
Build-Jdk-Spec: 1.8
Spring- Boot -Version: 2.2.1. RELEASE
Created-By: Maven Archiver 3.4.0
Main-Class: org. springframework . boot . loader .JarlauncherManifest.MF 文件中定義 Main-Class 設(shè)置為org.
springframework.boot.loader.JarLauncher, 也 就 是 說 , jar 包 程 序 啟 動 入 口 為JarL .auncher 類的 main 方法。JarLauncher 類位 于 spring-boot-loader 項目中,在 jar 包的 org 目錄中便存儲著 Launcher 相關(guān)類的 class 文件。
項目的弓導(dǎo)類定義在 Start-Class 屬性中,需要注意的是,Start-Class 屬性并非 Java 標準的 Manifest.MF 屬性。
本文給大家講解的內(nèi)容是SpringBoot打包部署解析:jar包的生成和結(jié)構(gòu)
下篇文章給大家講解的是SpringBoot打包部署解析:Launcher實現(xiàn)原理;
覺得文章不錯的朋友可以轉(zhuǎn)發(fā)此文關(guān)注小編;
感謝大家的支持!
本文就是愿天堂沒有BUG給大家分享的內(nèi)容,大家有收獲的話可以分享下,想學(xué)習(xí)更多的話可以到微信公眾號里找我,我等你哦。
