ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Eclipse 导出可执行 Jar 全攻略:主类、依赖与避坑实操

Eclipse 导出可执行 Jar 全攻略:主类、依赖与避坑实操 简介面向 Java 开发者与 Eclipse 使用者的实操教程专门解决将 Java 工程导出为可执行 JAR 文件、并正确包含第三方依赖的问题。资料以 PDF 文档呈现共 1 个文件压缩包大小仅 197KB便于下载后随时翻阅。教程从 Eclipse 的 Export 向导讲起重点演示 Runnable JAR file 的导出方式并说明处理第三方 Jar 包的推荐做法同时特别提醒打包后 Run Configuration 中设置的 JVM 参数不再生效需要以启动命令为准并给出配置文件 conf 目录的放置方法以及利用 run.bat 一键启动的简化方案。对于经常需要部署爬虫脚本、后台服务或独立工具程序的开发场景这份资料能帮助读者减少反复试错快速完成可执行 JAR 的打包与分发。目前已有 2021 人学习下载适合刚开始接触打包、需要处理多模块依赖配置的 Java 学习者参考。1. Eclipse 里导出的 Jar 跑不起来问题多半不在 Eclipse 身上用 Eclipse 导出可执行Java工程/可执行Jar文件包含第三方Jar包是大多数 Java 开发者从能写代码走向能交付工具时绕不开的一道坎。现象往往一致IDE 里右键 Run 顺风顺水Export 完成后在命令行敲 java -jar却报找不到或无法加载主类或者跑不了几行就抛 ClassNotFoundException。多数人第一反应是怪 Eclipse 的导出功能难用实际上翻车点集中在三个地方主类没有正确写进清单文件、第三方依赖没被打进去、依赖被解压合并后出现同名类覆盖。这篇笔记沿着工程准备、导出配置、依赖策略、踩坑排查、命令验证的顺序把每条路径的参数和副作用一次讲清楚适合正在用 Eclipse 写 Java 工具、需要把成果交付给同事运行的人。为什么强调包含第三方 Jar 包因为只导出自己的代码任何 IDE 都做得很好真正的分水岭就在依赖处理上。Eclipse 给了三个选项翻译得有些含糊很多人选错后拿到的是外表完整、内里残缺的 Jar。下面从打包前要确认的三件事讲起。2. 导出前的工程准备先认清主类、清单文件和第三方依赖2.1 主类到底是什么Runnable 类的判定标准在打开导出对话框之前第一件事是确认工程里存在一个能被 JVM 直接调用的入口。所谓可执行 Java 工程编译后的产物里必须有一个包含正确签名 main 方法的类。Eclipse 判断可执行的依据就是这个方法不是类名也不是工程名。一个合法的入口长这样package com.example.report; public class ReportGenerator { // 这才是 java -jar 能识别的入口 public static void main(String[] args) { System.out.println(report tool start); } }这里的三个修饰符 public、static、void 一个都不能少。如果 main 方法是 private或者缺少 String[] args 参数Eclipse 的 Launch configuration 里就不会出现这个类导出界面会直接提示没有可用的启动配置。还有一种常见写法是把 main 放到非 public 类里编译没问题但 Java 规范要求启动类本身必须是 public否则某些 JVM 实现会报主类找不到而 IDE 里 Run As 却能跑通这种不一致最容易误导人。我自己的习惯是在导出前先做一次双确认在包资源管理器中选中目标类右键 Run As - Java Application如果能正常启动才继续走导出流程。这个动作不值钱但能排除掉 50% 的导出后跑不起来问题。另外main 方法所在类尽量不要放在默认包里也就是不声明 package 的那个包。Eclipse 能编译MANIFEST.MF 里写Main-Class: MainApp也能跑但一旦你的代码和某个第三方 Jar 存在同名类默认包类在类加载时的优先级就会变得很不可控。哪怕工具只有两个文件我也建议建一个 com.xxx.util 之类的最小包名这个习惯能省掉很多类加载层面的玄学问题。2.2 MANIFEST.MFMain-Class 和 Class-Path 才是执行的核心Jar 文件本质是一个 ZIP 包只是多了 META-INF/MANIFEST.MF 清单文件。java -jar 启动时JVM 先读这个清单文件找到 Main-Class 指定的类再把 Class-Path 里声明的 Jar 加入运行时类路径。Eclipse 导出向导会自动生成清单文件但理解它的格式才能排查问题。一个典型的 MANIFEST.MF 长这样Manifest-Version: 1.0 Main-Class: com.example.report.ReportGenerator Class-Path: lib/commons-io-2.11.0.jar lib/gson-2.10.1.jar这里有两个格式要点。第一Class-Path 里多个 Jar 之间用空格分隔不是逗号也不是分号路径是相对于 Jar 文件所在目录的相对路径。第二清单文件每行最长 72 字节含换行符超过就必须续行续行以空格开头。Eclipse 自动生成时会把长行拆成多行这是合法格式但如果有人用记事本手动拼很容易在行尾多一个空格或少一个换行启动时直接报错。还有一个容易被忽略的编码问题。清单文件的写入编码在不同 JDK 版本上行为不一致如果你的类名或路径里含中文且工程的编译编码不是 UTF-8导出的 Jar 换台机器就可能找不到主类。所以导出前我会先看一眼工程的 Resource 属性确认 Text file encoding 是 UTF-8而不是继承平台默认的 GBK。这一点在避坑章节还会再提。2.3 梳理第三方依赖Maven 工程和普通工程各自的做法导出可执行 Jar 前得先知道工程到底依赖了哪些第三方 Jar。依赖来源不同处理方式完全不同。如果是 Maven 工程依赖清单在 pom.xml 的 里。Eclipse 的 Runnable JAR file 向导能自动读取 Maven 依赖并参与打包但前提是本地的 Maven 仓库状态是干净的。我常用的确认命令是mvn dependency:tree这条命令会把所有依赖的传递关系列出来比在 Eclipse 界面里一个个点开看更完整。mvn dependency:tree 输出里能看到每个依赖的 groupId、artifactId、version 和 scope。你真正要关注的是 scope 为 compile 和 runtime 的依赖这两类需要进 Jarscope 为 provided 的比如 servlet-api由运行环境提供打进去反而可能引发类冲突。如果你看到 optional 标记的依赖注意 optional 不影响传递但 Eclipse 不一定把它打进去运行到相关功能时才报 NoClassDefFoundError。如果是普通 Java 工程没有 pom.xml第三方 Jar 一般集中在工程的 lib 目录。这里有个高频翻车点把 Jar 拖进 lib 文件夹并不等于加入了 Build Path。Eclipse 编译时只看 Java Build Path 里配置的类库不在 Build Path 里的 Jar 对工程不可见导出时自然也不会包含。验证方式是在 Project Explorer 里展开工程的 Referenced Libraries 节点看里面是否列了你放进去的 Jar没有的话右键工程 - Build Path - Configure Build Path - Libraries - Add JARs 或 Add External JARs选中对应文件后再导出。还要区分一个概念编译期依赖和运行时依赖。有些 Jar 只是编译期需要比如代码生成器和注解处理器运行时用不到有些则是运行时通过反射动态加载的编译期根本没有直接引用。Eclipse 的导出向导不区分这两者它按 Build Path 里的清单全部打包。所以如果你发现导出的 Jar 体积异常大多出来的往往是编译期才用的东西反过来如果运行时报 ClassNotFoundException优先检查那个类在哪个 Jar 里以及那个 Jar 是否真的在 Build Path 中。3. Eclipse 导出可执行 Jar 的操作路径与三种库处理方式3.1 选对入口Runnable JAR file 而不是 JAR fileEclipse 的 File - Export 里有好几个和 Java 相关的选项很多人一路点到 Java - JAR file生成一个普通 Jar 就交付结果必然跑不起来。普通 JAR file 导出只做一件事把编译后的 class 文件打成一个包不写 Main-Class也不处理第三方依赖。真正生成可执行 Jar 的入口是 Java - Runnable JAR file这个名字直译就是可运行的 Jar。完整操作路径如下在包资源管理器中选中工程根节点不是选中某个类。菜单栏 File - Export...弹出窗口搜索框里输入 Runnable 三个字母选中 Runnable JAR file点 Next。Launch configuration 下拉框里选择之前 Run As 过的启动配置。列表为空说明工程里没有可运行的 main 方法回上一章检查入口类。Export destination 填输出 Jar 的完整路径。我一般输出到工程外部的 dist 目录避免把 Jar 写进工程的 bin 或 target 目录造成输出互相覆盖的错觉。Library handling 单选组里选择依赖处理方式这个在 3.2 详细展开。如果勾选 Save as ANT scriptEclipse 会生成一个 ant 脚本把这次导出配置固化下来。以后改了一点代码可以直接命令行执行 ant 脚本重新打包不用再点一遍界面。这个选项目前不常用但值得知道。很多 Eclipse 使用教程只会教你 Export - JAR file 把 class 存出来那对交付一个可执行程序帮助不大。判断方法很直接导出完成后右键 Jar 用解压软件打开看里面有没有 META-INF/MANIFEST.MF以及清单文件里有没有 Main-Class 这一行。没有就是选错了入口。3.2 Library handling 三选项到底在干什么Runnable JAR file 导出界面里有一组单选按钮是整个流程里最核心的决策点。这三个选项名字翻译得有点绕我按实际行为解释第一项 Extract required libraries into generated JAR把每个第三方依赖里的 class 文件全部解压再合并进你生成的 Jar。最终产物只有一个文件交付最方便。缺点是依赖的 class 全部被拆散Jar 内既不保留原始 Jar 的结构也无法区分哪些 class 来自哪个依赖。如果多个依赖里存在同名类后处理的那个会覆盖先处理的程序运行到某个类时到底加载哪个版本完全取决于 Eclipse 内部的处理顺序。第二项 Package required libraries into sub-folders名字听起来是把依赖 Jar 原封不动放进子目录实际不是。它仍然是把依赖的 class 解压出来只不过统一放进生成 Jar 内的 lib/ 子目录。最终产物还是一个自包含 Jar但内部结构是平铺的没有任何隔离能力。后面避坑章节会说这个选项解决不了同名类覆盖问题。第三项 Copy required libraries into a sub-folder next to the generated JAR这个选项不碰依赖 Jar 的内部内容。Eclipse 把工程自己的 class 打成主 Jar在输出路径旁边生成一个 lib 文件夹把依赖 Jar 原样复制进去然后在 MANIFEST.MF 里写 Class-Path。这是三种方式里最容易排查问题、最接近原样交付的一种。这里有一个很容易误解的点第二项的内部 lib 子目录和第三项的外部 lib 目录有本质区别。内部 lib 子目录对 JVM 加载没有特殊含义class 还是按包路径整体加载的外部 lib 目录则对应 Class-Path 的相对路径JVM 按清单文件指过去加载原始 Jar。选错参数再去看网上那些只说导出时选第二项就够了的教程很难对得上号。3.3 三种导出方式的产物对比把三种方式导出的产物结构摆在一起看更直观处理方式产出物Jar 内部结构运行时额外依赖典型使用场景Extract required libraries into generated JAR单个 Jar第三方 class 散落在根包路径下无纯工具类、无签名依赖、项目简单Package required libraries into sub-folders单个 Jar第三方 class 在 lib/ 子目录下无希望 Jar 内部结构清晰一点Copy required libraries into a sub-folderJar 旁路 lib/ 目录不含第三方 class需要 lib 目录与 Jar 同级有签名依赖、需要人工排查类冲突参数选择上没有绝对正确我的默认建议是能接受单个文件交付就选第一项但前提是依赖里不能有签名 Jar且不会出现同名类冲突项目依赖稍微复杂一点立刻换第三项宁可交付时多带一个目录也不要后期被类冲突折磨。第一项还有一个隐藏问题有些依赖 Jar 自带 META-INF/services 文件里面声明了 SPI 服务实现。解压合并后多个 Jar 的 services 文件会互相覆盖导致运行时 ServiceLoader 找不到对应的实现类程序不报编译错运行到某个功能时才知道挂了。这个问题下文避坑章节会重点展开这里先记住一个结论合并越多依赖资源文件冲突的概率越大。4. 第三方 Jar 包怎么打进去三种策略的完整对比与操作细节4.1 策略一解压合并适合追求单个交付文件方案 A 的效果在前一章已经说了把所有依赖的 class 全部复制进你的 Jar。Eclipse 在导出时逐个扫描依赖 Jar按包路径复制 class 文件最终把几千个类揉在一起。对使用者来说交付物是一个文件双击或命令行一把就跑体验最好。但它有两个明确的副作用。第一是类冲突如果两个第三方 Jar 里出现同一个全限定类名Eclipse 按 Build Path 顺序覆盖后处理的覆盖先处理的。你的程序可能运行到一个不是你预期实现的类上表现是行为诡异不报错。第二是资源文件覆盖class 文件之外依赖 Jar 里若有同名配置文件比如 spring.factories、META-INF/services/xxx同样会被覆盖SPI 机制直接失效ServiceLoader 跑不出来。应对办法只有一个接受不了这两个风险就退回方案 C。如果坚持用方案 A导出后第一时间检查重复条目jar tf report-tool.jar | grep META-INF/services | sort | uniq -d这条命令会把 Jar 内所有 META-INF/services 目录下重复出现的文件名列出来。有重复说明有服务描述文件被覆盖了运行结果大概率不符合预期。这里的逻辑是sort 让相同的路径相邻uniq -d 只输出出现超过一次的行。看到输出后如果确认哪些依赖声明了服务回到方案 C 重新导出否则继续排查类冲突。4.2 策略二内部 lib 子目录视觉效果大于实际收益方案 B 的界面描述是 Package required libraries into sub-folders我实际用下来发现它解决的问题非常有限。它把依赖 class 解压后放进 Jar 内的 lib/ 子目录但 JVM 的类加载机制不关心 class 在 Jar 内的物理位置只关心 classpath 顺序所以这个目录差异对运行没有影响。唯一的好处是打开 Jar 时能大致看出哪些 class 来自第三方仅此而已。如果你指望通过Jar 内 lib 子目录实现依赖隔离比如两个依赖版本冲突这个方案做不到。所有 class 最后都在同一个 classpath 上同名类照样互相覆盖。真正的隔离只能靠独立的 ClassLoader那已经超出了 Eclipse 导出能解决的范畴。这个方案最常坑人的点是 getResourceAsStream 读不到配置文件。原本在某个依赖 Jar 根目录下的 config.properties解压合并后路径还是那个路径理论上应该能读到但如果另一个依赖里也有一份同名配置文件覆盖顺序决定你读到的内容可能是错的。我见过一个项目用方案 B 导出的 Jar中文乱码查了半天最后发现是多个依赖里的 messages.properties 互相覆盖加载到的资源根本不是当前代码要的那个。我的总结是方案 B 可以当成排得整齐一点的方案 A但不要指望它有隔离能力。选它不会比方案 A 更安全反而会让你对内部结构产生错误预期。4.3 策略三外部 lib 旁路最稳定但交付时要带目录方案 C 是三者中唯一保留第三方 Jar 原样的方式。Eclipse 生成你的主 Jar 时只输出工程自己的 class然后到目标路径下创建 lib/ 文件夹把依赖 Jar 复制进去最后在 MANIFEST.MF 里写 Class-Path: lib/a.jar lib/b.jar 这样的相对路径。这里最关键的一点是Class-Path 里的路径是相对 Jar 文件位置的相对路径不是相对当前工作目录。也就是说不管你在哪个目录执行 java -jar /path/to/app.jarJVM 都会自动去找 /path/to/lib/ 下的依赖。但如果有人只把 Jar 拷走而漏了 lib 目录就会报 ClassNotFoundException报错信息里能清楚看到缺哪个类。这个方案是三种里最可预测的问题定位简单缺哪个类直接去 lib 目录找对应 Jar类冲突概率低因为 Jar 还是原来的 Jar没有被解压合并。代价是交付物从一个文件变成一个目录。如果目标机器是内网隔离环境还要额外注意 lib 目录里所有 Jar 的路径不能含空格否则部分脚本解析 Class-Path 时会出错。方案 C 也有自己的坑Eclipse 不会递归处理传递依赖。比如你引入 A.jarA.jar 内部依赖 B.jar但 B.jar 没有出现在工程的 Build Path 里Eclipse 只会复制 A.jar不会自动把 B.jar 拉进来。运行 A 的代码时JVM 加载 A 的类A 内部引用的 B 的类找不到异常信息指向 B 的某个类很多人误以为是 A.jar 本身有问题。遇到这种传递依赖场景我一般先用 mvn dependency:tree 把依赖树拉出来看哪些 Jar 不在 Build Path 里然后手动加进去。4.4 手动调整 MANIFEST.MF当 Class-Path 顺序需要干预时如果依赖特别多Eclipse 生成的 Class-Path 顺序不一定符合你想要的加载优先级。比如两个依赖里都有同一个类你想强制让某个版本生效可以导出后用文本编辑器打开 Jar 内的 MANIFEST.MF手动调整 Class-Path 里 Jar 的排列顺序然后用 jar 命令回写jar ufm report-tool.jar MANIFEST.MF这条命令中 u 表示更新f 指定 Jar 文件m 表示使用外部清单文件。更新后MANIFEST.MF 里的内容会整体替换 Jar 内原有的清单文件。注意这里的 MANIFEST.MF 是你在外部编辑好的文件不是从 Jar 里解压出来的那份两者很容易搞混。手动编辑时的格式要求很严格每行 72 字节以内超过要续行续行以空格开头文件最后要有空行。很多人用记事本编辑完最后一行没有换行符jar 命令不报错但 JVM 在解析时可能把最后一行忽略偏偏那一行写的是 Main-Class然后你又回到找不到主类的循环里。所以在保存后我会用编辑器开启显示换行符确认文件末尾确实有一个空行。5. Eclipse 导出 Jar 避坑5 个高频问题与排查思路5.1 报错找不到或无法加载主类先查清单再查环境现象在命令行执行 java -jar app.jar输出 Error: Could not find or load main class com.example.MainAppJava Web 场景下可能是找不到或无法加载主类 org.apache.catalina.startup.Bootstrap本质上同一个问题。原因分三类一是 MANIFEST.MF 里的 Main-Class 写错或格式坏二是 main 类确实不在 Jar 里比如主类所在包没被编译或被排除了三是 JDK 版本不匹配用高版本 JDK 编译的 class 文件在低版本 JVM 上加载失败报错信息和主类加载不了一样。解决按顺序来。先用 unzip 看清单unzip -p app.jar META-INF/MANIFEST.MF把 Main-Class 和 Class-Path 两行读出来人工核对全限定类名和包名是否一致。再去确认类文件在不在 Jar 里jar tf app.jar | grep MainApp最后在目标机器上执行 java -version对比编译工程的 JDK 版本。注意目标机器上 java 环境变量配置是否正确也会影响结果命令能执行不代表用的是你预期的那份 JDK用 which java 看一下实际解析路径。5.2 运行中抛 ClassNotFoundException第三方依赖根本没进去现象程序启动几秒后才报 java.lang.ClassNotFoundException: org.apache.commons.io.IOUtils而不是启动瞬间报错。原因要么导出时进错了入口选了普通 JAR file 而不是 Runnable JAR file要么依赖没有出现在工程的 Build Path 里要么方案 C 导出后 lib 目录漏带。解决打开导出对话框确认自己进的是 Runnable JAR file在 Project Explorer 的 Referenced Libraries 里确认依赖在不在如果是方案 C检查 Jar 同级目录下有没有生成 lib/lib/ 里有没有对应 Jar。这个坑的特点是报错越晚越隐蔽说明类是被反射或者延迟加载的排查时不要只盯着启动日志把完整堆栈打出来看第一个找不到的类名对应哪个依赖。5.3 解压合并后同名类覆盖行为变得不可预测现象导出时选了解压合并程序运行不报错但功能表现怪异比如日期格式化结果不对、JSON 序列化字段缺失。这种问题最折磨人因为没有异常信息。原因两个第三方 Jar 里有同名类Eclipse 按 Build Path 顺序覆盖后处理的覆盖先处理的。运行时会加载到哪个版本取决于导出时的处理顺序和你代码编译时看到的那个类不一定一致。解决把两个冲突的 Jar 找出来用反编译 jar 的工具比如 JD-GUI 打开逐个确认里面是否存在相同的全限定类名。然后回到方案 C保留 Jar 原样或者在 Build Path 的 Order and Export 标签页手动调整顺序把你想优先加载的版本排在前面。如果调整顺序还不够把冲突的依赖从 Build Path 移除改成手动 Add External JARs 的方式彻底绕开 Eclipse 的合并逻辑。5.4 中文路径、空格目录和系统编码引发的导出失败现象Eclipse 装在 D:\开发工具\eclipse工程在 D:\项目\report-gen导出向导能走完但生成的 Jar 在别的机器上运行报找不到主类且报错信息里的类名中文显示成乱码。原因清单文件里的类名和 Class-Path 路径经过平台默认编码写入。如果工程编译编码是 GBK而目标机器的区域设置不同JVM 解析清单文件时就会失败。这里的编码问题只影响字符串内容的写入方式和 class 文件本身无关所以在本机能跑、换机器就挂。解决导出前在工程属性里确认编码为 UTF-8路径是 Project - Properties - Resource - Text file encoding。同时确认 pom.xml 或 build 脚本里没有硬编码 -Dfile.encodingGBK 之类的参数。如果工程路径实在绕不开中文把工程整体复制到 C:\work 这样纯英文目录再导出一次对比结果。这个坑不是玄学是清单文件写入编码和文件系统编码双重作用。5.5 Maven 工程导出时依赖不完整provided 作用域与本地缓存现象pom.xml 里明明有依赖Eclipse 导出后 Jar 里没有对应类运行时报找不到 Spring 相关类或者 Eclipse 菜单导出时报 An internal error occurred during: Updating Maven project。原因Eclipse 的 Runnable JAR file 导出依赖的是 Maven 的 classpath而不是 pom 的原始内容。pom 里声明为 provided 或 test 作用域的依赖不会进 Jar本地仓库版本和 pom 不一致时Eclipse 使用的 classpath 是过期的另外 workspace 缓存损坏也会导致 Update Maven project 报内部错误。解决在 Eclipse 里右键工程 - Maven - Update Project勾选 Force Update of Snapshots/Releases 强制刷新。然后再看 Maven Dependencies 节点里实际列出的 Jar 和 pom 是否一致。provided 依赖由运行环境提供比如 servlet-api 在 Web 容器里有本来就不该打进去。遇到 Updating Maven project 内部错误多半是本地 .m2 里对应依赖的元数据损坏删掉该目录重新依赖下载就行不建议直接删 .metadata那样会把整个工作区的设置都清掉代价太大。6. 验证可执行 Jar 的三个命令和后续维护建议导出不是终点验证才是。我对每一个导出的 Jar 都会跑三条命令顺序固定缺一不可。第一条是体检确认 Jar 内部结构符合预期jar tf report-tool.jar | head -50看输出列表里有没有 META-INF/MANIFEST.MF、有没有你自己的主类 class 文件、依赖是否按预期出现。如果选了方案 C这一步还会看到 lib/ 目录整洁地区分第三方内容。jar tf 的输出就是 Jar 内部路径和 ZIP 的条目列表一致用 unzip -l 看效果一样。第二条是核对清单文件unzip -p report-tool.jar META-INF/MANIFEST.MF输出后人工核对 Main-Class 和 Class-Path 两行。我在避坑章节的排查顺序里先用了它验证时也一样。很多人跳过这一步直接跑结果了半天依赖问题最后发现是 Main-Class 行尾缺了一个换行符。第三条才是真正的运行验证java -jar report-tool.jar --help或者带业务参数跑一次冒烟测试。注意这里的当前工作目录是敲命令时所在的目录不是 Jar 文件所在目录。程序里如果用了相对路径读配置文件就会在这里踩坑建议在代码里用 ReportGenerator.class.getProtectionDomain().getCodeSource().getLocation() 取 Jar 实际位置再拼接相对路径这比依赖用户从哪个目录启动更可靠。验证通过后维护层面的习惯我建议尽早培养只要这个工程还会迭代就把打包从 Eclipse 手动导出迁到 Maven 的 maven-assembly-plugin 或 shade 插件。原因不是 Eclipse 做不了而是手工导出的配置不可追溯代码仓库里无法复现一次导出的全过程。我自己就有过血泪经验一个工具用 Eclipse 导出交付三个月后再改原来的 Launch configuration 已经被删了重新配出来的 Jar 行为竟然不一样。用插件定义打包等于把过程固化进仓库才是长久的后悔药。最后一个习惯是跨环境验证。如果工具要跑在 ARM 架构的机器上或者目标机器只有 JRE 没有 JDK提前在对应环境跑一遍 java -version 和 java -jar。类文件版本错误通常只在目标环境暴露本地永远复现不了。检验过程保持这套顺序Eclipse 导出可执行 Jar 就不再是黑匣子了。希望这些经验帮到你少踩一次是一次。本文还有配套的精品资源点击获取
返回列表