Java应用的分发一直是一个比较麻烦的问题。这是因为Java应用的运行需要虚拟机的支持,仅有Java应用打包的JAR文件是不够的,目标机器还需要安装版本匹配的JDK或JRE。随着云原生和容器化技术的流行,Java应用可以选择以容器镜像的形式来打包和分发,极大地降低了分发难度。不过仍然有相当一部分的Java应用需要直接安装在客户的机器上。
通常的解决方案是使用第三方安装工具,如install4j,创建应用的安装包。安装包负责打包应用和所依赖的Java运行环境。安装工具的问题在于过于繁琐,并且通常是收费的。很多时候我们只是需要简单的运行一个Java程序而已。比如,在客户的机器上运行Java编写的数据迁移工具。
对于这样的需求,我们可以使用JDK 14中新增的Java打包工具jpackage。该工具在JDK 14和15中是预览功能,在JDK 16中已经成为正式功能。
jpackage 的基本用法
下面以JDK 16来进行说明。在JDK的bin目录下可以找到jpackage工具。jpackage可以生成平台相关的软件包:
- Linux:deb和rpm
- macOS:pkg和dmg
- Windows:msi和exe
默认情况下,jpackage生成与当前运行环境相匹配的软件包。
所使用的pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>SeleniumTest</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>SeleniumTest</name>
<properties>
<maven.compiler.source>19</maven.compiler.source>
<maven.compiler.target>19</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- selenium-java -->
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>4.7.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.9.0</version>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
<!--资源相关-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<overwrite>true</overwrite>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<!--打包相关-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.2</version>
<configuration>
<archive>
<addMavenDescriptor>false</addMavenDescriptor>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<!--指定主类,类的全限定类名-->
<mainClass>stzz.architect.ljxwtl.cn.SeleniumMain</mainClass>
</manifest>
<manifestEntries>
<Class-Path>./</Class-Path>
</manifestEntries>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.3.0</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<type>jar</type>
<includeTypes>jar</includeTypes>
<!--打包阶段时将依赖的jar包导出到lib目录下-->
<outputDirectory>${project.build.directory}/lib</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
自定义 JDK 镜像
默认生成的应用打包文件比较大,这是因为整个JDK中的模块都被打包了进去。可以对应用使用的JDK镜像进行定制,仅包含应用需要的模块。对于一个应用来说,整个打包过程一般分成三步来完成。
使用jdeps来输出依赖的JDK模块的名称。参数--print-module-deps的作用是输出模块名称,--ignore-missing-deps的作用是忽略模块解析的错误。
jdeps -cp "lib/*" --module-path "lib/*" --multi-release 18 --print-module-deps --ignore-missing-deps SeleniumTest-1.0-SNAPSHOT.jar
如果不添加参数--ignore-missing-deps
,会产生很多错误,表示找不到依赖的类。这是由于Spring Boot中很多功能是可选的,这些缺失的类在运行时并不会被用到,因此不会影响应用的运行,但是会影响jdeps
的检查结果。
上述命令所产生的结果如下所示:
java.base,java.compiler,java.desktop,java.instrument,java.logging,java.management,java.security.jgss,jdk.unsupported
下一步是通过jlink
来创建自定义的JDK镜像,如下面的代码所示。参数--add-modules
中的模块列表来自jdeps
命令的输出。产生的JDK镜像在目录custom-jre
中。
jlink --add-modules java.base,java.compiler,java.desktop,java.instrument,java.logging,java.management,java.security.jgss,jdk.unsupported --module-path lib --output custom-jre
除了一些通用的参数之外,jpackage
还可以使用平台相关的参数来定制安装包。比如,在Windows上,--win-menu
可以把应用添加到启动菜单,--win-shortcut
可以在桌面上创建快捷方式。macOS和Linux上也有相似的参数。
将SeleniumTest-1.0-SNAPSHOT.jar放入lib目录中后执行:
jpackage --name simple-rest-service --input lib --main-jar SeleniumTest-1.0-SNAPSHOT.jar --runtime-image custom-jre
打包时提示错误:类型[rpm]无效或不受支持
,解决办法:
yum install rpm-build redhat-rpm-config
macos上的命令:
jpackage --name simple-rest-service --input lib --main-jar SeleniumTest-1.0-SNAPSHOT.jar --runtime-image custom-jre --icon icon.icns