spring boot jar 启动报错 Zip64 archives are not supported

spring boot jar 启动报错 Zip64 archives are not supported

  • 原因、解决方案
  • 问题
    • 为什么 spring boot 不支持 zip64
    • zip、zip64 功能上的区别
    • zip 的文件格式
    • spring-boot-loader 是如何判断是否是 zip64 的?
  • 参考

spring boot 版本是 2.1.8.RELEASE,引入以下 phoenix 依赖之后启动报错。

<dependency>
    <groupId>org.apache.phoenix</groupId>
    <artifactId>phoenix-client-hbase-2.4</artifactId>
    <version>5.1.3</version>
</dependency>

错误日志:

PS D:\project\java\zip64\target> java -jar .\zip64-0.0.1-SNAPSHOT.jar
Exception in thread "main" java.lang.IllegalStateException: Failed to get nested archive for entry BOOT-INF/lib/phoenix-client-hbase-2.4-5.1.3.jar
        at org.springframework.boot.loader.archive.JarFileArchive.getNestedArchive(JarFileArchive.java:108)
        at org.springframework.boot.loader.archive.JarFileArchive.getNestedArchives(JarFileArchive.java:87)
        at org.springframework.boot.loader.ExecutableArchiveLauncher.getClassPathArchives(ExecutableArchiveLauncher.java:69)
        at org.springframework.boot.loader.Launcher.launch(Launcher.java:50)
        at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:52)
Caused by: java.io.IOException: Unable to open nested jar file 'BOOT-INF/lib/phoenix-client-hbase-2.4-5.1.3.jar'
        at org.springframework.boot.loader.jar.JarFile.getNestedJarFile(JarFile.java:258)
        at org.springframework.boot.loader.jar.JarFile.getNestedJarFile(JarFile.java:244)
        at org.springframework.boot.loader.archive.JarFileArchive.getNestedArchive(JarFileArchive.java:104)
        ... 4 more
Caused by: java.lang.IllegalStateException: Zip64 archives are not supported
        at org.springframework.boot.loader.jar.CentralDirectoryEndRecord.getNumberOfRecords(CentralDirectoryEndRecord.java:121)
        at org.springframework.boot.loader.jar.JarFileEntries.visitStart(JarFileEntries.java:117)
        at org.springframework.boot.loader.jar.CentralDirectoryParser.visitStart(CentralDirectoryParser.java:85)
        at org.springframework.boot.loader.jar.CentralDirectoryParser.parse(CentralDirectoryParser.java:56)
        at org.springframework.boot.loader.jar.JarFile.<init>(JarFile.java:125)
        at org.springframework.boot.loader.jar.JarFile.<init>(JarFile.java:112)
        at org.springframework.boot.loader.jar.JarFile.createJarFileFromFileEntry(JarFile.java:289)
        at org.springframework.boot.loader.jar.JarFile.createJarFileFromEntry(JarFile.java:266)
        at org.springframework.boot.loader.jar.JarFile.getNestedJarFile(JarFile.java:255)

原因、解决方案

Google 很快就找到了原因,stackoverflow 上有类似的问题 java - Add more than 65535 entries jar in Spring boot - Stack Overflow。

第一个回答给出了原因:spring boot 不支持一个 jar 文件中多于 65534(这里应该写错了,应该是 65535) 个文件,并附上了抛异常的代码。

第二个回答是 spring boot 的 issues,有兴趣的可以自己看一下 Support zip64 format executable archives · Issue #2895 · spring-projects/spring-boot (github.com)

第三个回答给出了解决办法:升级到 2.2.x,也给出了支持 zip64 的提交记录 Support zip64 jars by cvienot · Pull Request #16091 · spring-projects/spring-boot (github.com)。我升级成 2.2.0.RELEASE 确实解决了问题。

image-20240625151243205

问题

回答一中的代码来自 spring-boot-loader 子项目中的 org.springframework.boot.loader.jar.CentralDirectoryEndRecord#getNumberOfRecords 方法,依赖如下:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-loader</artifactId>
    <version>2.1.8.RELEASE</version>
</dependency>

为什么 spring boot 不支持 zip64

从 ZIP (file format) - Wikipedia 中可以看出 zip、zip64 在文件的格式上是不同的。猜测应该是开发者没想到 jar 包里的文件个数或 jar 包的大小会超过 65535,所以没有实现 zip64 相关的。从以下提交中能看出一二。直到最后的两个提交才有人实现了 zip64 的相关代码。

image-20240625173000853

zip、zip64 功能上的区别

zip64 格式是标准 zip 格式的扩展,实际上消除了 zip 存档中文件大小和数量的限制。

每种格式允许的最大值总结如下:

