Java打包FatJar方法小結
在函式計算(Aliyun FC)中釋出一個 Java 函式,往往需要將函式打包成一個 all-in-one 的 zip 包或者 jar 包。Java 中這種打包 all-in-one 的技術常稱之為 Fatjar 技術。本文小結一下 Java 裡打包 FatJar 的若干種方法。
什麼是 FatJar
FatJar 又稱作 uber-Jar,是包含所有依賴的 Jar 包。Jar 包中嵌入了除 java 虛擬機器以外的所有依賴。我們知道 Java 的依賴分為兩種, 零散的 .class 檔案和把多個 .class 檔案以 zip 格式打包而成 jar 檔案。FatJar 是一個 all-in-one Jar 包。FatJar 技術可以讓那些用於最終釋出的 Jar 便於部署和執行。
三種打包方法
我們知道 .java 原始碼檔案會被編譯器編譯成位元組碼.class 檔案。Java 虛擬機器執行的是 .class 檔案。一個 java 程式可以有很多個 .class檔案。這些 .class 檔案可以由 java 虛擬機器的類裝載器執行期裝載到記憶體裡。java 虛擬機器可以從某個目錄裝載所有的 .class 檔案,但是這些零散的.class 檔案並不便於分發。所有 java 支援把零散的.class 檔案打包成 zip 格式的 .jar 檔案,並且虛擬機器的類裝載器支援直接裝載 .jar 檔案。
一個正常的 java 程式會有若干個.class 檔案和所依賴的第三方庫的 jar 檔案組成。
1. 非遮蔽方法(Unshaded)
非遮蔽是相對於遮蔽而說的,可以理解為一種樸素的辦法。解壓所有 jar 檔案,再重新打包成一個新的單獨的 jar 檔案。
藉助 Maven Assembly Plugin 都可以輕鬆實現非遮蔽方法的打包。
Maven Assembly Plugin
Maven Assembly Plugin 是一個打包聚合外掛,其主要功能是把專案的編譯輸出協同依賴,模組,文件和其他檔案打包成一個獨立的釋出包。使用描述符(descriptor)來配置需要打包的物料組合。並預定義了常用的描述符,可供直接使用。
預定義描述符如下
- bin 只打包編譯結果,幷包含 README, LICENSE 和 NOTICE 檔案,輸出檔案格式為 tar.gz, tar.bz2 和 zip。
- jar-with-dependencies 打包編譯結果,並帶上所有的依賴,如果依賴的是 jar 包,jar 包會被解壓開,平鋪到最終的 uber-jar 裡去。輸出格式為 jar。
- src 打包原始碼檔案。輸出格式為 tar.gz, tar.bz2 和 zip。
- project 打包整個專案,除了部署輸出目錄 target 以外的所有檔案和目錄都會被打包。輸出格式為 tar.gz, tar.bz2 和 zip。
除了預定義的描述符,使用者也可以指定描述符,以滿足不同的打包需求。
打包成 uber-jar,需要使用預定義的 jar-with-dependencies 描述符:
在 pom.xml 中加入如下配置
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>CHOOSE LATEST VERSION HERE</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>assemble-all</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
Gradle Java plugin
gradle 下打包一個非遮蔽的 jar 包,有不少外掛可以用,但是由於 gradle 自身的靈活性,可以直接用 groove 的 dsl 實現。
apply plugin: `java`
jar {
from {
(configurations.runtime).collect {
it.isDirectory() ? it : zipTree(it)
}
}
}
非遮蔽方法會把所有的 jar 包裡的檔案都解壓到一個目錄裡,然後在打包同一個 fatjar 中。對於複雜應用很可能會碰到同名類相互覆蓋問題。
2. 遮蔽方法(Shaded)
遮蔽方法會把依賴包裡的類路徑進行修改到某個子路徑下,這樣可以一定程度上避免同名類相互覆蓋的問題。最終釋出的 jar 也不會帶入傳遞依賴衝突問題給下游。
Maven Shade Plugin
在 pom.xml 中加入如下配置
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.1.1</version>
<configuration>
<!-- put your configurations here -->
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
Gradle Shadow plugin
Gradle shadow plugin 使用非常簡單,簡單宣告外掛後就可以生效。
plugins {
id `com.github.johnrengelman.shadow` version `2.0.4`
id `java`
}
shadowJar {
include `*.jar`
include `*.properties`
exclude `a2.properties`
}
遮蔽方法依賴修改 class 的位元組碼,更新依賴檔案的包路徑達到規避同名同包類衝突的問題,但是改名也會帶來其他問題,比如程式碼中使用 Class.forName 或 ClassLoader.loadClass 裝載的類,Shade Plugin 是感知不到的。同名檔案覆蓋問題也沒法杜絕,比如
META-INF/services/javax.script.ScriptEngineFactory
不屬於類檔案,但是被覆蓋後會出現問題。
3. 巢狀方法(Jar of Jars)
還是一種辦法就是在 jar 包裡巢狀其他 jar,這個方法可以徹底避免解壓同名覆蓋的問題,但是這個方法不被 JVM 原生支援,因為 JDK 提供的 ClassLoader 僅支援裝載巢狀 jar 包的 class 檔案。所以這種方法需要自定義 ClassLoader 以支援巢狀 jar。
Onejar Maven Plugin
One-JAR 就是一個基於上面巢狀 jar 實現的工具。onejar-maven-plugin 是社群基於 onejar 實現的 maven 外掛。
<plugin>
<groupId>com.jolira</groupId>
<artifactId>onejar-maven-plugin</artifactId>
<version>1.4.4</version>
<executions>
<execution>
<goals>
<goal>one-jar</goal>
</goals>
</execution>
</executions>
</plugin>
Spring boot plugin
One-JAR 有點年久失修,好久沒有維護了,Spring Boot 提供的 Maven Plugin 也可以打包 Fatjar,支援非遮蔽和巢狀的混合模式,並且支援 maven 和 gradle 。
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<layout>ZIP</layout>
<requiresUnpack>
<dependency>
<groupId>org.jruby</groupId>
<artifactId>jruby-complete</artifactId>
</dependency>
</requiresUnpack>
</configuration>
</plugin>
plugins {
id `org.springframework.boot` version `2.0.4.RELEASE`
}
bootJar {
requiresUnpack `**/jruby-complete-*.jar`
}
requiresUnpack 引數可以定製那些 jar 不希望被解壓,採用巢狀的方式打包到 Fatjar 內部。
其打包後的內部結構為
example.jar
|
+-META-INF
| +-MANIFEST.MF
+-org
| +-springframework
| +-boot
| +-loader
| +-<spring boot loader classes>
+-BOOT-INF
+-classes
| +-mycompany
| +-project
| +-YourClasses.class
+-lib
+-dependency1.jar
+-dependency2.jar
應用的類檔案被防止到 BOOT-INF/classes 目錄,依賴包被放置到 BOOT-INF/lib 目錄。
檢視 META-INF/MANIFEST.MF 檔案,其內容為
Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: com.mycompany.project.MyApplication
啟動類是固定的 org.springframework.boot.loader.JarLauncher,應用程式的入口類需要配置成 Start-Class。這樣做的目的主要是為了支援巢狀 jar 包的類裝載,替換掉預設的 ClassLoader。
但是函式計算需要的 jar 包是一種打包結構,在服務端執行時會解壓開,不會呼叫 Main-Class。所以自定義 ClassLoader 是不生效的,所以不要使用巢狀 jar 結構,除非在入口函式指定重新定義 ClassLoader 或者 Classpath 以支援 BOOT-INF/classes 和 BOOT-INF/lib 這樣的定製化的類路徑。
小結
外掛 | 構建平臺 | 工作機制 |
---|---|---|
maven-assembly-plugin | maven | Unshaded |
Gradle Java plugin | gradle | Unshaded |
maven-shade-plugin | maven | Shaded |
com.github.johnrengelman.shadow | gradle | Shaded |
Onejar | ant, maven | Jar of Jars |
Spring boot plugin | maven, gradle | Unshaded, Jar of Jars |
單從 Fatjar 的角度看, Spring boot maven/gradle 做得最精緻。但是 jar 包內部的自定義路徑解壓開以後和函式計算是不相容的。所以如果用於函式計算打包,建議使用 Unshaded 或者 Shared 的打包方式,但是需要自己注意檔案覆蓋問題。
參考閱讀
- https://imagej.net/Uber-JAR
- https://softwareengineering.stackexchange.com/questions/297276/what-is-a-shaded-java-dependency
- https://docs.spring.io/spring-boot/docs/current/reference/html/executable-jar.html
相關文章
- android gradle 多渠道打包小結AndroidGradle
- electron打包更新與整合sqlite小總結SQLite
- 總結幾個webpack打包優化的方法Web優化
- String方法小總結
- js 字串方法小結JS字串
- 【JAVA】- 知識點小結Java
- Java併發小結01Java
- js 陣列的方法小結JS陣列
- YUIDoc的使用方法小結UI
- Java執行緒安全小結Java執行緒
- Java 各種鎖的小結Java
- Java最佳實踐小結 - jonathangilesJava
- js陣列操作方法小結JS陣列
- JavaScript建立物件方法例項小結JavaScript物件
- DOM節點刪除方法小結
- 全景分割丨全景分割方法小結
- mysql中複製表結構的方法小結MySql
- 常用Java8語法小結Java
- java Stream結合函式方法Java函式
- Effective Java 避免使用終結方法和清空方法Java
- 小程式程式碼打包處理
- 戰略性系統思考方法小結
- linux下的QT打包方法LinuxQT
- JAVA堆外記憶體排查小結Java記憶體
- 「小程式JAVA實戰」小程式的上傳(終結)(72)Java
- 不同Java除錯方法總結 - VardhanJava除錯
- Java-String的常用方法總結!Java
- JAVA 專案 配合 Docker 打包JavaDocker
- js的繼承方法小結(prototype、call、apply)JS繼承APP
- Hive表小檔案合併方法總結Hive
- Linux系統時間同步方法小結(NTP)Linux
- 【UniApp】-uni-app-打包成小程式APP
- java程式碼-編譯-打包-執行_雲原生時代筆記總結Java編譯筆記
- Java-執行緒間通訊小結Java執行緒
- java基礎語法知識小結(1)Java
- 修改vue打包後的結構Vue
- java專案打包(maven+原生)JavaMaven
- java打包相關的步驟Java