SpringBoot: 可执行jar的特殊逻辑

news2024/11/28 20:33:38

这一篇我们来看看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是怎么引导并启动我们的应用的。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

NRF24L01(2.4G)模块的使用——SPI时序(软件)篇

一、SPI的简介&#xff1a; SPI 是英语Serial Peripheral interface的缩写&#xff0c;顾名思义就是串行外围设备接口。是Motorola首先在其MC68HCXX系列处理器上定义的。 SPI&#xff0c;是一种高速的&#xff0c;全双工&#xff0c;同步的通信总线&#xff0c;并且在芯片的管脚…

R语言 | 使用最简单方法添加显著性ggpubr包

本期教程原文&#xff1a;使用最简单方法添加显著性ggsignif包 本期教程 获得本期教程代码和数据&#xff0c;在后台回复关键词&#xff1a;20240605 小杜的生信笔记&#xff0c;自2021年11月开始做的知识分享&#xff0c;主要内容是R语言绘图教程、转录组上游分析、转录组下游…

毫米波SDK使用2

5.5 毫米波SDK-TI组件 毫米波SDK功能分解成组件将在接下来的几小节中解释。有关这些模块的详细文档&#xff0c;请参阅位于mmwave_mcuplus_sdk_<ver>/docs/mmwave_sdk_module_document .html的顶层文档。 5.5.1 演示 5.5.1.1 毫米波演示 这个演示位于mmwave_mcuplus_sd…

批量高效调整图片像素:自定义缩小bmp图片,画质优先,一键实现高效优化

图片已经成为我们生活中不可或缺的一部分。无论是社交媒体分享&#xff0c;还是工作文件传输&#xff0c;图片总是扮演着重要的角色。然而&#xff0c;有时候&#xff0c;我们可能会面临一个问题&#xff1a;图片像素过大&#xff0c;不仅占用过多的存储空间&#xff0c;还可能…

【网络教程】Iptables官方教程-学习笔记7-简单理解IPTABLES规则的作用流程

前面学习了IPTABLES的所有功能介绍后&#xff0c;一个Linux设备里的IPTABLES规则集是如何运行的&#xff0c;这里简单做个介绍。 在Linux设备里输入"iptables -nvl",得到该设备的所有防火墙规则&#xff0c;得到的结果中可以看到这个设备防火墙里所有的链以及链里的…

STM32F103C8移植uCOSIII并以不同周期点亮两个LED灯(HAL库方式)【uCOS】【STM32开发板】【STM32CubeMX】

STM32F103C8移植uC/OSIII并以不同周期点亮两个LED灯&#xff08;HAL库方式&#xff09;【uC/OS】【STM32开发板】【STM32CubeMX】 实验说明 将嵌入式操作系统uC/OSIII移植到STM32F103C8上&#xff0c;构建两个任务&#xff0c;两个任务分别以1s和3s周期对LED进行点亮—熄灭的…

力扣hot100:394. 字符串解码(递归)

LeetCode&#xff1a;394. 字符串解码 本题容易想到用递归处理&#xff0c;在写递归时主要是需要明确自己的递归函数的定义。 不过我们也可以利用括号匹配的方式使用栈进行处理。 1、递归 定义递归函数string GetString(string & s,int & i); 表示处理处理整个numbe…

高中数学:数列-基础概念

一、什么是数列&#xff1f; 一般地&#xff0c;我们把按照确定的顺序排列的一列数称为数列&#xff0c;数列中的每一个数叫做这个数列的项&#xff0c;数列的第一项称为首项。 项数有限个的数列叫做有穷数列&#xff0c;项数无限个的数列叫做无穷数列。 二、一般形式 数列和…

2024高考作文引发的人工智能争议

又是一年高考季&#xff0c;多少学子的修行成果也在这这一刻迎来了终极检验&#xff0c;多少学子的梦也在这一刻拉开了揭晓序幕&#xff0c;多少学习的命运也在这一刻迎来了人生中的第一次转变。每年的高考不仅是学子们的人生大事&#xff0c;也是多少父母的热切期望&#xff0…

Java Web学习笔记25——Vue组件库Element

什么是Element&#xff1f; Element: 是饿了么团队研发的&#xff0c;一套为开发者、设计师和产品经理准备的基于Vue2.0的桌面端组件库。 组件&#xff1a;组成网页的部件&#xff0c;例如&#xff1a;超链接、按钮、图片、表格、表单、分页条等等。 官网&#xff1a;https:…

