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 中加入如下配置

clipboard.png

Gradle Java plugin

gradle 下打包一個非遮蔽的 jar 包,有不少插件可以用,但是由於 gradle 自身的靈活性,可以直接用 groove 的 dsl 實現。

clipboard.png

非遮蔽方法會把所有的 jar 包裏的文件都解壓到一個目錄裏,然後在打包同一個 fatjar 中。對於複雜應用很可能會碰到同名類相互覆蓋問題。
  1. 遮蔽方法(Shaded)

遮蔽方法會把依賴包裏的類路徑進行修改到某個子路徑下,這樣可以一定程度上避免同名類相互覆蓋的問題。最終發佈的 jar 也不會帶入傳遞依賴衝突問題給下游。

Maven Shade Plugin

在 pom.xml 中加入如下配置

clipboard.png

Gradle Shadow plugin

Gradle shadow plugin 使用非常簡單,簡單聲明插件後就可以生效。

clipboard.png

遮蔽方法依賴修改 class 的字節碼,更新依賴文件的包路徑達到規避同名同包類衝突的問題,但是改名也會帶來其他問題,比如代碼中使用
Class.forName 或 ClassLoader.loadClass 裝載的類,Shade Plugin
是感知不到的。同名文件覆蓋問題也沒法杜絕,比如META-INF/services/javax.script.ScriptEngineFactory不屬於類文件,但是被覆蓋後會出現問題。
  1. 嵌套方法(Jar of Jars)

還是一種辦法就是在 jar 包裏嵌套其他 jar,這個方法可以徹底避免解壓同名覆蓋的問題,但是這個方法不被 JVM 原生支持,因爲 JDK 提供的 ClassLoader 僅支持裝載嵌套 jar 包的 class 文件。所以這種方法需要自定義 ClassLoader 以支持嵌套 jar。

Onejar Maven Plugin

One-JAR 就是一個基於上面嵌套 jar 實現的工具。onejar-maven-plugin 是社區基於 onejar 實現的 maven 插件。

clipboard.png

Spring boot plugin

One-JAR 有點年久失修,好久沒有維護了,Spring Boot 提供的 Maven Plugin 也可以打包 Fatjar,支持非遮蔽和嵌套的混合模式,並且支持 maven 和 gradle 。

clipboard.png

clipboard.png

requiresUnpack 參數可以定製那些 jar 不希望被解壓,採用嵌套的方式打包到 Fatjar 內部。

其打包後的內部結構爲

clipboard.png

應用的類文件被放置到 BOOT-INF/classes 目錄,依賴包被放置到 BOOT-INF/lib 目錄。

查看 META-INF/MANIFEST.MF 文件,其內容爲

clipboard.png

啓動類是固定的 org.springframework.boot.loader.JarLauncher,應用程序的入口類需要配置成 Start-Class。這樣做的目的主要是爲了支持嵌套 jar 包的類裝載,替換掉默認的 ClassLoader。

但是函數計算 Java Runtime 需要的 jar 包是一種打包結構,在服務端運行時會解壓開,./lib 目錄加到 classpath中,單不會調用 Main-Class。所以自定義 ClassLoader 是不生效的,所以不要使用嵌套 jar結構,除非在入口函數指定重新定義 ClassLoader 或者 classpath 以支持 BOOT-INF/classes 和BOOT-INF/lib 這樣的定製化的類路徑。

小結

clipboard.png

單從 Fatjar 的角度看, Spring boot maven/gradle 做得最精緻。但是 jar 包內部的自定義路徑解壓開以後和函數計算是不兼容的。所以如果用於函數計算打包,建議使用 Unshaded 或者 Shared 的打包方式,但是需要自己注意文件覆蓋問題。

本文作者:倚賢

閱讀原文

本文爲雲棲社區原創內容,未經允許不得轉載。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章