尧图精选

Java jar打包exe实战:从exe4j到jpackage的完整方案

🕒 发布时间:2026/10/1 22:45:19 📁 来源:尧图网络
做我们这行的最怕的不是写代码而是写完代码之后跟别人说“你先装个JDK配置下环境变量再双击这个jar包”。说实话jar包这个东西自己用没问题可一旦要把工具交给不懂技术的同事、客户或者想发布成一个小软件挂在网上那它就是个门槛。对方双击jar包毫无反应或者弹一个“找不到主类”的报错你就得远程一顿操作最后往往以“要不你装个360驱动大师”收尾非常崩溃。所以“Java jar打包成exe应用程序”这个需求本质上不是技术炫技而是解决Java程序分发给终端用户时的环境依赖问题。我自己做过不少内部工具也帮朋友打包过几个License生成器、报表小助手之类的东西这条路踩过的坑足够写个连载了。这篇就把我实际用过、验证过的最靠谱的几套方案从原理到步骤一步一步拆开揉碎了讲清楚。你会搞清楚exe文件里到底装了什么为什么有些exe只需双击就能跑以及不同场景下该选哪条路。1. 先搞清楚exe的本质它到底帮你做了什么1.1 为什么jar包双击跑不起来exe就能很多人对打包有一个误区觉得把jar变成exe是某种“格式转换”就像MP4转AVI一样。其实完全不是。Java的jar包本质上是一个zip压缩包里面装的是一堆编译好的class文件它需要Java虚拟机JVM来解释执行。你双击jar包时操作系统会调用javaw.exe让它在JVM里跑这个jar——前提是系统装了Java运行时环境JRE而且文件关联配置正确。而exe是Windows操作系统直接认识的可执行文件它的启动流程和jar完全不一样。把jar打包成exe表面上是换了个文件后缀实际上是给你这个程序做了一个Windows原生外壳。系统双击exe时它通过这个外壳去加载JVM然后启动你的Java程序。这个过程对终端用户是透明的他们不需要知道Java是什么不需要配置环境变量看起来就是一个原生的Windows软件。实际操作的时候有些exe是把JRE和jar一起打包进去的文件体积会暴涨到几十上百MB但换来的是在任何Windows机器上都能跑有些则是只生成一个轻量的启动器运行时会搜索系统里已经装好的Java环境找不到就提示用户安装。这两种思路没有绝对的好坏取决于你给谁用。1.2 四套主流方案横向对比市面上把jar做成exe的工具和思路非常多我按实际使用体验选出四条最主流的路子方案原理是否内嵌JRE上手难度适用场景exe4j生成一个Windows启动器加载已有JRE运行jar可配置默认不内嵌低图形化界面一步步点内部工具、给同事用、快速交付Launch4j同样是生成启动器纯开源免费支持命令行不内嵌需外部JRE低但界面稍显老旧开源项目、有洁癖不喜欢商业软件的人jpackageJDK官方工具原生支持打包exe、msi、dmg可以内嵌精简JRE中需学命令行个人作品、正式发布、需要安装在多台机器GraalVM Native Image直接把Java代码编译成原生机器码不再需要JVM天然就是原生exe高对代码有侵入性追求极致启动速度和内存占用不怕折腾这几条路我都实测过。下面不会全讲一遍那样太水了重点放在exe4j和jpackage这两条主线上前者是我平时最常用的快速交付手段后者是JDK官方钦定的正规军。Launch4j的操作逻辑和exe4j几乎一样会了一个另一个看两眼就会。GraalVM单独当个彩蛋说因为它涉及的东西跟“打包”关系不大而是另一种运行形态了。有一点需要先给大家打个预防针不管用哪种方案Java程序打成exe之后仍然需要在目标电脑上有对应的Java运行环境才能跑除非你手动把JRE打包进去。为什么因为class文件到了JVM里还是解释执行的JIT也是运行时才触发exe壳只是负责“叫醒”JVM真正的活儿还是JVM干的。这一点骗不了人。所以说如果你追求的是“一个exe打天下什么依赖都不装”那技术上能做到把JRE塞进去代价就是安装包体积蹭蹭往上涨。后面的事例里会具体算这笔账。2. 动手前的准备工作环境和心态2.1 环境清单一次配齐见过太多人卡在第一步本地代码能在IDEA里跑但一打包就报错。这里的水很深我按自己平时开新项目的习惯整理一个标准环境清单JDK 8 或更高版本建议JDK 8或者JDK 11这两个版本的企业级使用率最高兼容性也最稳Maven 或 Gradle绝大多数Java项目都有依赖需要用它来构建出完整可运行的fat jar以WinRAR或7-Zip为代表的解压工具后面排查问题时会用到用来看jar包结构一个准备用来测试的“干净”Windows环境最好是虚拟机或者家里那台没装Java的旧电脑这一步非常重要有一点切记打包机的JDK版本和目标机器要尽量一致。比如你用JDK 17编译出来的class拿到只装了JRE 8的机器上跑十有八九会报UnsupportedClassVersionError。这不是exe的问题是Java版本兼容的老问题。实际项目里我一般固定用JDK 8来出包因为JRE 8的装机量最大兼容性最好。群晖、威联通这些NAS自带的Java运行环境也基本都是8。2.2 先把Spring Boot应用打成可执行的fat jar不管下一步用哪个工具打包exe前提都是先有一个“能独立运行的jar包”。如果你用的是Spring Boot这里有个关键操作用maven-shade-plugin或者Spring Boot自带的spring-boot-maven-plugin把所有依赖lib都打进去生成一个fat jar也叫uber jar。普通的jar包只包含你的业务代码不包含第三方库单独运行必然NoClassDefFoundError。我在pom.xml里最常用的是spring-boot-maven-plugin它有一个repackagegoal可以把带依赖的jar包重新整理成一个可执行jar。操作很简单build plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId configuration mainClasscom.example.MainApplication/mainClass /configuration executions execution goals goalrepackage/goal /goals /execution /executions /plugin /plugins /build然后执行mvn clean package打完包之后别急着进行下一步先做两个验证。第一打开target目录看jar包大小是不是至少有10MB以上Spring Boot项目通常20MB起步如果只有几百KB说明依赖没打进去。第二在命令行里手动执行一遍java -jar your-app.jar能正常启动说明这个jar本身没有问题。为什么非要这么严谨因为我见过太多人在这一步就翻车打成exe之后在别人电脑上报错排查半天最后发现是自己的jar包压根不能独立跑白白浪费几个小时。3. exe4j实操图形化打包最省心的快速方案3.1 准备一个精简版JRE这一步很多人不知道但它是让exe“双击即用”的关键。exe4j默认生成的exe只是启动器不会把Java环境打包进去所以目标电脑必须装JRE。如果你希望对方省去装JRE这一步那就手动准备一个精简版JRE跟exe放在同一个目录下分发。获取精简版JRE的方式很简单。先在本机装好JDK比如JDK 8然后执行命令jlink --add-modules java.base,java.desktop,java.logging,java.sql,java.naming,java.management,java.security.jgss,java.instrument,jdk.unsupported --output runtimejlink是JDK 9之后自带的一个模块裁剪工具它可以根据你指定的模块列表生成一个精简的JRE目录只需要几十MB比完整JRE的200多MB小多了。模块列表怎么确定看你的项目用了哪些Java功能。如果项目里有GUI加上java.desktop用到了数据库加上java.sql用到日志框架加上java.logging。不确定的话宁可多加一两个模块也不要少加导致运行时报ClassNotFoundException。命令执行完当前目录会生成一个runtime文件夹里面有bin、lib、conf等目录。这个就是后面要分发的JRE。实测下来一个Spring Boot项目用这套精简JRE跑起来没问题文件体积从原来的200MB压缩到40~50MB效果好得很。注意jlink需要模块化的JDK9以上才能用。如果你只用JDK 8那我建议直接在网上下载一个便携版的JRE8效果一样。3.2 exe4j安装与核心配置逐项拆解exe4j是ej-technologies公司的商业软件但是它的基础功能免费只是打包出来的exe会有一个启动时的水印提示框注册后消失。如果你只是给同事内部用免费版完全够了。下载安装的过程就不啰嗦了重点说向导页面里那些选项是什么意思很多人瞎点一遍根本不知道自己在选什么。打开exe4j之后选择“Jar in exe”模式这是最常用的模式——把jar包作为程序核心用exe启动器来调用它。然后会进入一个配置向导我把关键步骤列出来第一页Project type选“Jar in exe”其他选项保持默认。第二页是程序信息页Program name就是你最终生成的exe名字随便起最好别带中文。这里有个小坑exe4j免费版在老版本里对中文路径支持有问题后来修复了但是为了保险起见整个项目路径、输出路径、文件名一律用英文否则某些神奇的中文乱码问题就够你喝一壶的。第三页是Executable info设置exe文件的生成方式里面有一个“Change working directory to”的下拉框这个选项特别容易被忽略但作用很大。它决定程序启动时的工作目录是哪里。如果你的项目里有读外部配置文件、写日志文件这类操作路径计算都是基于工作目录的选错了就会找不到文件。一般推荐选“Set the working directory to the directory of this executable”也就是让exe所在目录成为工作目录这样你把整个文件夹拷到哪里程序都能正常工作。第四页是Java invocation这一步是整个配置里最核心的。界面里可以设置启动时传给JVM的参数比如内存参数。列表里默认会有-Djava.net.preferIPv4Stacktrue这种常用项我一般再手动加上-Xms64m -Xmx512m限制一下内存占用防止一些写的烂的程序把内存吃干。然后重点来了Classpath一栏点击“Add”选择你的jar包确保它出现在列表里。如果你用了第三方依赖且没有打fat jar这里可以继续把依赖jar包都加进来。Main class那栏填你的启动类全类名比如com.example.MainApplication千万别漏了包名。这儿可以点旁边的“Browse”按钮让工具自动扫描jar包里的main方法如果扫不出来或者扫出来多个说明jar包没打干净或者里面确实有多个入口需要回头检查一下。最后一路Next到Finish点击生成按钮exe文件就会出现在输出目录里。3.3 目录结构与分发策略exe4j默认只是生成一个孤零零的exe文件但你分发的时候不能只给这一个文件。除非目标电脑已经装了匹配版本的JRE否则必须配套一个runtime目录。我建议分发的目录结构是这样的你的工具文件夹/ ├── mytool.exe ├── runtime/ │ ├── bin/ │ ├── lib/ │ └── conf/ └── config/ 可选的配置文件目录把这个文件夹打包成zip发给对方对方解压后点exe就能跑。文件夹整体放到哪里都行桌面、D盘、U盘都可以因为前面设置了工作目录跟随exe所在位置所以它不会出现“找不到配置文件”这种幺蛾子。实测时我试过把这个文件夹整个拷到一台没有安装任何Java环境的Windows 10电脑上双击exe程序三秒内启动没有弹任何安装向导没有配环境变量整个过程跟原生软件完全无差。这才是分发工具该有的体验。4. jpackage实操JDK官方方案正规军的打法4.1 为什么还要学jpackageexe4j的问题是什么它是商业软件核心功能虽然免费但总给人“临时工”的感觉。而且它做的exe本质上还是一个壳跟真正意义上的Windows安装包还是有距离。如果你做的是一个正儿八经的产品想发布那种带安装向导、能写入开始菜单、能在“控制面板-程序和功能”里被卸载的完整应用那就得学学JDK官方提供的jpackage工具。jpackage从JDK 14开始引入在JDK 16正式转正它是官方钦定的打包工具。它能做的事情包括但不限于生成exe、生成msi安装程序、生成自定义安装包自动把JRE打包进去生成一个精简的运行时镜像支持自定义图标、版本号、版权信息、授权协议等。也就是说你想要的一切正式发布相关的功能它都能做到。4.2 先用jlink裁出精简运行时jpackage强大归强大但它的默认行为是把JVM相关的所有可能用到的组件都放进去导致生成的安装包动不动就是100MB。这时候需要配合jlink一起用先把运行时裁剪到最小再让jpackage基于这个最小运行时来打包。我实际用的命令是这样的# 第一步构建fat jar mvn clean package # 第二步用jlink裁剪JRE只保留必要模块 jlink --add-modules java.base,java.desktop,java.logging,java.sql,java.naming,java.management,java.security.jgss,java.instrument,jdk.unsupported \ --strip-debug \ --no-man-pages \ --no-header-files \ --compress2 \ --output runtime # 第三步通过jpackage打包exe并指定使用裁剪后的runtime jpackage \ --name MyTool \ --input target/ \ --main-jar mytool.jar \ --main-class com.example.MainApplication \ --runtime-image runtime \ --type exe \ --dest dist \ --icon icon.ico \ --vendor MyCompany \ --app-version 1.0.0 \ --win-menu \ --win-shortcut简单解释一下每个参数的含义不然你抄了也不知道做了什么--name指定应用名称会用在安装目录、开始菜单快捷方式上--input指向包含jar包的目录jpackage会把这个目录下的所有文件作为程序内容的一部分--main-jar指定主jar包的名称--main-class指定启动类如果jar包的MANIFEST.MF里有Main-Class属性这个参数可以省略--runtime-image就是刚才jlink生成的runtime目录这一步是瘦身的关键--type exe指定生成Windows安装程序--dest指定输出目录--icon指定图标必须是.ico格式不支持jpg/png直接改后缀网上有个在线转换工具可以生成--win-menu和--win-shortcut让安装程序自动创建开始菜单快捷方式和桌面快捷方式命令执行完dist目录下会多出一个MyTool-1.0.0.exe安装程序双击运行会走一个标准安装向导安装完能在开始菜单找到程序入口能在控制面板卸载。这才是“软件”该有的样子。4.3 jpackage的坑模块缺失、图标格式和非模块化项目jpackage这套流程最大的坑在于--runtime-image指定的运行时里如果少了程序实际用到的模块启动时会报各种ClassNotFoundException或者NoClassDefFoundError而且这个报错往往要到目标机器上跑起来才会出现排查非常费劲。我的经验是第一版jlink的模块列表尽量保守把常用模块都加上等程序确认没问题了再一个一个剔减。通常情况下的安全名单是java.base、java.desktop、java.logging、java.sql、java.naming、java.management、java.security.jgss、java.instrument、jdk.unsupported、jdk.crypto.ec、jdk.crypto.cryptoki。别问为什么问就是这些在常见业务系统里几乎是标配依赖。另外图标那个坑必须单独说。jpackage对图标格式要求非常严格必须是标准的.ico格式推荐使用256x256的icon。有些同学拿一张png直接改后缀一路噼里啪啦报错最后还以为是命令写错了。怎么办网上找一个png转ico在线工具右键目标任一png图片上传转换下载替换就可以了。还有一点容易忽略如果你的项目不是模块化的大多数业务项目都是非模块化的jpackage会默认用类路径方式加载这样没问题。但如果某个依赖在运行时非要通过模块路径去加载那就需要用--add-modules或者--module-path指定。这个情况比较少见但真遇到了也别慌先在本地用java -jar跑一遍确认jar本身没问题再回头看是不是运行时缺模块。5. 踩坑实录我拿真金白银换来的经验教训5.1 问题速查表碰到直接找答案我把这几年帮别人打包遇到的高频问题整理成了一个速查表。遇到问题先对照这个表格排查现象根本原因快速排查方法解决方案生成exe后双击没反应启动器没找到JRE或者jar包本身有问题命令行执行mytool.exe -XshowSettings:java看有没有输出确认runtime目录存在确认jar包在本地能java -jar启动双击弹出控制台窗口但马上消失主类配置错误程序启动即崩用命令行执行mytool.exe --verbose会打印完整错误栈核对Main Class填写是否正确检查jar清单里的Main-Class报UnsupportedClassVersionError编译JDK版本 运行JRE版本在目标机器执行java -version确认版本用低版本JDK重新编译或者给目标机器配高版本JRE在开发机正常拷贝到别的电脑报找不到类目标机器JRE缺模块或者fat jar没打全用7-Zip打开jar包看lib下有没有依赖jar重新用spring-boot-maven-plugin打fat jar中文文件名/中文路径乱码打包工具和Windows默认编码不一致看错误信息有没有?或乱码全链路使用英文路径代码里不要硬编码中文路径生成的exe被杀毒软件误报某些打包工具生成的壳特征类似木马检查Windows Defender历史记录换用jpackage这类官方工具或者给文件做代码签名打包后程序启动很慢运行时内嵌了过多模块或者程序启动逻辑慢对比开发机的启动时间裁剪jlink模块列表检查程序里有没有同步初始化第三方SDK5.2 最典型的两个坑本机能跑、别人机器跑不了这个问题的典型原因我在上文展开过多次了不外乎两种一种是JRE版本不匹配另一种是JRE模块缺失。在这想补充一个容易被忽略的细节某些Java代码直接或间接用到了非标准JDK内部类比如sun.misc.BASE64Encoder、com.sun.image.codec.jpeg.JPEGCodec这种。老版本的JDK 8里这些类还在但JRE 11之后默认不允许访问了。如果你的项目用的是JDK 8编译但目标机器装的是较新的JRE这种代码可能直接炸掉。排查的时候别忙着重打包先在目标机器上手动执行一遍java -jar mytool.jar把错误信息看清楚。这个过程能给你省下大量瞎猜的时间。5.3 安全风险别以为打成exe就万事大吉了很多同学把代码打成exe之后就觉得“我这是编译后的原生程序代码不可能被看了”。这是个很危险的误解。Java程序不管包成什么壳核心字节码class文件依然是可反编译的。网上随便搜一下“jar反编译工具”用JD-GUI这类工具拖进去源码结构、类名、方法名、甚至部分逻辑基本一览无余反编译出来的代码可读性还不错。如果项目涉及商业逻辑、账号密码、加密密钥这类敏感信息打成exe并不能帮你保密。怎么办我曾经处理过的一个方案是把核心算法单独拆出来用GraalVM Native Image把它编译成纯机器码再用JNIJava Native Interface在Java代码里去调用。这样外人反编译得到的是接口调用但核心实现的机器码基本没法还原。代价是开发和构建复杂度翻倍所以这个方案只适合核心逻辑外围代码不需要这么干。还有更实际的建议不要在代码里明文写数据库密码、第三方API密钥。即便打成exe别人一个反编译就能看到。正确的姿势是把这些配置放到外部文件里并做好对应的权限控制。6. 进阶彩蛋GraalVM Native Image到底有多神这个内容原本不打算写但是热词里“graalvm打包成exe”“java 启动速度”相关的内容实在太多了那就当作为进阶方向简单聊聊但先声明这套方案不适合初学者也不适合特别复杂的项目。GraalVM Native Image的核心思路是把你的Java代码包括所有依赖、JVM必要的运行时组件直接AOT编译成一个原生exe可执行文件。它不再需要JVM或者说它把JVM“溶化”到了这个exe里。这样带来的效果是启动时间从原来的2~3秒降到几十毫秒内存占用缩小数倍而且整个程序只有一个exe文件不需要任何运行时环境分发体验最好。但代价也是明显的。第一很多Java生态的反射、动态代理、字节码增强玩法在这个模式下默认是跑不起来的除非你在配置文件里显式注册。像Spring Boot全家桶、Hibernate这类重度使用反射的框架要在Native Image下跑起来得费很大的劲去适配。如果你用一个正常的Spring Boot项目去试GraalVM大概率会得到一堆错误。第二构建时间特别长而且需要安装GraalVM编译器外加本机C语言编译环境光是配置环境的复杂度就劝退了一大半人。我什么时候推荐用这个方案如果你的程序本身就是纯命令行工具、工具类软件没有数据库、没有复杂的Spring容器代码逻辑也足够“透明”不使用反射那GraalVM确实是一个非常惊艳的选择。打包出来的exe单文件只有几十MB启动速度飞快往U盘里一放拿到哪都能跑简直不要太爽。如果做的是正式的企业级业务系统那目前还是老老实实用jpackage吧。7. 实战小技巧打包记忆口诀和自动化其实写到这儿核心内容已经全部分享完了。但按照惯例最后再送两个实战中得出的技巧一个是更方便的记忆法一个是省时间的自动化思路。其实打包的经验浓缩下来无非就是三句话先把jar包弄干净fat jar成型再把JRE搞小小精简运行时最后选对壳子exe4j/jpackage。这三步缺一不可很多人打包失败要么是第一步偷懒了要么是第二步根本不知道还有这回事。然后说自动化。如果是团队内部频繁出工具包我强烈建议花一下午时间把打包过程写成一个批处理脚本或者Maven插件配置。我自己是把exe4j的配置导出成.exe4j文件之后再用命令行执行exe4j.exe mytool.exe4j来打包这样每次出一个新版本只需要两步双击构建脚本、在dist目录拿exe。省下来的时间多刷几套java面试题不香吗。最后再单独说明一点如果你正在用IDEA开发一个非Maven项目手动导入了一堆外部jar包那打包的时候就要格外小心。这类项目的fat jar不会自动生成建议顺手配上maven-shade-plugin把依赖手工打进去。IDEA官方教程里也有“Artifacts”打jar包的方式但那个方式对依赖的处理很脆弱稍有不慎就会漏包。我和Java打包这件事打了这么久的交道最大的体会是不要迷信任何一个工具先搞清楚你的jar里到底有什么目标机器上缺什么然后选最省事的方案。工具只是形式搞清楚原理才是解决问题的关键。如果有人看完这篇能少搜三次“jar 打包 exe 教程”那这篇文章就没白写。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →