话说一个java应用,在开发的最后一步是什么呢?毫无疑问,就是打包发布,我将会在这里记录一下我之前使用过的各类打包发布的方法。当然,我们不能要求每一个使用软件的人都要手动安装JDK(JRE),所以我们默认捆绑一个JRE进来。
准备
我们将打包好的jar包以及需要的一切都准备好,单独放入一个文件夹,如果你是spring-boot的工程,请注意使用spring的插件进行打包,因为springboot会接管你自己的主类,这个时候,你需要使用这个作为Main类:org.springframework.boot.loader.JarLauncher使用这个而不是你自己定义的main类来启动,java才能正确找到你需要的依赖包,springboot会将依赖包和你的应用打包在一起。如果使用maven,那么请使用copy-dependency这个插件,将依赖包复制到target里面,如果你没有使用依赖管理器,那么请将构建路径的jar包收集起来,也可以将他们合并到你的jar包里面。总之,确保你的jar包在打包之后可以在命令行环境中运行起来,我们可以使用java命令运行他,观察是否可以正常启动,如果不可以,那么就说明你的准备还不充分,需要自行排查问题。例如:

我准备了这些,我要用java运行一下看看:

看起来已经启动成功了,那么这个文件夹就是启动应用所需要的全部了,接下来的打包工作就要围绕着这些进行。在进行打包之前,我们还要为应用准备一个好看的图标,windows需要的是icon,mac需要的是icns,大家可以自己想办法进行制作,当然我已经准备好了,在文件夹的截图中就可以看到一个图标。另外呢,本文大概分为两个部分,一个是针对java8,一个是针对java9+,主要采用javaPackager和exe4j两种方式,当然exe4j只能对windows使用,如果想跨平台请考虑javapackager。
Java8的打包发布:
这里主要有两种:Java自带的Packager以及Exe4j,其实还有其他办法,但是这里不在列举他们,因为不好用。
第一个:JavaPackager
新的版本里面也叫javaPackager,在java8里面就叫做javafxPackager,我们可以在java安装的目录的bin文件夹里面找到他,这是一个命令行工具,我们可以通过它创建一个java应用的启动器,其实idea和eclipse都有对这个命令行工具的封装,不过我们先不管他们,直接通过命令行完成打包。首先,打开命令行进入你的javahome,如果你已经配置了环境变量,那么这一步可以免除。

你可以看到,java版本是1.8,javapackager命令存在,这是我们进行下一步操作的基础。
接下来,我们运行这样的命令开始进行打包:
javapackager -deploy -native image -outdir ./outdir -outfile [应用程序名] -appclass [Main类] -srcfiles . -name [应用程序名] -Bicon=[图标路径]
当然,这些都是常用的参数,下面简单说明一下:
-deploy -native image:发布为image类型,不打包,绿色版的方式,windows就是带一个jre的文件夹,包含exe和一个jre环境。
– outdir 输出的文件夹的名字,这里是当前文件夹下的outdir文件夹。- outfile 随便写,好像没有什么作用。
– appclass 应用程序主类,用来启动的那个,得含有main方法的,大家都懂吧
– srcfiles 需要的文件,这里有一个“.”符号,表示当前文件夹的意思,这会把我们需要的所有文件都复制到输出文件夹的合理位置,如果你想指定文件也可以,请使用空格分隔你需要的各个文件。
– name 应用程序名- Bicon 应用程序的图标文件的位置

我们执行此命令之后,如果看到类似上图的结果,那么打包就成功了,result application bundle这个说的就是打包后的应用存储的位置,直接将此文件夹复制到合适的位置即可。

这就是打包后bundles的输出文件夹,里面就有一个可执行文件,以上就是使用javapackager的打包方式了,当然,以上流程适用于java8,包括java8以下的应用使用java8打包都可以。
第二种:Java8使用Exe4j
当然,exe4j需要那种你懂得的版本,如果你不懂得,那就是说你应该使用一个在用于学习软件原理和非商业用途没有法律问题的那个版本 这样就明白了吧,至于这种版本怎么找嘛,百度就有很多。在使用Exe4j的时候,我们需要提前复制一个jre出来,我知道很多人这个时候会问,为什么不用单EXE模式呢?因为单exe会在临时文件夹释放文件,容易被当做病毒,所以还是使用regular mode。不过这个现在还不重要,因为我们还有一件事要做,就是把JDK复制一份,随便放哪,然后把jdk文件夹的名字改成你应用程序的名字。