Standard FormatZip64 Format
Number of Files Inside an Archive65,5352^64 - 1
Size of a File Inside an Archive [bytes]4,294,967,2952^64 - 1
Size of an Archive [bytes]4,294,967,2952^64 - 1
Number of Segments in a Segmented Archive999 (spanning) 65,535 (splitting)4,294,967,295 - 1
Central Directory Size [bytes]4,294,967,2952^64 - 1

zip 的文件格式

zip格式压缩包主要由三大部分组成:数据区中央目录记录区(也有叫核心目录记录)中央目录记录尾部区

数据区是由一系列本地文件记录组成,本地文件记录主要是记录了压缩前后文件的元数据以及存放压缩后的文件

中央目录记录区是有一系列中央目录记录所组成,一条中央目录记录对应数据区中的一个压缩文件记录

中央目录记录尾部(End of central directory record)主要作用是用来定位中央目录记录区的开始位置,同时记录压缩包的注释内容

End of central directory record (EOCD)

OffsetBytesDescription[33]中文
04End of central directory signature = 0x06054b50签名
42Number of this disk (or 0xffff for ZIP64)
62Disk where central directory starts (or 0xffff for ZIP64)
82Number of central directory records on this disk (or 0xffff for ZIP64)
102Total number of central directory records (or 0xffff for ZIP64)文件数量(ZIP64 为 0xffff )
124Size of central directory (bytes) (or 0xffffffff for ZIP64)
164Offset of start of central directory, relative to start of archive (or 0xffffffff for ZIP64)
202Comment length (n)注释长度
22nComment

spring-boot-loader 是如何判断是否是 zip64 的?

// 从 bytes 的 offset 偏移量开始,以小端模式读取 length 个字节
public static long littleEndianValue(byte[] bytes, int offset, int length) {
    long value = 0;
    for (int i = length - 1; i >= 0; i--) {
       value = ((value << 8) | (bytes[offset + i] & 0xFF));
    }
    return value;
}
/**
 * A ZIP File "End of central directory record" (EOCD).
 *
 * @author Phillip Webb
 * @author Andy Wilkinson
 * @see <a href="https://en.wikipedia.org/wiki/Zip_%28file_format%29">Zip File Format</a>
 */
class CentralDirectoryEndRecord {

	// EOCD 最小长度,从表中可以看出在没有注释的情况下是 22
	private static final int MINIMUM_SIZE = 22;

	// 从表中可以看出注释长度为 2 字节,所有最大值是 65535
	private static final int MAXIMUM_COMMENT_LENGTH = 0xFFFF;

	private static final int MAXIMUM_SIZE = MINIMUM_SIZE + MAXIMUM_COMMENT_LENGTH;

	// EOCD 开始的标记
	private static final int SIGNATURE = 0x06054b50;

	// EOCD 中“注释长度”字段的偏移量,从表中可以看出是 20
	private static final int COMMENT_LENGTH_OFFSET = 20;

	// 每次从文件尾部读取 256 字节
	private static final int READ_BLOCK_SIZE = 256;

	// 最终是 EOCD 的字节数组
	private byte[] block;

	// EOCD 在 block 中的偏移量
	private int offset;

	// EOCD 的字节数
	private int size;

	/**
	 * Create a new {@link CentralDirectoryEndRecord} instance from the specified
	 * {@link RandomAccessData}, searching backwards from the end until a valid block is
	 * located.
	 * @param data the source data
	 * @throws IOException in case of I/O errors
	 */
	CentralDirectoryEndRecord(RandomAccessData data) throws IOException {
		// 从文件尾部读取 256 字节
		this.block = createBlockFromEndOfData(data, READ_BLOCK_SIZE);
		this.size = MINIMUM_SIZE;
		this.offset = this.block.length - this.size;
		// 尝试找到 EOCD 的开头
		while (!isValid()) {
			this.size++;
			if (this.size > this.block.length) {
				if (this.size >= MAXIMUM_SIZE || this.size > data.getSize()) {
					throw new IOException(
							"Unable to find ZIP central directory " + "records after reading " + this.size + " bytes");
				}
				// 每次多读 1 字节
				this.block = createBlockFromEndOfData(data, this.size + READ_BLOCK_SIZE);
			}
			// offset 每次向前移动 1 字节
			this.offset = this.block.length - this.size;
		}
	}

	private byte[] createBlockFromEndOfData(RandomAccessData data, int size) throws IOException {
		int length = (int) Math.min(data.getSize(), size);
		return data.read(data.getSize() - length, length);
	}

