痛苦玩转Java的jpackage模块打包Windows可执行程序(包含第三方非模块化包)

257 阅读4分钟

  使用 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 命令行参数官方文档:docs.oracle.com/en/java/jav…