接着把之前准备好的东西复制到这个jdk文件夹,然后就可以启动exe4j啦。

第一步,Next。

第二步:Regular mode

第三步,填写应用名称,选择distribution source directionary为刚刚改过名字的jdk文件夹。

第四步,Executeable type就是GUIApplication,如果你不是,就选择console Application。勾选icon file,然后选择一个图标,被圈出来的是单例选项,勾选后,此应用程序只能启动一个,也就是说如果此程序已经在运行,是无法重复启动的,请填写Executeable name,这是应用程序的名称。

redirection和32bits or 64bits这两个需要特别注意:redirection是说保存system.out.println这些到一个文件里面去,还可保存异常到一个文件里面去,这两个最好都配置一下,点击redirection开始配置:

打开里面的redirect stdout以输出system.out然后是32bit or 64bit,请注意,如果你需要生成64位的应用,请在这里面勾选generate 64bit Executeable。

如果你选择了这个,你应该使用64位的JDK。接下来配置构建路径,点Java invocation,然后这样:

如果你只有一个jar包,就点Archive,如果你有很多,请把他们放到一个文件夹里面,选择Directory,然后选择那个文件夹,然后ok。


注意,选择主类的时候,如果你在使用Springboot,请使用JarLauncher,如上图。

这个是Native library选项,如果你有jni类库,可以在这里配置他们的位置。接下来开始配置JRE。

点击左侧Search sequence,继续配置

删除所有默认的项目,然后添加一个directory,位置填写为“.”。

如图所示,到此为止,直接点击finish,exe4j就会生成一个exe,同时你会看到这个:

到此大功告成,exe生成成功,点击Click here to start the Application就可以运行exe了。如果你发现exe没有顺利启动,那么请在任务管理器关闭exe,然后检查前面的配置是不是哪里出错了,如果你发现任务管理器没有对应进程,那么请使用cmd的tasklist命令,这里肯定找得到,找到之后使用taskkill关闭它。例如:我们运行tasklist可以得到这样的东西:

被圈起来的部分就是pid,我们要执行这样的命令结束进程:taskkill /f /pid [进程的pid]结束所有的你的进程之后,就可以删掉exe,修改配置,然后重新点击左侧的finish构建exe。那么是不是一切到此结束了呢?还不是,我们需要保存exe4j的工程文件,然后从工程文件打开exe4j,接着改配置:

distribution sorcery directory的路径改为“.”

icon file的路径改为相对路径

classPath改为相对路径,如果有jni的话,native library改为相对路径,到此为止,finish完成最终构建,保存工程,打包完成,这样,之前的jre文件夹就是你的工程的打包文件夹,他看起来会比较乱,删掉你没有用的那些之后,就可以压缩发给其他人了。java8 打包总结首先呢,我其实用过很多中打包工具,包括exe4j,javapackager现在介绍的两种,除此之外还有jsmooth,install4j等等,但是都没有这两个友好,更关键的是第三方工具我目前知道的也就是exe4j还在更新,他目前甚至支持最新的JPMS模块化系统。相比javapackager,exe4j的redirection我觉得很好用,因为如果你没有做log的工作的话,这样一个输出对于调试是很友好的。
Java 9+的打包
这里为什么要单独说呢?是因为jdk在jdk9之后开始模块化了,它提供了一个jlink,我们可以只用jlink添加自己需要的jdk模块,也是从这个开始,不在提供单独JRE。
JRE需要你自己根据需要进行Jlink链接获取。所以,如果想要在jdk9以上版本进行打包,在jdk9 – jdk11这个时候,有两种选择:
一个是直接按照jdk8的方式进行打包,这个是毫无疑问可以成功的,唯一不一样的是你需要生成一个JRE,直接生成一个带有所有jdk模块的jre就行了。
但是如果你使用了module-info这个东西,那么事情就变得不一样了,你需要知道自己使用了那些jdk的module,因此我们首先需要分析整个应用的模块依赖性。那么该怎么做呢?
jdk8开始,java就提供了一个依赖分析工具,叫做jdeps,分析依赖模块就靠它了:
jdeps -s –recursive –multi-release [JDK版本] –module-path “[添加jar包的路径,不同路径使用分号隔开(在java9+版本中,这个被称作module-path)]” [被分析的jar包路径]
解释一下这个命令行:-s 仅需要给出summary(摘要,汇总的信息)即可–recursive 这样可以分析传递性的依赖,防止分析的依赖不完整。–multi-release 有的时候jar包是多发行版的,添加这个flag可以防止出现错误。–module-path 模块路径,都已经用这个版本jdk了,我觉得应该知道这是什么,多个路径之间用分号隔开。然后最后面是jar包的路径。那么这样的命令,运行之后你应该看到类似下面的画面:

