spring boot jar 启动报错 Zip64 archives are not supported
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)... 4more
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 确实解决了问题。
问题
回答一中的代码来自
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 的相关代码。
zip、zip64 功能上的区别
zip64 格式是标准 zip 格式的扩展,实际上消除了 zip 存档中文件大小和数量的限制。
每种格式允许的最大值总结如下:
Standard FormatZip64 FormatNumber of Files Inside an Archive65,5352^64 - 1Size of a File Inside an Archive [bytes]4,294,967,2952^64 - 1Size of an Archive [bytes]4,294,967,2952^64 - 1Number of Segments in a Segmented Archive999 (spanning) 65,535 (splitting)4,294,967,295 - 1Central 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 个字节publicstaticlonglittleEndianValue(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>
*/classCentralDirectoryEndRecord{// EOCD 最小长度,从表中可以看出在没有注释的情况下是 22privatestaticfinalintMINIMUM_SIZE=22;// 从表中可以看出注释长度为 2 字节,所有最大值是 65535privatestaticfinalintMAXIMUM_COMMENT_LENGTH=0xFFFF;privatestaticfinalintMAXIMUM_SIZE=MINIMUM_SIZE+MAXIMUM_COMMENT_LENGTH;// EOCD 开始的标记privatestaticfinalintSIGNATURE=0x06054b50;// EOCD 中“注释长度”字段的偏移量,从表中可以看出是 20privatestaticfinalintCOMMENT_LENGTH_OFFSET=20;// 每次从文件尾部读取 256 字节privatestaticfinalintREAD_BLOCK_SIZE=256;// 最终是 EOCD 的字节数组privatebyte[] block;// EOCD 在 block 中的偏移量privateint offset;// EOCD 的字节数privateint 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)throwsIOException{// 从文件尾部读取 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()){thrownewIOException("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;}}privatebyte[]createBlockFromEndOfData(RandomAccessData data,int size)throwsIOException{int length =(int)Math.min(data.getSize(), size);return data.read(data.getSize()- length, length);}// 尝试找到 EOCD 的开头privatebooleanisValid(){// 长度小于 EOCD 的最小长度,肯定不符合if(this.block.length <MINIMUM_SIZE// 读取 block 最开始的 4 个字节,与 EOCD 的标记进行比较,不符合则返回 false// 如果相等则找到了 EOCD 的开头||Bytes.littleEndianValue(this.block,this.offset +0,4)!=SIGNATURE){returnfalse;}// 读取注释长度 2 字节// Total size must be the structure size + commentlong commentLength =Bytes.littleEndianValue(this.block,this.offset +COMMENT_LENGTH_OFFSET,2);// EOCD 的字节数肯定等于 EOCD 的最小长度 + 注释内容的长度returnthis.size ==MINIMUM_SIZE+ commentLength;}/**
* Return the number of ZIP entries in the file.
* @return the number of records in the zip
*/publicintgetNumberOfRecords(){// 读取 block 偏移量文 10 的 2 个字节,即文件数量long numberOfRecords =Bytes.littleEndianValue(this.block,this.offset +10,2);// 如果文件数量为 65535 则为 Zip64if(numberOfRecords ==0xFFFF){thrownewIllegalStateException("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
版权归原作者 赵丙双 所有, 如有侵权,请联系我们删除。