博客 / 詳情

返回

痛苦玩轉Java的jpackage模塊打包Windows可執行程序(包含第三方非模塊化包)

  
  使用 jpackage 生成運行時映像,對於windows平台可以直接生成 exe 文件,並附帶運行環境,雙擊即可運行;所有的 jar 被打包為單個 modules 文件,位於生成的最終運行環境文件的 runtime\lib 中;一個簡單的 JavaFx 應用打包後不會超過100M。
  在此過程中會遇到很多不支持模塊化(module)的JAR包,這些依賴的包內部沒有 module-info.class 文件,我們會搞定它!

痛苦過程1:不可避免的會遇到並依賴非模塊化的jar包。
  例如 commons-logging(org.apache.commons.logging),只要使用了apache 的開源包,幾乎不可避免的會依賴日誌組件。由於許多開源包並未提供模塊化支持因此這是繞不開的麻煩;除非不用任何外部依賴。
  解決辦法就是手動為這些依賴包注入 module-info.class 使其成為模塊包,首先需要通過 jdeps 命令從 jar 包導出 module-info.java 文件,命令如下示例。

jdeps --ignore-missing-deps --multi-release 21 --generate-module-info %OUT% %JAR%

  %OUT% 輸出文件的目錄,通常會輸出自動包名文件夾內部有 module-info.java
%JAR% JAR 的路徑和包名,可以使用相對路徑
  
  獲得 module-info.java 文件之後要將其編譯為 module-info.class,命令如下所示例。

javac -p %DIR%\lib --patch-module org.apache.pdfbox=%JAR% %DIR%\org.apache.pdfbox\versions\21\module-info.java 

%DIR% 之前的輸出文件目錄
%DIR%\lib 依賴包的目錄位置
%JAR% JAR 的路徑和包名,可以使用相對路徑
  最後將其 module-info.class 更新到 JAR 包內,命令如下所示例。

jar -u -f %JAR% -C %DIR%\org.apache.pdfbox\versions\21 module-info.class

  經過以上處理,不支持 module 的 JAR 包現在可以正常的執行 jpackage 打包了。
  當然我們需要手動處理每一個包,如果是 maven 項目最好在 maven 打包完成,將所有 jar 依賴輸出之後進行;這樣可以避免損壞 maven 在本地下載的包,原本以為事情到此可以結束了,實際上還差的很遠;接下來開始痛苦過程2。

痛苦過程2:非模塊的JAR包通過生成和注入 module-info.class 之後可能遇到的問題。
  當我們滿心歡喜的運行我們生成的最終運行程序,雙擊 exe 即可,看起來一切正常;這就是 JAVA 模塊厲害之處,打包成功並不代表最終程序是OK的,如果我們試試更多的功能,會發現事情並不是希望的那樣;莫名其妙的錯誤開始出現,如果這時候你的程序沒有額外的輸出錯誤和日誌,
默認的控制枱日誌已經無跡可尋,那將會是一場災難。commons-logging 也救不了我們,因為出錯的可能就是它。

錯誤1:

org.apache.commons.logging.LogFactory: module org.apache.commons.logging not declear 'uses'
  org.apache.commons.logging 採用了一種離奇的工作方式,運行時動態判斷那個具體的日誌組件可以被加載,例如:log4j,然後開始輸出日誌,如果所有的外部日誌組件都不存在,那麼回退到 java.logging (java.util.logging),回退行為是自動的;所以日誌實現和本身的類定義是脱離關係的,這會導致模塊化時丟失所有實現,主要丟失的是 org.apache.commons.logging.impl 中的類,因為在打包時未建立直接關係。
  而且 org.apache.commons.logging 無法通過上面的方式注入 module-info.class ;我們推測這也和實現方式有關係,因為模塊看起來沒有可導出的任何有用代碼。好吧實在是沒辦法了嗎?我們的終極辦法是將 org.apache.commons.logging 源代碼下載來,手動添加 module-info.java ,移除了我們不需要的加載第三方日誌的代碼,將JDK更改為 Java21 然後重新打包為模塊。
  還有個現象,commons-logging 除非明確在我們工程中的 module-info.java 中指定 requires org.apache.commons.logging; 如果是間接依賴,通常不會影響 jpackage 打包,但是一定會在運行時出錯(需要輸出日誌時)。

  錯誤2

class org.apache... (in module org.apache.fontbox) cannot access class org.apache... (in module org.apahce.pdfbox.io)
because module org.apache.fontbox dose not read module org.apahce.pdfbox.io
  這是因為自動生成的 module-info.java 文件沒有添加必要的 requires org.apache.pdfbox.io ;自動生成模塊信息文件不會添加模塊之間的相互依賴,這個得手動完成,當然這是個痛苦得工作。
我們需要為所有非模塊化JAR製備的 module-info.java 手動添加額外的依賴。
  這些外部依賴有時候還是非常明顯的,如果確實不清楚,打包後的功能測試可以暴露依賴缺失;唯一需要注意的是,沒有控制枱,你需要通過界面或自己其它的方式輸出錯誤信息。我們在 JavaFX 中是通過警告框輸出錯誤信息,代碼如下所示。

void error(Throwable e) {
        if (e != null) {
            e.printStackTrace(System.err);
 
            final Alert alert = new Alert(AlertType.ERROR);
            alert.initModality(Modality.APPLICATION_MODAL);
            alert.initOwner(window());
            alert.setResizable(true);
            alert.setContentText(e.toString());
            alert.setHeaderText(e.getMessage());
            alert.setTitle("R11");
            alert.showAndWait();
        }
    }

  以上是我們在開發 r11 程序時,為了通過 jpackage 將 javaFx 桌面程序打包為最終鏡像時遇到的問題;r11 是一款用於將軟件源代碼導出為軟件著作權代碼文檔的工具,推薦去官網 r11.joyzl.cn 下載試試。
              終於打包成功,終於看見了界面!

1.png

 jpackage 命令行參數官方文檔:
https://docs.oracle.com/en/java/javase/21/docs/specs/man/jpackage.html

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.