	// 尝试找到 EOCD 的开头
	private boolean isValid() {
		// 长度小于 EOCD 的最小长度,肯定不符合
		if (this.block.length < MINIMUM_SIZE
				// 读取 block 最开始的 4 个字节,与 EOCD 的标记进行比较,不符合则返回 false
				// 如果相等则找到了 EOCD 的开头
				|| Bytes.littleEndianValue(this.block, this.offset + 0, 4) != SIGNATURE) {
			return false;
		}
		// 读取注释长度 2 字节
		// Total size must be the structure size + comment
		long commentLength = Bytes.littleEndianValue(this.block, this.offset + COMMENT_LENGTH_OFFSET, 2);
		// EOCD 的字节数肯定等于 EOCD 的最小长度 + 注释内容的长度
		return this.size == MINIMUM_SIZE + commentLength;
	}

	/**
	 * Return the number of ZIP entries in the file.
	 * @return the number of records in the zip
	 */
	public int getNumberOfRecords() {
		// 读取 block 偏移量文 10 的 2 个字节,即文件数量
		long numberOfRecords = Bytes.littleEndianValue(this.block, this.offset + 10, 2);
		// 如果文件数量为 65535 则为 Zip64
		if (numberOfRecords == 0xFFFF) {
			throw new IllegalStateException("Zip64 archives are not supported");
		}
		return (int) numberOfRecords;
	}

}

参考

  • java - Add more than 65535 entries jar in Spring boot - Stack Overflow
  • 压缩包Zip格式详析(全网最详细)_zip格式详解-CSDN博客
  • ZIP文件格式分析 | Sp4n9x’s Blog
  • ZIP (file format) - Wikipedia

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/743211.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

HBDNY-40/1端子排电压继电器 DC110V 导轨安装 约瑟JOSEF

HBDNY系列端子排型电压电流继电器 系列型号&#xff1a;&#xff08;3、4过/低电压型&#xff0c;5、6过/低电流型&#xff09; HBDNY-30/1端子排型电压继电器&#xff1b;HBDNY-30/2端子排型电压继电器&#xff1b; HBDNY-30/3端子排型电压继电器&#xff1b;HBDNY-30/4端子…

摩柏BI,轻松实现word报告自动更新

|| 导语 告别手工&#xff01;在数字化办公的今天&#xff0c;高效、准确的数据处理能力已经成为职场人士必备的技能之一。尤其是对于财务分析师、市场研究员和管理人员&#xff0c;他们需要处理的报告不仅数量庞大&#xff0c;而且对数据的准确性和实时性要求极高。传统WORD报…

python爬虫需要什么HTTP代理?

用来爬虫的话&#xff0c;还是建议用高匿名代理&#xff0c;但显然题主用了高匿名代理还是出现了一部分问题&#xff0c;我们可以先找到问题关键再解决它&#xff0c;一般爬虫用了高匿名代理出现被封会有以下几种原因&#xff1a; 1.代理IP的质量不过关 一般来说每个网站都有…

解锁小红书新玩法:中小企业出海营销的集成策略

随着全球数字化浪潮的推进&#xff0c;小红书作为生活方式分享平台的崛起&#xff0c;为中小企业提供了一个全新的营销舞台。NetFarmer&#xff0c;作为专注于企业数字化出海的服务商&#xff0c;深谙小红书的营销策略&#xff0c;并致力于通过HubSpot产品销售与实施&#xff0…

HarmonyOS应用开发学习经验

一、HarmonyOS学习官网 开发者能力认证 HarmonyOS应用开发者基础认证6月之前的学习资源官网已经关闭过期&#xff0c;大家不要慌&#xff0c;官方更新了最新资源&#xff0c;但是&#xff0c;对于之前没有学习完的学员不友好&#xff0c;存在知识断片的现象&#xff0c;建议官…

微型导轨:手术机器人的高精度“骨骼”

微型导轨精度高&#xff0c;摩擦系数小&#xff0c;自重轻&#xff0c;结构紧凑&#xff0c;被广泛应用在医疗器械中&#xff0c;尤其是在手术机器人中的应用&#xff0c;通过手术机器人&#xff0c;外科医生可以远离手术台操纵机器人进行手术。可以说&#xff0c;是当之无愧的…

C语言实现:贪心算法

算法基础原理 贪心算法是一种在求解问题时&#xff0c;总是做出在当前看来是最好的选择的算法。它不从整体最优上进行考虑&#xff0c;而是通过每一步的局部最优选择&#xff0c;希望达到全局的最优解. 贪心算法的特点:贪心算法在每一步都选择当前状态下的最优解&#xff0c;即…

SSH的基本使用

文章目录 1. SSH使用介绍2. 如何配置OpenSSH Client和OpenSSH Server2.1 Windows系统配置2.2 Linux系统配置2.2.1. 安装OpenSSH服务2.2.2. 启动和检查SSH服务 3. SSH具体使用方式4. vscode中使用ssh远程连接 1. SSH使用介绍 SSH 最常见的用途是通过加密连接在不安全的网络中进…

fiddler抓https包

