前言
尽管 Java 程序的最优运行环境为 Linux ,然而鉴于部分客户的硬件环境状况,我们的部分应用不得不部署于 Windows 操作系统之下。在 JVM 的架构模式中,Java 程序虽具备跨平台运行的能力,然而实际操作起来却颇具复杂性,需先行安装 jdk ,继而配置各类系统变量等。倘若客户对技术领域缺乏了解,那么这一系列操作对其而言着实不够友好。那么,能否将我们的程序制作成一个技术包,使用户仅需点击即可运行呢?接下来,我将为诸位分享借助 maven 来打包 Windows 环境下 Java 程序的多种方式。
众多老铁留言反馈,在操作过程中遭遇诸多问题。在此,我已完整整理相关代码及资源,诸位可通过以下链接进行下载:
一、准备开发环境和项目
我们所运行的各类软件,若就其安装及运行方式而言,大致可归为两类:一类为控制台程序,另一类为包含 gui 的界面交互程序。若采用 Java 进行开发,这两类程序均可实现。我们分别准备两个案例来演示打包步骤,其一为控制台运行,其二为图形化程序。诸位可通过文章头部的资源进行下载。同时,开发环境选用 Apache Maven 3.6.3 与 JDK8 。对于控制台程序,我们采用 springboot ,对于 gui 程序,则使用 javafx 分别进行演示。
二、将 springboot 程序打包成 exe 并绑定 jdk
springboot 打包 jar
我们创建一个 springboot 程序,对 pom.xml 文件的 build 部分进行如下修改,添加一个 pkg-sb 以方便后续的名称管理:
<build>
<finalName>pkg-sb</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
而后运行打包命令:
mvn clean -DskipTests package
如此,在 target 目录下将生成一个 pkg-sb.jar 的文件。实际上,此 jar 文件即可直接运行。倘若在 Windows 环境下已安装 jdk8 ,则可直接运行:
java -jar pkg-sb.jar
但此过程需在控制台中进行,且对于用户而言颇为不便。尽管也可编写一个 bat 脚本,然而并非十分优雅。当下,我们将此程序打包成 exe 文件。
springboot 打包 exe 并绑定 jdk
maven 拥有众多插件可用于打包 EXE ,此处我们选用 launch4j-maven-plugin 来达成目的。将此插件添加至 pom.xml ,先看如下代码:
<plugin>
<groupId>com.akathist.maven.plugins.launch4j</groupId>
<artifactId>launch4j-maven-plugin</artifactId>
<version>1.7.25</version>
<executions>
<execution>
<id>l4j-clui</id>
<phase>package</phase>
<goals>
<goal>launch4j</goal>
</goals>
<configuration>
<!-- 打包类型,可使用值:console、gui 分别代表控制台和图形界面程序-->
<headerType>console</headerType>
<!-- jar文件位置 -->
<jar>target/pkg-sb.jar</jar>
<!-- 生成exe文件的名称 -->
<outfile>target/pkg-sb.exe</outfile>
<errTitle>pkg-sb-error</errTitle>
<classPath>
<!--
这里务必要留意,springboot 默认打包后的启动类是此类别,而非我们程序中的 Application 文件,
若不清楚,可将打包的 jar 文件解压并查找:META-INF/MANIFEST.MF 文件查看其中的:Main-Class
属性值
-->
<mainClass>org.springframework.boot.loader.JarLauncher</mainClass>
<addDependencies>true</addDependencies>
<preCp>anything</preCp>
</classPath>
<jre>
<jdkPreference>jdkOnly</jdkPreference>
<!-- jdk运行目录,这里可使用绝对路径,也可使用相对路径,不建议使用绝对路径,此处我们直接采用相对路径,下面此配置表明 jdk 的目录与 exe 的文件处于同级 -->
<path>./runtime</path>
<jdkPreference>preferJre</jdkPreference>
<opts>
<opt>-server</opt>
<opt>-Xss256k</opt>
<opt>-XX:MetaspaceSize=256m</opt>
<opt>-XX:MaxMetaspaceSize=256m</opt>
<opt>-Xms512m</opt>
<opt>-Xmx512m</opt>
<opt>-Xmn512m</opt>
<opt>-XX:SurvivorRatio=8</opt>
<opt>-XX:+AggressiveOpts</opt>
<opt>-XX:+UseBiasedLocking</opt>
<opt>-XX:+DisableExplicitGC</opt>
<opt>-XX:MaxTenuringThreshold=12</opt>
<opt>-XX:+UseConcMarkSweepGC</opt>
<opt>-XX:+UseParNewGC</opt>
<opt>-XX:+CMSParallelRemarkEnabled</opt>
<opt>-XX:LargePageSizeInBytes=128m</opt>
<opt>-XX:+UseFastAccessorMethods</opt>
<opt>-XX:+UseCMSInitiatingOccupancyOnly</opt>
<opt>-Djava.awt.headless=true</opt>
<opt>-Duser.timezone=GMT+08</opt>
<opt>-Dvar1=var1-value-001</opt>
<opt>-Dconfig.file=./config.properties</opt>
</opts>
</jre>
<versionInfo>
<fileVersion>1.2.3.4</fileVersion>
<txtFileVersion>txt file version?</txtFileVersion>
<fileDescription>a description</fileDescription>
<copyright>my copyright</copyright>
<productVersion>4.3.2.1</productVersion>
<txtProductVersion>txt product version</txtProductVersion>
<productName>E-N-C-C</productName>
<internalName>ccne</internalName>
<originalFilename>pkg-sb.exe</originalFilename>
</versionInfo>
</configuration>
</execution>
</executions>
</plugin>
在此,主要关注 configuration 配置,需留意的几个要点说明如下:
- headerType :打包类型,可使用值:console 、gui 分别代表控制台和图形界面程序。
- jar :jar 文件位置。
- outfile :生成 exe 文件的名称。
- classPath.mainClass :此处务必留意,springboot 默认打包后的启动类是此类别,而非我们程序中的 Application 文件。若不清楚,可将打包的 jar 文件解压并查找:META-INF/MANIFEST.MF 文件查看其中的:Main-Class 属性值。
- jre.path :jdk 运行目录,这里可使用绝对路径,也可使用相对路径,不建议使用绝对路径,此处我们直接采用相对路径,下面此配置表明 jdk 的目录与 exe 的文件处于同级。
- jre.opts :此处可传入运行时的参数设置以及系统变量传递,我们添加了两个环境变量的测试。
接下来,我们再次运行 maven 命令进行打包:
mvn clean -DskipTests package
可以看到已生成 exe 文件:
完善打包结构并制作发布包
方才完成了 exe 文件的生成,但点击运行时毫无反应,此乃因我们配置的 jdk 运行目录未添加进去。
生成 jdk 运行时
生成 jdk 运行时的方法众多,其一为直接前往官方下载 jre 包,若您的 jdk 版本大于 8 ,若为 jdk8 以下,则直接前往官方下载 jre 。我在此处使用 jlink 来生成,因为 jlink 能够定制所需的模块,生成的运行时相对较小。在 cmd 控制台中使用以下命令生成:
jlink --add-modules java.base,java.compiler,java.desktop,java.net.http,java.management,java.scripting,java.naming,java.sql,java.logging,jdk.unsupported,jdk.httpserver,jdk.internal.jvmstat,jdk.jfr --output runtime
生成的目录结构如下:
我们运行时的目录名称为 runtime ,而后将方才生成的 EXE 文件拷贝至 runtime 同级的目录下。倘若您是直接下载的 jre ,则需将 jre 的目录文件更改为 runtime ,如下图所示:
点击运行:
此处已运行成功,此为一个控制台程序。
制作 zip 发布包
方才我们均为手动操作,将所需资源手动拷贝,接下来我们使用 assembly 插件来对我们所需的资源进行统一管理。首先修改我们的项目结构:
编写 assembly 配置文件:
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
<id>package</id>
<formats>
<format>zip</format>
</formats>
<includeBaseDirectory>true</includeBaseDirectory>
<fileSets>
<fileSet>
<directory>runtime</directory>
<outputDirectory>\runtime</outputDirectory>
</fileSet>
<fileSet>
<directory>conf</directory>
<outputDirectory></outputDirectory>
<includes>
<include>*.properties</include>
</includes>
</fileSet>
<fileSet>
<directory>${project.build.directory}</directory>
<outputDirectory></outputDirectory>
<includes>
<include>pkg-sb.exe</include>
</includes>
</fileSet>
</fileSets>
</assembly>
而后在 pom.xml 中添加插件 assembly :
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.4.2</version>
<configuration>
<appendAssemblyId>false</appendAssemblyId>
<finalName>${build.finalName}</finalName>
<descriptors>
<descriptor>assembly/package.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
最后运行 maven 打包命令:
mvn clean -DskipTests package
完成后此处生成了一个 zip 文件:
接下来,我们可直接将此 zip 发送给用户,解压后的目录格式如下:
直接双击 exe 运行。下面我们测试一下在打包时传递的系统参数,我们在代码中添加如下代码:
@RequestMapping("/test")
public Object test(){
String var1=System.getProperty("var1");
String config_file=System.getProperty("config.file");
StringBuffer buffer=new StringBuffer();
buffer.append("获取到系统变量 var1 的值:").append(var1).append("\n");
if(!StringUtils.isEmpty(config_file)){
File file=new File(config_file);
if(file.exists()){
try (FileInputStream inputStream=new FileInputStream(file)){
Properties properties=new Properties();
properties.load(inputStream);
buffer.append("获取到配置文件信息:").append(properties.toString());
}catch (Exception e){
buffer.append("读取配置文件错误:"+e.getMessage());
}
}else {
buffer.append("配置文件不存在");
}
}else {
buffer.append("未获取到配置文件变量信息");
}
return ResponseEntity
.ok()
.body(buffer.toString());
}
程序启动成功后,我们访问:http://localhost:12345/test
表明我们在打包时传递的系统变量在程序中已成功获取。至此,控制台程序打包已完成。
总结
上述我们完成了 springboot 程序打包 EXE 的流程,并且自带 jdk 运行时,如此一来,便不依赖目标机上的 jdk 环境。即便目标机上未曾安装 jdk ,我们的程序亦能正常运行,且依照我们所需的版本进行运行。然而,此种方式仍非尽善尽美,毕竟始终需启动控制台,倘若不慎关闭了控制台,整个程序亦将停止。后续,我将为诸位分享如何将此 exe 制作成 Windows 的系统服务,以使程序于后台运行。因篇幅与时间所限,有意学习的同学请关注我后续的更新。