详解C++中的ANSI、Unicode和UTF8三种字符编码及相互转换

目录 1、概述 2、Visual Studio中的字符编码 3、ANSI窄字节编码 4、Unicode宽字节编码 5、UTF8编码 6、如何使用字符编码 7、三种字符编码之间的相互转换&#xff08;附源码&#xff09; 8、Windows系统对使用ANSI窄字节字符编码的程序的兼容 9、字符编码导致程序启动…

1-8 C语言分支循环语句

C语言的语句分为 5 类 1&#xff1a;表达式语句2&#xff1a;函数调用语句3&#xff1a;控制语句4&#xff1a;复合语句5&#xff1a;空语句 控制语句&#xff1a;用于控制程序的执行流程&#xff0c;以实现程序的各种结构方式&#xff0c;它们由特定的语句定义符组成&#x…

【日记】遇到了一个 “不愿睁眼看世界也没受过社会毒打” 的逆天群友(464 字)

正文 今天坐在柜台玩了一天手机…… 手机都玩没电了快。下午在劝一个群友睁眼看世界&#xff0c;实在劝不动。他真的太逆天了&#xff0c;我不清楚这么高学历的人&#xff0c;怎么能说出这么天真的话。逆天又离谱。 晚上的时间几乎全在做家务。平时晚上都是跳舞来着&#xff0c…

云原生架构案例分析_1.某旅行公司云原生改造

随着云计算的普及与云原生的广泛应用&#xff0c;越来越多的从业者、决策者清晰地认识到“云原生化将成为企业技术创新的关键要素&#xff0c;也是完成企业数字化转型的最短路径”。因此&#xff0c;具有前瞻思维的互联网企业从应用诞生之初就扎根于云端&#xff0c;谨慎稳重的…

git推送代码到github拒绝推送的解决方案

这里描述一下本地推送的场景&#xff0c;首先我在码云上建立了一个前端项目&#xff0c;进行了自己的个性化开发&#xff0c;后期在github上创建了一个一样的项目仓库存放代码。使用webstorm进行代码开发。在下面这个位置可以选择推送的代码位置。 选择推送github仓库之后&…

图文详解Windows系统下搭建mysql开发环境——mysql Community 8 和 navicat Premium 17 的安装和使用

在正式开始学习使用MySQL之前&#xff0c;我们有必要先搭建一个良好的开发环境&#xff0c;让我们的学习和工作效率事半功倍。 本文涉及到的软件百度云盘&#xff1a;链接&#xff1a;https://pan.baidu.com/s/1jj_YajEv8adeEjMrXLhOTQ?pwd1023 提取码&#xff1a;1023 目录 …

React的表单学习

react的表单的双向绑定 // userState实现计数实例 import {useState} from react// 1.声明一个react的状态 -useState// 2.核心绑定流程//1.通过value属性绑定react状态//2.绑定onChange事件&#xff0c;通过事件参数e拿到输入框最新的值&#xff0c;反向修改到react状态 func…

Linux--标准IO库

一、标准IO简介 所谓标准 I/O 库则是标准 C 库中用于文件 I/O 操作&#xff08;譬如读文件、写文件等&#xff09;相关的一系列库函数的集合&#xff0c;通常标准 I/O 库函数相关的函数定义都在头文件 <stdio.h> 中&#xff0c;所以我们需要在程序源码中包含 <s…

[office] excel工作表数据分级显示 #其他#笔记

excel工作表数据分级显示 如下图1所示的工作表数据&#xff0c;我们按东区、西区、南区、北区来建立分级显示。 图1 这里先利用“创建组”命令建立分级显示。选取单元格区域A3:E5&#xff0c;单击功能区“数据”选项卡“分级显示”组中的“创建组——创建组…”命令&#xff…

使用Cython编译Python源码加密加速,有这一篇就够了!

0 前言 python是一门脚本语言&#xff0c;运行时由python虚拟机解释执行。当我们使用python设计好算法给第三方使用时只能提供源码&#xff0c;任何运行我们算法的人都可以看到源码以及对应的算法思路。因此&#xff0c;需要一定手动保护源码。 最简单的保护方式是使用代码混…