1&#xff0c;安装fiddler省略 2&#xff0c;下载证书步骤&#xff1a;tools-options-https 点击确认&#xff0c;点击OK&#xff0c;点击是 把证书安装到谷歌浏览器上步骤&#xff1a;点击谷歌浏览器右上角的设置&#xff0c;在搜索框中搜索证书&#xff0c;点击“证书管理”…

常见的排序算法【总结】

目录 排序的基本概念与分类排序的稳定性内排序与外排序简单排序冒泡排序时间复杂度&#xff1a; O ( n 2 ) O(n^2) O(n2) 简单选择排序排序原理&#xff1a;时间复杂度&#xff1a; O ( n 2 ) O(n^2) O(n2) 插入排序排序原理&#xff1a;时间复杂度&#xff1a; O ( n 2 ) O(n^…

MFC GDI绘制卡通人物

文章目录 主要代码完整visual studio工程下载 主要代码 // DrawFrogView.cpp : implementation of the CDrawFrogView class //#include "stdafx.h" #include "DrawFrog.h"#include "DrawFrogDoc.h" #include "DrawFrogView.h"#inclu…

让TSN DDS运转起来——面向智能汽车的以太网测试解决方案

概述 作为OPEN联盟和AUTOSAR联盟的核心成员&#xff0c;经纬恒润多年来持续为国内外各大OEM和供应商提供车载以太网相关的咨询服务&#xff0c;涵盖TCP/IP、SOME/IP、DDS、诊断、TSN等前沿技术领域的设计和测试。同时&#xff0c;经纬恒润与行业内的合作伙伴紧密合作&#xff0…

Vulnhub靶场DC-4练习

目录 0x00 准备0x01 主机信息收集0x02 站点信息收集0x03 漏洞查找与利用1. 爆破登录2. 命令执行3. 反弹shell4. hydra爆破ssh5. 提权 0x04 总结 0x00 准备 下载链接&#xff1a;https://download.vulnhub.com/dc/DC-4.zip 介绍&#xff1a; DC-4 is another purposely built …

【精品案例】数字孪生技术与数字工厂案例(59页PPT)

引言&#xff1a;随着工业4.0和智能制造的快速发展&#xff0c;数字孪生技术和数字工厂已成为制造业转型升级的重要趋势。数字孪生技术通过构建虚拟的数字模型&#xff0c;实现对物理实体全生命周期的映射与仿真&#xff0c;为企业的产品研发、设计、制造等提供有力支持。而数字…

如何评估SD-WAN专线带宽、确保网络性能

网络带宽的充足与否直接关系到业务的正常运作和用户的使用体验。为了确保最佳效果&#xff0c;SD-WAN专线的带宽需要根据企业的规模和具体网络需求进行详细评估。评估过程中需充分考虑实时应用、用户数量、分支机构间的连接以及业务特点。本文将探讨以下问题&#xff1a;SD-WAN…

基于Java的4S店车辆管理系统

你好&#xff0c;我是计算机专业的毕业生&#xff0c;很高兴与您分享我的毕业设计。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;Java、SpringBoot、B/S模式 工具&#xff1a;MyEclipse、MySQL 系统展示 首页 个人中心 销售员管理界面 车辆维修管…

Ubuntu安装NVIDIA驱动

目录 安装gcc 安装NVIDIA驱动 检查nvidia显卡型号 根据显卡型号下载对应的驱动 安装命令 如何卸载 安装gcc 安装显卡驱动需要使用gcc&#xff0c;输入命令检查是否有gcc gcc --version 如果有版本号弹出&#xff0c;说明已经有gcc环境了&#xff0c;没有的则运行以下…

【Docker】存储数据卷

目录 1、挂载数据卷到容器里 2、查询挂载文件 3、容器与主机之间映射共享卷 4、三个容器之间使用共享卷 5、卷数据的备份与恢复 5.1 备份 5.2 恢复 1、挂载数据卷到容器里 docker run -itd --name test02 -v /data nginx docker exec -it test02 bashls / docker inspe…

1.8 HTTP协议结构

我们来看一下HTTP协议到底由哪些部分组成&#xff0c;也就是HTTP协议的结构。知道了这些知识才能在接口测试中游刃有余。 我们看上图&#xff0c;HTTP协议由四部分组成 起始行 描述请求和响应的基本信息。 当是请求时&#xff1a;请求方法是GET&#xff0c;调用的地址&#…

python基础篇(5):None类型

1 None类型 Python中有一个特殊的字面量&#xff1a;None&#xff0c;其类型是&#xff1a;<class NoneType> 无返回值的函数&#xff0c;实际上就是返回了&#xff1a;None这个字面量 None表示&#xff1a;空的、无实际意义的意思 函数返回的None&#xff0c;就表示…