不考虑传统 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包中的文件路径
image.png

开始解压

# 只解压出来需要的文件,无需把整个jar包都解压出来
jar xvf ZmlTools-1.4.2.jar top/wys/utils/ClassUtils.class filetype.properties

命令分解

我们把这个命令拆分成几个部分来看:

  1. jar: 这是命令的名称,代表Java Archive Tool(Java归档工具)。它用于创建和管理.jar文件。 JAR文件本质上是基于ZIP格式的压缩文件,用于打包Java的类文件、元数据和资源(如图片、配置文件等)。

  2. xvf: 这是传递给jar命令的一组选项,它们组合在一起,定义了要执行的操作。选项的顺序通常不重要,但它们必须紧跟在jar命令之后。

    • x:代表 "extract"(提取)。这是该命令要执行的主要操作,即从归档文件中提取文件。
    • v:代表 "verbose"(详细)。使用此选项会在标准输出(通常是你的终端)中显示详细的操作过程,比如正在提取哪些文件、它们的名称和大小等。
    • f:代表 "file"(文件)。这个选项告诉jar命令,将要操作的归档文件名是在命令行中指定的(在这个例子中就是ZmlTools-1.4.2.jar)。 如果不使用f选项,jar命令会尝试从标准输入读取归档内容。
  3. ZmlTools-1.4.2.jar: 这是要执行提取操作的目标JAR文件的名称。

  4. top/wys/utils/ClassUtils.classfiletype.properties: 这是两个用空格隔开的文件路径参数。它们指定了需要从ZmlTools-1.4.2.jar文件中提取的具体文件。

    • jar命令允许你指定一个或多个文件进行提取。
    • 如果此处不指定任何文件,jar命令默认会提取JAR包中的所有文件。

命令执行过程

当你在终端中执行这条命令时,会发生以下情况:

  1. jar工具会打开名为 ZmlTools-1.4.2.jar 的归档文件。
  2. 它会在归档文件中查找 top/wys/utils/ClassUtils.classfiletype.properties 这两个文件。
  3. 找到文件后,jar工具会将它们解压并复制到你当前所在的目录下。
  4. 对于带有路径的文件(如 top/wys/utils/ClassUtils.class),jar命令会在当前目录下创建相应的文件夹结构(即 top/wys/utils/),然后将ClassUtils.class文件放入其中。
  5. 由于使用了v选项,在提取过程中,终端会打印出类似下面的信息,告诉你操作的详细进展:
    created: top/
    created: top/wys/
    created: top/wys/utils/
    inflated: top/wys/utils/ClassUtils.class
    inflated: filetype.properties
    
  6. 操作完成后,原始的ZmlTools-1.4.2.jar文件保持不变。

样例截图

这个是windows 下的操作截图,可以直接在服务器上进行操作,不用把 jar 包下载到本地

image.png

开始压缩更新

修改完成后,再把文件压缩回jar包中

jar uvf ZmlTools-1.4.2.jar top/wys/utils/ClassUtils.class filetype.properties

image.png

命令分解

我们将命令的每个部分进行拆解:

  1. jar: 依然是Java Archive Tool(Java归档工具)的命令。

  2. uvf: 这是传递给jar命令的一组选项。这里的关键是 u

    • u:代表 "update"(更新)。这是此命令要执行的主要操作。它会向指定的归档文件中添加新文件,或者替换归档中已存在的同名文件。
    • v:代表 "verbose"(详细)。使用此选项会在终端上显示详细的操作过程,例如“adding: top/wys/utils/ClassUtils.class”,让你清楚地看到哪些文件被添加或更新了。
    • f:代表 "file"(文件)。这个选项告诉jar命令,要操作的归档文件名是在命令行中直接指定的(即 ZmlTools-1.4.2.jar)。
  3. ZmlTools-1.4.2.jar: 这是被更新的目标JAR文件的名称。这个文件必须已经存在。

  4. top/wys/utils/ClassUtils.classfiletype.properties: 这两个参数指定了用来更新的源文件。这些文件必须存在于你执行命令时所在的当前目录中。

命令执行过程

当你执行这条命令时,会发生以下情况:

  1. jar工具会打开名为 ZmlTools-1.4.2.jar 的归档文件进行修改。
  2. 它会从你当前的本地文件系统中查找 top/wys/utils/ClassUtils.classfiletype.properties 这两个源文件。
    • 重要提示: jar工具会期望在当前目录下能找到一个名为 top 的文件夹,里面有 wys 文件夹,再里面有 utils 文件夹,最终包含 ClassUtils.class 文件。同时,它也期望在当前目录下能找到 filetype.properties 文件。
  3. 对于每一个指定的源文件,jar工具会执行以下判断:
    • 如果JAR包中不存在同名文件jar工具会直接将这个新文件添加到JAR包中,并保持其目录结构。
    • 如果JAR包中已存在同名文件jar工具会比较本地源文件的修改时间戳和JAR包中文件的修改时间戳
      • 如果本地的源文件更新(时间戳更晚),jar工具就会用本地文件替换掉JAR包中的旧文件。
      • 如果本地的源文件没有更新(时间戳相同或更早),jar工具会跳过这个文件,不进行任何操作。
  4. 这个更新操作是直接在原 ZmlTools-1.4.2.jar 文件上进行的,它会修改这个文件。
  5. 由于使用了v选项,在更新过程中,终端会打印出被添加或替换的文件列表。

核心区别

命令jar xvf ...jar uvf ...
操作提取 (Extract)更新 (Update)
数据流向从 JAR包 -> 到 本地文件系统从 本地文件系统 -> 到 JAR包
对JAR包的影响不修改原始的JAR包直接修改原始的JAR包

场景二

打包方式采用 依赖lib包 和 可执行源码 jar包 分离的方式

tomcat出现漏洞,对其进行依赖升级

1. 拷贝升级 jar 包

  1. 将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包
    
    

image.png

2. 修改依赖的jar包版本号

更新原来的运行jar包的MANIFEST.MF文件中 tomcat的版本号为 9.0.87

这里以更新flowCharge 为例:

  1. 将 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
  1. 使用 vim 编辑 META-INF/MANIFEST.MF ,j将 tomcat-annotations-apitomcat-embed-coretomcat-embed-websocket的 3 个 tomcat 的jar包版本号为9.0.87,然后保存并退出!
    image.png

  2. 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-
  

image.png

 # 切回到 tmp 目录下,开始准备重新打包
   cd ../../
   

image.png

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 命令:“将当前目录下的所有文件和子目录(递归地)都打包进去。”

关键选项 M0 的深度解析

为什么使用 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 ./ 命令时:

  1. jar 工具准备创建一个名为 log-0.0.1.jar 的新文件。
  2. 它开始递归地扫描当前目录 (./) 下的所有内容。
  3. 它将扫描到的每一个文件和目录结构都直接存入 log-0.0.1.jar 中,不进行任何压缩
  4. 整个过程中,它不会创建 META-INF/ 目录和 MANIFEST.MF 文件。
  5. 最终,你得到的是一个体积可能较大(因为未压缩)但创建速度极快、且不含清单文件的归档包。

Q.E.D.


寻门而入,破门而出