应使用 try (JarFile jar = new JarFile(path, true)) 安全打开JAR,要求路径为本地绝对路径或file:// URI,启用验证;文件被占用时改用ZipInputStream;注意JarEntry.getName()无前导/、需手动规范化路径防遍历攻击、编码可能乱码、entries()/stream()无序、getTime()/getSize()常返回-1。

怎么用 JarFile 安全打开一个 JAR 文件
直接 new JarFile("xxx.jar") 很容易在文件不存在、损坏或被其他进程占用时抛出 IOException,甚至触发 SecurityException(比如从网络 URL 加载且未签名)。必须包裹 try-with-resources,否则句柄泄漏会导致后续无法删除或重写该 JAR。
- 永远用
try (JarFile jar = new JarFile(path, true))—— 第二个参数true表示启用验证,能提前发现 MANIFEST.MF 校验失败 - 路径必须是本地绝对路径或
file://开头的 URI;不支持http://或类路径资源(如getClass().getResource()返回的jar:file://URL 需先提取文件路径) - 若 JAR 被 IDE 或构建工具锁住(常见于正在运行的 Spring Boot 应用),
JarFile会直接失败,此时应改用ZipInputStream流式读取(牺牲随机访问能力)
JarEntry 的 getName() 返回路径格式和陷阱
JarEntry.getName() 返回的是以正斜杠 / 分隔的内部路径,**不带前导 /**,例如 "META-INF/MANIFEST.MF" 或 "com/example/Main.class"。这不是文件系统路径,不能直接传给 new File()。
- 判断是否为目录:检查
entry.getName().endsWith("/"),而不是靠entry.isDirectory()—— 后者在某些 JDK 版本中不可靠,尤其对空目录条目 - 路径中的
..或.不会被自动规范化,恶意构造的 JAR 可能含"../../etc/passwd"这类路径,解压时需手动校验(如Paths.get(entry.getName()).normalize().startsWith("allowed/...")) - 注意编码:JAR 规范允许 UTF-8,但旧工具打包可能用平台默认编码,
JarEntry不做转码,getName()返回字节原样解码结果,中文路径乱码时得用ZipInputStream+ 自定义Charset
遍历所有条目时,entries() 和 stream() 的性能与顺序差异
JarFile.entries() 返回的是 Enumeration<jarentry></jarentry>,而 JarFile.stream()(JDK 8+)返回 Stream<jarentry></jarentry>。两者底层都依赖 ZIP 中央目录,但行为不同。
在调试 Java 后端 API 故障时使用此技能,尤其适用于以下任务:分析日志、跨 Controller、Service 和 Mapper 层追踪请求、定位异常根因、检查 SQL 执行或参数流转,以及生成简洁的诊断结论与修复方案。
-
entries()是懒加载,但不保证顺序;实际顺序取决于 ZIP 中央目录记录顺序,通常和打包时添加顺序一致,但压缩工具可能重排 -
stream()默认也是无序的,若需按路径排序,必须显式.sorted(Comparator.comparing(JarEntry::getName)),否则META-INF/可能排在最后 - 不要在循环中反复调用
getJarEntry("xxx")查找 —— 每次都线性扫描中央目录,O(n);应先用stream().collect(Collectors.toMap(JarEntry::getName, e -> e))建索引
为什么 JarEntry.getTime() 经常返回 -1
ZIP 格式只存储 DOS 时间戳(精度 2 秒,范围 1980–2107),且 JAR 打包工具(如 Maven Shade、Gradle)默认不写入时间戳,导致 JarEntry.getTime() 返回 -1。这不是 bug,是规范允许的“未指定”状态。
立即学习“Java免费学习笔记(深入)”;
- 别拿它做缓存判断或增量构建依据;需要可靠时间信息,应读取
META-INF/MANIFEST.MF里的Created-By或自定义头字段 -
JarEntry.getSize()和getCompressedSize()也可能为 -1(流式压缩或未知大小),此时需用getInputStream()实际读取并计数 - 若必须校验内容一致性,优先用
Manifest中的Digest属性,或对getInputStream()做 SHA-256 计算,而非依赖时间戳
真正麻烦的是那些没有中央目录的“扁平化” JAR(比如某些 ProGuard 输出),JarFile 会直接拒绝打开;这种时候只能退到 ZipInputStream,但得自己处理条目边界和 CRC 校验。

