看到箭头后面的东西了吗?那些就是依赖的模块了,找到里面的以java和javafx和jdk开头的模块,抄到一个记事本里面,一会要用。
抄下来的模块名之间要用逗号隔开,类似于这样:java.base,javafx.fxml,jdk.Jsonobject
接下来使用jlink生成JRE,命令这样写:
jlink –module-path “[这里写Jmod文件的路径(一般就是javafx的jmods路径,如果有其他的模块也可以写在这,路径之间用分号隔开)]” –add-modules [这里黏贴刚刚抄下来的模块名] –output .\runtime
到此为止,我们就有了一个JRE啦,但是要注意,jre是不完整的,为什么不完整连接进去呢?这是因为到目前为止很多jar不是标准的模块化的jar,他没有module-info.java这个模块描述符。没有描述符,就无法被jlink,因此我们只连接jdk的标准模块,得到的JRE是这样的:

通常来说,这个jre的体积会十分友好,相比java8的100MB+,这些文件都十分小巧了。
JRE完成之后,进一步打包的方式也是两种,一个是javapackager,一个是exe4j,但是注意exe4j你的用高版本的。不过到现在还不着急打包,因为你得确定jre好用,应用能正常启动。
首先,如果你用了javafx,那么javafx的jar包,都是不需要的,因为被jlink加到jre内部了,其他的jar包如果你也有一些放到了jlink里面,那么再打包的时候,也需要删除。然后我们使用jlink得到的jre来运行jar包,注意观察有没有缺少的模块,如果缺少(出现FindException)记得重新通过jlink生成JRE,不在JRE里面的jar包你需要通过参数指定到module-path里面,具体来说就像这样:
runtime\bin\java –module-path “[模块路径,如果有多个,路径之间用分号隔开,jlink过的模块不要放进去]” -m [你在module-info里面指定的模块名]/[你的启动类名]
如果顺利启动,那么我们就能够继续打包了。这里常见的问题:
- FindException 模块没有找到,javafx,jdk,java开头的模块,检查jlink有没有加上,如果是其他的jar包,检查你的module-path里面有没有。
- module reads package [包名] from both [模块名] and [模块名],通常是jar包有重复,因为都到打包的地步了,如果是之前的问题也早该处理好了,这个时候删除你用不到的jar包即可,比较常见于lombok和asm的冲突,你可以对lombok的require语句改为require static。
通常就是这种模块和依赖的问题。使用jPackager打包jpackager需要至少JDK14,恩,最新的版本当然更好,它也可以打包没那么新的java,你可以使用它打包java11之类的,另外还需要一个WIX,请到GitHub下载:
然后解压,放到合适的位置后,将wix的二进制文件目录加入PATH环境变量。
最后,通过这样的命令就可以完成打包啦:
jpackage –runtime-image [JLink生成的JRE] –type app-image -n [应用程序名称] -p “[jar包路径,里面应该包含除了jlink进去的包之外的包]” –icon [“图标文件的路径”] -m [module-info里面定义的模块名]/[主类名]

在windows下打包出来就是这样的。解释下命令行参数:
–runtime-image 指定jre路径的参数。
–type app-image,生成绿色版的应用,不制作安装程序
-n 应用程序名称,可执行文件和生成的文件夹名称就是这个
-p 模块的路径
-icon 图标的路径
-m 指定主类和主模块
果然还是命令行打包简单粗暴。exe4j打包和java8步骤基本一致,但是注意不要在使用classpath了,要把jar包放进module-path,同时main类也要在module-path里面找。

然后使用的jre就是jlink得到的jre,其他步骤参考上面java8的步骤。
结语
关于javapackager和exe4j,其实都还可以,但是有一点要注意,如果你选择使用javapackager,那么打包后的应用的路径不要出现中文,因为很有可能导致一些路径上的问题,尤其是java8的javapackager。





