您的位置:首页 > 科技 > IT业 > SpringBoot: 可执行jar的特殊逻辑

SpringBoot: 可执行jar的特殊逻辑

2024/11/15 16:18:57 来源:https://blog.csdn.net/randavy/article/details/139547654  浏览:    关键词:SpringBoot: 可执行jar的特殊逻辑

这一篇我们来看看Java代码怎么操作zip文件(jar文件),然后SpringBoot的特殊处理,文章分为2部分

  1. Zip API解释,看看我们工具箱里有哪些工具能用
  2. SpringBoot的特殊处理,看看SpringBoot Jar和普通Jar的不同

1. Zip API解释

1. ZipFile

我们先通过ZipFile来读取jar文件,通过ZipFile#entries()方法返回Zip内的每一个元素,每个元素可能是目录或文件,如果是目录则在目标文件夹下创建对应目录,否则拷贝文件到目标位置

private static void unzipByZipFile(String org, String dest) throws IOException {clean(dest);ZipFile zip = new ZipFile(org);Enumeration<? extends ZipEntry> ez = zip.entries();while (ez.hasMoreElements()) {ZipEntry ze = ez.nextElement();if (ze.isDirectory()) {Files.createDirectories(Path.of(dest, ze.getName()));} else {Path target = Path.of(dest, ze.getName());try (InputStream is = zip.getInputStream(ze)) {Files.copy(is, target);}}}
}

接下来在main方法内调用unzipByZipFile来查看测试效果,并查看输出的目录

public static void main(String[] args) throws IOException {unzipByZipFile("D:\\Workspace\\yangsi\\target\\yangsi-0.0.1-SNAPSHOT.jar", "d:/temp");
}
2. ZipInputStream

使用ZipInputStream读取和ZipFile读取基本类似,通过getNextEntry先获取一个ZipEntry,读取完毕后用closeEntry编译当前ZipEntry。

private static void unzipByZipInputStream(String org, String dest) throws IOException {clean(dest);try (ZipInputStream zis = new ZipInputStream(new FileInputStream(org))) {ZipEntry ze = null;while ((ze = zis.getNextEntry()) != null) {if (ze.isDirectory()) {Files.createDirectories(Path.of(dest, ze.getName()));} else {Files.copy(zis, Path.of(dest, ze.getName()));}zis.closeEntry();}}
}
3. ZipOuputStream

现在我们使用ZipOutputStream将之前解压出来的文件重新打包成jar,代码如下

private static void zipByZipOutputStream(String dir, String dst) throws IOException {Files.deleteIfExists(Path.of(dst));try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(dst))) {Path root = Path.of(dir);for (Path x : Files.list(root).toList()) {addToZip(root, x, zos);}}
}private static void addToZip(Path root, Path file, ZipOutputStream zos) throws IOException {if (Files.isDirectory(file)) {for (Path x : Files.list(file).toList()) {addToZip(root, x, zos);}} else {ZipEntry e = new ZipEntry(root.relativize(file).toString());zos.putNextEntry(e);Files.copy(file, zos);zos.closeEntry();}
}

2. SpringBoot的特殊处理

1. 对比文件

到现在为止,一切都看起来很没好,我们通过ZipInputStream解压了jar包,然后又通过ZipOutputStream重新打成可执行jar。 直到我们尝试执行这个通过ZipOutputStream打包的jar,才发现了问题。

~$ java -jar temp.jar
Error: Invalid or corrupt jarfile temp.jar

问题发生在哪呢?处在ZipOutputStream的压缩级别上,SpringBoot的jar对文件压缩做了特殊处理。如果我们有3个压缩文件,分别标号为1、2、3

  1. 文件1,是正常SpringBoot项目通过Maven打包后的结果
  2. 文件2,是将文件1中的jar解压后,通过ZipOutputStream采用0压缩级别(不压缩)打包的文件
  3. 文件3,是将文件1中的jar解压后,采用默认压缩级别打包的文件

可以看到org、META-INF在文件1、文件3中的文件大小是完全一致的,所以这部分文件在SpringBoot JAR也是被压缩的。

而BOOT-INF却3中方式都不同,我们进入BOOT-INF看看,文件1、文件3中的普通文件(classes、idx)文件是一样的,也就是普通文件不做压缩。而文件1、文件2的lib文件夹是一样的。

所以总结下来,Spring Boot Maven Plugin打成的可执行jar,对普通文件采用了压缩,而jar文件仅仅打包而不压缩。这也是为什么我们执行java -jar temp.jar时报错的原因。

2. 设置jar不压缩

现在我们要修改ZipOutputStream的输出,jar文件仅存储不压缩,需要在代码中设置jar的ZipEntry.setMethod(ZipEntry.STORED),同时要自己计算crc和文件大小。

private static void zipByZipOutputStream(String dir, String dst) throws IOException {Files.deleteIfExists(Path.of(dst));try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(dst))) {Path root = Path.of(dir);for (Path x : Files.list(root).toList()) {addToZip(root, x, zos);}}
}private static void addToZip(Path root, Path file, ZipOutputStream zos) throws IOException {if (Files.isDirectory(file)) {for (Path x : Files.list(file).toList()) {addToZip(root, x, zos);}} else if (isJar(file)) {ZipEntry e = new ZipEntry(root.relativize(file).toString());long size = Files.size(file);e.setSize(size);e.setCompressedSize(size);e.setMethod(ZipEntry.STORED);try (InputStream fis = Files.newInputStream(file, StandardOpenOption.READ); CheckedInputStream cis = new CheckedInputStream(fis, new CRC32()); ByteArrayOutputStream bos = new ByteArrayOutputStream();) {cis.transferTo(bos);long crc = cis.getChecksum().getValue();e.setCrc(crc & 0xFFFFFFFF);}zos.putNextEntry(e);Files.copy(file, zos);zos.closeEntry();} else {ZipEntry e = new ZipEntry(root.relativize(file).toString());zos.putNextEntry(e);Files.copy(file, zos);zos.closeEntry();}
}private static boolean isJar(Path file) {return file.getFileName().toString().toLowerCase().endsWith(".jar");
}

再次打包后可以看到(文件4),我们打包的文件大小和原始文件是一摸一样的了。

应该说Spring Boot的这种特殊处理是合理且必要的,jar文件本身已经做过压缩,再次压缩意义不大。

现在我们有足够的背景知识了,下一篇我们来看看SpringBoot可执行Jar是怎么引导并启动我们的应用的。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com