不考虑传统 war 包文件部署,仅考虑目前常用的 jar包,在服务器更新服务时大致有以下三种场景
- 场景一:增量更新 jar 包中的部分 class 文件
- 场景二:可执行 jar 包和 lib 依赖包分开打包,需要增量升级部分 lib 依赖包
- 场景三:更新 fat-jar 包中的 依赖jar包 或者 jar包中的 class 文件(springboot 默认打的是 fat-jar,把所有依赖包和源码包打包成一个jar包),对于只改了少量代码时,对服务的更新十分不友好,全量更新就要传输非常大的jar包
现在针对于这三种场景来举例如何使用命令行更新,请确保服务器或者本地已经配置了 java的环境变量,确保可以正常找到 jdk 的 jar
命令
场景一
增量更新 jar 包中的部分 class 文件
解压 出来 jar包中的 filetype.properties
配置文件,修改其中的配置信息后,再更新回去;增量更新 top/wys/utils/ClassUtils.class
文件
解压前,可以先借助 vim
命令,查看一下 jar包中的文件路径
开始解压
# 只解压出来需要的文件,无需把整个jar包都解压出来
jar xvf ZmlTools-1.4.2.jar top/wys/utils/ClassUtils.class filetype.properties
命令分解
我们把这个命令拆分成几个部分来看:
-
jar
: 这是命令的名称,代表Java Archive Tool(Java归档工具)。它用于创建和管理.jar
文件。 JAR文件本质上是基于ZIP格式的压缩文件,用于打包Java的类文件、元数据和资源(如图片、配置文件等)。 -
xvf
: 这是传递给jar
命令的一组选项,它们组合在一起,定义了要执行的操作。选项的顺序通常不重要,但它们必须紧跟在jar
命令之后。x
:代表 "extract"(提取)。这是该命令要执行的主要操作,即从归档文件中提取文件。v
:代表 "verbose"(详细)。使用此选项会在标准输出(通常是你的终端)中显示详细的操作过程,比如正在提取哪些文件、它们的名称和大小等。f
:代表 "file"(文件)。这个选项告诉jar
命令,将要操作的归档文件名是在命令行中指定的(在这个例子中就是ZmlTools-1.4.2.jar
)。 如果不使用f
选项,jar
命令会尝试从标准输入读取归档内容。
-
ZmlTools-1.4.2.jar
: 这是要执行提取操作的目标JAR文件的名称。 -
top/wys/utils/ClassUtils.class
和filetype.properties
: 这是两个用空格隔开的文件路径参数。它们指定了需要从ZmlTools-1.4.2.jar
文件中提取的具体文件。jar
命令允许你指定一个或多个文件进行提取。- 如果此处不指定任何文件,
jar
命令默认会提取JAR包中的所有文件。
命令执行过程
当你在终端中执行这条命令时,会发生以下情况:
jar
工具会打开名为ZmlTools-1.4.2.jar
的归档文件。- 它会在归档文件中查找
top/wys/utils/ClassUtils.class
和filetype.properties
这两个文件。 - 找到文件后,
jar
工具会将它们解压并复制到你当前所在的目录下。 - 对于带有路径的文件(如
top/wys/utils/ClassUtils.class
),jar
命令会在当前目录下创建相应的文件夹结构(即top/wys/utils/
),然后将ClassUtils.class
文件放入其中。 - 由于使用了
v
选项,在提取过程中,终端会打印出类似下面的信息,告诉你操作的详细进展:created: top/ created: top/wys/ created: top/wys/utils/ inflated: top/wys/utils/ClassUtils.class inflated: filetype.properties
- 操作完成后,原始的
ZmlTools-1.4.2.jar
文件保持不变。
样例截图
这个是windows 下的操作截图,可以直接在服务器上进行操作,不用把 jar 包下载到本地
开始压缩更新
修改完成后,再把文件压缩回jar包中
jar uvf ZmlTools-1.4.2.jar top/wys/utils/ClassUtils.class filetype.properties
命令分解
我们将命令的每个部分进行拆解:
-
jar
: 依然是Java Archive Tool(Java归档工具)的命令。 -
uvf
: 这是传递给jar
命令的一组选项。这里的关键是u
。u
:代表 "update"(更新)。这是此命令要执行的主要操作。它会向指定的归档文件中添加新文件,或者替换归档中已存在的同名文件。v
:代表 "verbose"(详细)。使用此选项会在终端上显示详细的操作过程,例如“adding: top/wys/utils/ClassUtils.class”,让你清楚地看到哪些文件被添加或更新了。f
:代表 "file"(文件)。这个选项告诉jar
命令,要操作的归档文件名是在命令行中直接指定的(即ZmlTools-1.4.2.jar
)。
-
ZmlTools-1.4.2.jar
: 这是被更新的目标JAR文件的名称。这个文件必须已经存在。 -
top/wys/utils/ClassUtils.class
和filetype.properties
: 这两个参数指定了用来更新的源文件。这些文件必须存在于你执行命令时所在的当前目录中。
命令执行过程
当你执行这条命令时,会发生以下情况:
jar
工具会打开名为ZmlTools-1.4.2.jar
的归档文件进行修改。- 它会从你当前的本地文件系统中查找
top/wys/utils/ClassUtils.class
和filetype.properties
这两个源文件。- 重要提示:
jar
工具会期望在当前目录下能找到一个名为top
的文件夹,里面有wys
文件夹,再里面有utils
文件夹,最终包含ClassUtils.class
文件。同时,它也期望在当前目录下能找到filetype.properties
文件。
- 重要提示:
- 对于每一个指定的源文件,
jar
工具会执行以下判断:- 如果JAR包中不存在同名文件:
jar
工具会直接将这个新文件添加到JAR包中,并保持其目录结构。 - 如果JAR包中已存在同名文件:
jar
工具会比较本地源文件的修改时间戳和JAR包中文件的修改时间戳。- 如果本地的源文件更新(时间戳更晚),
jar
工具就会用本地文件替换掉JAR包中的旧文件。 - 如果本地的源文件没有更新(时间戳相同或更早),
jar
工具会跳过这个文件,不进行任何操作。
- 如果本地的源文件更新(时间戳更晚),
- 如果JAR包中不存在同名文件:
- 这个更新操作是直接在原
ZmlTools-1.4.2.jar
文件上进行的,它会修改这个文件。 - 由于使用了
v
选项,在更新过程中,终端会打印出被添加或替换的文件列表。
核心区别
命令 | jar xvf ... | jar uvf ... |
---|---|---|
操作 | 提取 (Extract) | 更新 (Update) |
数据流向 | 从 JAR包 -> 到 本地文件系统 | 从 本地文件系统 -> 到 JAR包 |
对JAR包的影响 | 不修改原始的JAR包 | 直接修改原始的JAR包 |
场景二
打包方式采用 依赖lib包 和 可执行源码 jar包 分离的方式
tomcat出现漏洞,对其进行依赖升级
1. 拷贝升级 jar 包
-
将lib下的 三个tomcat jar包 拷贝到对应的服务lib目录下,将原来的低版本jar包备份后,进行删除
# 进入到 lib 目录下,先进行备份 tar -zcvf tomcat-20240416-bak.tar.gz tomcat-*.jar # 将备份文件移动到上级目录 mv tomcat-20240416-bak.tar.gz ../ # 移除低版本 jar包 rm -rf tomcat-*.jar # 手动放置高版本jar包
2. 修改依赖的jar包版本号
更新原来的运行jar包的
MANIFEST.MF
文件中 tomcat的版本号为9.0.87
这里以更新flowCharge 为例:
- 将 MANEFIEST.MF 文件从jar包中解压出来
#
# jar xvf xxx.jar META-INF/MANIFEST.MF 是将 xxx.jar 包中的 META-INF 目录下的 MANIFEST.MF 文件解压到 当前目录,解压出来的 文件目录也保持围 META-INF/MANIFEST.MF
jar xvf fee-0.0.1-SNAPSHOT.jar META-INF/MANIFEST.MF
-
使用
vim
编辑META-INF/MANIFEST.MF
,j将tomcat-annotations-api
、tomcat-embed-core
、tomcat-embed-websocket
的 3 个 tomcat 的jar包版本号为9.0.87
,然后保存并退出!
-
将
META-INF/MANIFEST.MF
文件更新回原来的jar包# 1️⃣更新前,先备份原来jar包(压缩包名称自己定义) tar -zcvf fee-0.0.1-SNAPSHOT.jar-202404161452-bak.tar.gz fee-0.0.1-SNAPSHOT.jar # 2️⃣将MANIFEST.MF 文件更新到 jar包中 # 命令示例: jar uvfm xxx.jar META-INF/MANIFEST.MF # 注意: 往jar包更新一般文件时使用 uvf 参数即可,这里之所以添加 m 参数是因为 MANIFEST.MF 为归档文件 jar uvfm fee-0.0.1-SNAPSHOT.jar META-INF/MANIFEST.MF
命令分解
我们将命令的每个部分进行拆解:
-
jar
: 依然是Java Archive Tool(Java归档工具)的命令。 -
uvfm
: 这是传递给jar
命令的一组选项。u
: 代表 "update"(更新)。表示要对指定的JAR包进行更新操作。v
: 代表 "verbose"(详细)。在执行命令时,会在终端打印出详细的操作信息。f
: 代表 "file"(文件)。表示要操作的JAR包文件名是在命令行中指定的(即fee-0.0.1-SNAPSHOT.jar
)。m
: 代表 "manifest"(清单)。这是一个非常关键的选项。它告诉jar
命令,你将提供一个外部的清单文件,其内容将被用来创建或覆盖JAR包内部的META-INF/MANIFEST.MF
文件。
-
参数的顺序(非常重要)
当m
选项被使用时,jar
命令对后面参数的顺序有严格要求:
jar <选项> <目标jar文件名> <源manifest文件名> [其他要添加的文件...]
所以在这个命令中:
fee-0.0.1-SNAPSHOT.jar
是被更新的目标JAR文件。META-INF/MANIFEST.MF
是作为源数据使用的外部清单文件。
命令执行过程
jar
工具会准备更新名为fee-0.0.1-SNAPSHOT.jar
的归档文件。- 由于
m
选项的存在,jar
工具会把紧跟在JAR文件名后面的参数(即META-INF/MANIFEST.MF
)识别为源清单文件的路径。 jar
工具会在你当前的工作目录下查找这个名为META-INF/MANIFEST.MF
的文件。这意味着,你的当前目录下必须有一个META-INF
文件夹,并且里面包含一个MANIFEST.MF
文件。jar
工具会读取这个外部MANIFEST.MF
文件的内容。- 然后,它会打开
fee-0.0.1-SNAPSHOT.jar
文件,并用刚刚读取的外部清单文件的内容,完全替换掉JAR包内部原有的META-INF/MANIFEST.MF
文件。 - 这个操作是直接修改
fee-0.0.1-SNAPSHOT.jar
文件本身。 - 由于使用了
v
选项,终端会打印出类似 "adding: META-INF/MANIFEST.MF" 的信息,确认操作已执行。
3. 验证 jar 包中的 MANIFEST.MF 文件已更新成功
vim fee-0.0.1-SNAPSHOT.jar
# 光标切到 MANIFEST.MF 所在行,敲击 键盘上的 ENTER 键 ,进入 MANIFEST.MF 文件,确认 版本号已修改成功即可
场景三
更新 fat-jar 包中的 依赖jar包
使用的是 springboot 默认的打包方式,要升级jar包中的tomcat版本,无法直接删除jar包内的文件,需要先进行解压后,重新进行打包
1. 备份 和 解压
# 备份 原来的jar包
tar -zcvf log-0.0.1.jar-202404161511-bak.tar.gz log-0.0.1.jar
# 创建一个临时文件,并将log-0.0.1.jar 文件移动到该目录下进行处理,避免同级目录下的其他配置文件在后续打包时造成不必要的影响
mkdir tmp
mv log-0.0.1.jar tmp
cd tmp
jar xvf log-0.0.1.jar
# 进入到lib目录下删除旧的依赖包
cd BOOT-INF/lib
rm tomcat-embed-*
# 将升级后的jar包放到该目录下
# 确认已将新包放到该目录下
ll|grep tomcat-
# 切回到 tmp 目录下,开始准备重新打包
cd ../../
2. 重新打 jar 包
# 使用存储模式重新进行打包
# 0 表示告诉 jar 命令以存储模式(store mode)而非默认的 deflate 压缩模式来打包文件
# 也可以指定原来的MANIFEST.MF 文件进行打包 jar cfm0 log-0.0.1.jar META-INF/MANIFEST.MF ./
jar cfM0 log-0.0.1.jar ./
# 将 log-0.0.1.jar 移动到原来的目录
mv log-0.0.1.jar ../
jar cfM0 log-0.0.1.jar ./
这个命令的作用是:创建一个新的、完全不经过压缩的 JAR 文件,名为 log-0.0.1.jar
,其中包含当前目录下的所有文件和子目录,并且不自动生成清单文件(MANIFEST.MF)。
命令分解
-
jar
: Java Archive Tool(Java归档工具)的命令。 -
cfM0
: 这是一组选项的组合,定义了jar
命令要执行的具体操作。c
: 代表 "create"(创建)。表示要创建一个新的归档文件。f
: 代表 "file"(文件)。表示将在命令行中指定要创建的归档文件的名称(即log-0.0.1.jar
)。M
: 代表 "no-manifest"(不要创建清单文件)。这是一个关键选项。默认情况下,jar
命令在创建归档时会自动生成一个META-INF/MANIFEST.MF
清单文件。使用M
选项可以阻止这一行为。0
(零): 代表 "store only; do not use ZIP compression"(仅存储,不使用ZIP压缩)。这是另一个关键选项。默认情况下,JAR文件会使用ZIP算法对内容进行压缩以减小文件体积。使用0
选项会告诉jar
工具完全禁用压缩,只是将文件原样“存储”到归档中。
-
log-0.0.1.jar
: 这是将要被创建的JAR文件的名称。 -
./
: 这指定了要添加到JAR包中的内容来源。.
代表当前目录。./
是对当前目录的明确表示。- 这个参数告诉
jar
命令:“将当前目录下的所有文件和子目录(递归地)都打包进去。”
关键选项 M
和 0
的深度解析
为什么使用 M
(不创建Manifest)?
META-INF/MANIFEST.MF
文件是JAR包的元数据中心,包含了版本信息、创建者信息,以及最重要的 Main-Class
属性(用于使JAR包可执行)。在以下情况下,你可能不希望创建它:
- 创建库文件:如果你正在打包一个库(library),它本身不是一个可独立运行的程序,而是被其他应用引用的,那么它就不需要
Main-Class
,清单文件也就不是必需的。 - 自定义Manifest:你可能计划稍后手动添加一个自己编写的、内容更丰富的
MANIFEST.MF
文件。 - 非Java内容打包:虽然不常见,但
jar
工具本质上是一个ZIP工具,你可能只是用它来归档一些非Java的资源,此时清单文件毫无意义。
为什么使用 0
(不压缩)?
禁用压缩看起来有违常理,因为通常我们希望文件越小越好。但在某些特定场景下,不压缩反而更有优势:
- 性能和速度:
- 打包速度:不进行压缩会使得打包过程非常快,因为CPU无需进行计算密集型的压缩操作。
- 读取速度:某些应用服务器或类加载器可以直接从JAR包中读取文件,如果文件没有被压缩,就可以省去解压缩的步骤,从而可能加快类的加载速度。
- 内容已经压缩:如果你要打包的内容本身就是已经被压缩过的格式(例如:
.jpg
、.png
、.mp3
、.zip
文件),再次对它们进行压缩几乎不会减小体积,反而会白白浪费CPU资源。在这种情况下,禁用压缩是明智的选择。
执行过程总结
当你运行 jar cfM0 log-0.0.1.jar ./
命令时:
jar
工具准备创建一个名为log-0.0.1.jar
的新文件。- 它开始递归地扫描当前目录 (
./
) 下的所有内容。 - 它将扫描到的每一个文件和目录结构都直接存入
log-0.0.1.jar
中,不进行任何压缩。 - 整个过程中,它不会创建
META-INF/
目录和MANIFEST.MF
文件。 - 最终,你得到的是一个体积可能较大(因为未压缩)但创建速度极快、且不含清单文件的归档包。
Q.E.D.