jar工具详解

JAR 简介

JAR(Java Archive)文件格式能够将多个文件打包到单个归档文件中。通常,JAR 文件包含应用程序相关联的 .class 文件和辅助资源文件。JAR 文件格式提供了许多好处:

  • 安全性:您可以对 JAR 文件的内容进行数字签名。识别您的签名的用户可以选择允许您的软件安全权限,否则它不会拥有。
  • 减少传输时间和存储空间:Jar 文件本质上是一个 ZIP 压缩文件,打包之后文件总大小会变小,单个稍小的 Jar 文件进行文件传输时可以大大减少文件 IO 操作次数和流的传输长度,从而减少传输时间,同时也减少了存储空间。
  • 打包扩展:扩展框架提供了一种向 Java 核心平台添加功能的方法,JAR 文件格式定义了扩展的打包规范。通过使用 JAR 文件格式,你也可以把你的软件变成扩展。
  • 包密封性:存储在 JAR 文件中的 package 可以选择密封,这样 package 就可以加强版本一致性。在 JAR 文件中密封一个 package 意味着在该包中定义的所有类必须在同一 JAR 文件中找到
  • 包版本控制:JAR 文件可以保存关于它所包含的文件的数据,例如供应商和版本信息。
  • 可移植性:处理 JAR 文件的机制是 Java 平台核心 API 的标准部分。

JAR 文件是用 ZIP 文件格式打包的,因此您可以将它们用于无损数据压缩和解压缩、归档和解归档等任务。这些任务是 JAR 文件最常见的用途之一,仅使用这些基本特性就可以实现 JAR 文件的许多好处。要使用 JAR 文件执行基本任务,可以使用 JDK 中提供的 Java 归档工具。因为 Java 归档工具是通过使用 jar 命令调用的,所以本教程将其称为“jar 工具”。如果在本地已经安装好了 JDK 环境,则可以在命令行输入 jar 命令来查看帮助信息:

# 在 JDK 安装目录下的 bin 存在 jar 文件,在该目录下执行 jar 可以查看帮助信息
# 如果配置了 JAVA_HOME 环境变量并将 JAVA_HOME\bin 添加到了 PATH 中则可以在任何目录下执行 jar 命令
PS D:\development\tools\jdk1.8.0_171\bin> jar
用法: jar {ctxui}[vfmn0PMe] [jar-file] [manifest-file] [entry-point] [-C dir] files ...
选项:
    -c  创建新档案
    -t  列出档案目录
    -x  从档案中提取指定的 (或所有) 文件
    -u  更新现有档案
    -v  在标准输出中生成详细输出,详细输出告诉您每个文件添加到 JAR 文件时的名称。
    -f  指定档案文件名
    -m  包含指定清单文件中的清单信息。
    -n  创建新档案后执行 Pack200 规范化
    -e  为捆绑到可执行 jar 文件的独立应用程序指定应用程序入口点
    -0  (数字零)选项表示仅存储而不使用 ZIP 压缩 JAR 文件
    -P  保留文件名中的前导 '/' (绝对路径) 和 ".." (父目录) 组件
    -M  指示不应生成默认清单文件。Jar 工具默认会自动将一个清单文件添加到 Jar 归档文件中,其路径名为 META-INF/ manifest
    -i  为指定的 jar 文件生成索引信息
    -C  更改为指定的目录并包含以下文件
如果任何文件为目录, 则对其进行递归处理。
清单文件名, 档案文件名和入口点名称的指定顺序
与 'm', 'f' 和 'e' 标记的指定顺序相同。
示例 1: 将两个类文件归档到一个名为 classes.jar 的档案中:
       jar cvf classes.jar Foo.class Bar.class
示例 2: 使用现有的清单文件 'mymanifest' 并
           将 foo/ 目录中的所有文件归档到 'classes.jar' 中:
       jar cvfm classes.jar mymanifest -C foo/ .

下表总结了常见的 JAR 文件操作:

  • 创建 JAR 文件:jar cf jar-file input-file(s)
  • 查看 JAR 文件中的内容:jar tf jar-file
  • 提取 JAR 文件中的内容:jar xf jar-file;
  • 提取 JAR 文件中的指定的内容:jar xf jar-file archived-file(s)
  • 运行 JAR 文件中打包的应用程序(需要 Main-class manifest header):java -jar app.jar

创建和查看 JAR 文件

创建 JAR 文件的命令的基本格式是:jar cf jar-file input-file(s)。此命令中使用的选项和参数包括:

  • c 选项表示您要创建(create)一个 JAR 文件。
  • f 选项表示希望输出转到文件而不是标准输出。
  • jar-file 参数是您希望得到的 JAR 文件的名称。您可以对 JAR 文件使用任何文件名。按照惯例,JAR 文件名有一个 .jar 扩展名,尽管这不是必需的。
  • input-file(s) 参数是一个以空格分隔的列表,其中包含要包含在 JAR 文件中的一个或多个文件。输入文件参数可以包含通配符 * 符号。如果任何列表中的任一项是目录,那么这些目录的内容将递归地添加到 JAR 文件中。

cf 选项可以按任意顺序出现,但它们之间不能有任何空格。

此命令将生成一个压缩的 JAR 文件并将其放置在当前目录中。该命令还将为 JAR 归档生成一个默认清单文件。您可以将这些附加选项中的任何一个添加到基本的 cf 选项中:

  • v 选项在构建 JAR 文件时在 stdout 上生成详细(verbose)输出。详细输出告诉您每个文件添加到 JAR 文件时的名称。
  • 0 (数字零)选项表示不希望压缩 JAR 文件。
  • M 选项指示不应生成默认清单文件。
  • m 选项用于包含现有清单文件中的清单信息。
  • -C 选项在执行命令期间更改目录。请参见下面的示例。

创建 JAR 文件时,创建时间存储在 JAR 文件中。因此,即使 JAR 文件的内容没有改变,当您多次创建 JAR 文件时,得到的文件也不完全相同。在构建环境中使用 JAR 文件时,应该注意这一点。建议您使用清单文件中的版本控制信息而不是创建时间来控制 JAR 文件的版本。请参阅设置包版本信息部分。

示例

我们创建了一个名为 D:\idea_projects\ant-demo 测试项目。该目录下存在两个类文件:com.rosydawn.ant.AntTest 和 com.rosydawn.ant.HelloWord,以及一个 resources 目录,该目录下有 properties 和 xml 这两个子目录,这两个子目录下又分别存在 build.properties 和 build.xml 两个文件。如下所示:

# 在 Win10 的 PowerShell 中使用 `tree /F` 命令可以看到这些内容和结构
PS D:\idea_projects\ant-demo> tree /F
卷 学习 的文件夹 PATH 列表
卷序列号为 0001-1BB3
D:.
│
├─META-INF
│      MANIFEST.MF
│
└─src
    ├─com
    │  └─rosydawn
    │      └─ant
    │              AntTest.java
    │              HelloWord.java
    │
    └─resources
        ├─properties
        │      build.properties
        │
        └─xml
                build.xml
# 使用 IDEA 编译之后,在项目的编译输出目录 D:\idea_projects\out\production\ant-demo 可以看到编译后的内容
PS D:\idea_projects\out\production\ant-demo> tree /F
卷 学习 的文件夹 PATH 列表
卷序列号为 0001-1BB3
D:.
├─com
│  └─rosydawn
│      └─ant
│              AntTest.class
│              HelloWord.class
│
└─resources
    ├─properties
    │      build.properties
    │
    └─xml
            build.xml
# cvf 选项表示创建一个指定路径的 jar 包,打包时指定了 .class 文件和资源文件路径
PS D:\idea_projects\out\production\ant-demo> jar cvf jar-test.jar .\com\rosydawn\ant\AntTest.class .\com\rosydawn\ant\HelloWord.class resources
已添加清单
正在添加: com/rosydawn/ant/AntTest.class(输入 = 559) (输出 = 349)(压缩了 37%)
正在添加: com/rosydawn/ant/HelloWord.class(输入 = 566) (输出 = 350)(压缩了 38%)
正在添加: resources/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: resources/properties/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: resources/properties/build.properties(输入 = 33) (输出 = 29)(压缩了 12%)
正在添加: resources/xml/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: resources/xml/build.xml(输入 = 1509) (输出 = 610)(压缩了 59%)
# -t 选项列出档案目录
PS D:\idea_projects\out\production\ant-demo> jar tf .\jar-test.jar
META-INF/
META-INF/MANIFEST.MF
com/rosydawn/ant/AntTest.class
com/rosydawn/ant/HelloWord.class
resources/
resources/properties/
resources/properties/build.properties
resources/xml/
resources/xml/build.xml
# -tf 选项列出档案目录中各文件的详细信息
PS D:\idea_projects\out\production\ant-demo> jar tvf .\jar-test.jar
     0 Sun Jul 25 06:28:08 CST 2021 META-INF/
    69 Sun Jul 25 06:28:10 CST 2021 META-INF/MANIFEST.MF
   559 Sun Jul 25 06:14:26 CST 2021 com/rosydawn/ant/AntTest.class
   566 Sun Jul 25 06:14:26 CST 2021 com/rosydawn/ant/HelloWord.class
     0 Sun Jul 25 06:14:26 CST 2021 resources/
     0 Sun Jul 25 06:14:26 CST 2021 resources/properties/
    33 Sun Jul 25 04:07:54 CST 2021 resources/properties/build.properties
     0 Sun Jul 25 06:14:26 CST 2021 resources/xml/
  1509 Sun Jul 25 04:06:14 CST 2021 resources/xml/build.xml
# 0 选项仅存储文件而不进行压缩。输出内容中不含有压缩相关说明
PS D:\idea_projects\out\production\ant-demo> jar cvf0 jar-test.jar .\com\rosydawn\ant\AntTest.class .\com\rosydawn\ant\HelloWord.class resources
已添加清单
正在添加: com/rosydawn/ant/AntTest.class(输入 = 559) (输出 = 559)(存储了 0%)
正在添加: com/rosydawn/ant/HelloWord.class(输入 = 566) (输出 = 566)(存储了 0%)
正在添加: resources/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: resources/properties/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: resources/properties/build.properties(输入 = 33) (输出 = 33)(存储了 0%)
正在添加: resources/xml/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: resources/xml/build.xml(输入 = 1509) (输出 = 1509)(存储了 0%)
# 指定文件列表时使用通配符 * 来匹配文件路径中的任意字符
PS D:\idea_projects\out\production\ant-demo> jar cvf jar-test.jar *
已添加清单
正在添加: com/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: com/rosydawn/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: com/rosydawn/ant/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: com/rosydawn/ant/AntTest.class(输入 = 559) (输出 = 349)(压缩了 37%)
正在添加: com/rosydawn/ant/HelloWord.class(输入 = 566) (输出 = 350)(压缩了 38%)
正在添加: resources/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: resources/properties/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: resources/properties/build.properties(输入 = 33) (输出 = 29)(压缩了 12%)
正在添加: resources/xml/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: resources/xml/build.xml(输入 = 1509) (输出 = 610)(压缩了 59%)
PS D:\idea_projects\out\production\ant-demo> jar cvf jar-test.jar .\com\rosydawn\ant\*.class resources
已添加清单
正在添加: com/rosydawn/ant/AntTest.class(输入 = 559) (输出 = 349)(压缩了 37%)
正在添加: com/rosydawn/ant/HelloWord.class(输入 = 566) (输出 = 350)(压缩了 38%)
正在添加: resources/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: resources/properties/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: resources/properties/build.properties(输入 = 33) (输出 = 29)(压缩了 12%)
正在添加: resources/xml/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: resources/xml/build.xml(输入 = 1509) (输出 = 610)(压缩了 59%)
# -C 选项用来选择某个目录中的哪些文件打包到 JAR 文件的根目录中。下面的 “-C resources xml” 的含义为选择将 resources 目录下 xml 子目录打包到 JAR 中
PS D:\idea_projects\out\production\ant-demo> jar cvf xml.jar -C resources xml
已添加清单
正在添加: xml/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: xml/build.xml(输入 = 1509) (输出 = 610)(压缩了 59%)
PS D:\idea_projects\out\production\ant-demo> jar tf xml.jar
META-INF/
META-INF/MANIFEST.MF
xml/
xml/build.xml
# 下面的 “-C resources .” 的含义为选择将 resources 目录中的所有内容打包到 JAR 的根目录中
PS D:\idea_projects\out\production\ant-demo> jar cvf resources.jar -C resources .
已添加清单
正在添加: properties/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: properties/build.properties(输入 = 33) (输出 = 29)(压缩了 12%)
正在添加: xml/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: xml/build.xml(输入 = 1509) (输出 = 610)(压缩了 59%)
PS D:\idea_projects\out\production\ant-demo> jar tf resources.jar
META-INF/
META-INF/MANIFEST.MF
properties/
properties/build.properties
xml/
xml/build.xml

从 JAR 文件中提取内容

用于提取JAR文件内容的基本命令是:jar xf jar-file [archived-file(s)]。像往常一样,xf 选项在命令中出现的顺序无关紧要,但是它们之间不能有空格。在提取文件时,jar 工具从 JAR 文件中复制所需文件并将其写入当前目录,从而复制文件在 JAR 文件中的目录结构。原始 JAR 文件保持不变。在提取文件时,Jar 工具将覆盖与提取的文件具有相同路径名的任何现有文件。

示例

PS D:\idea_projects\out\production\ant-demo> mkdir jar
PS D:\idea_projects\out\production\ant-demo> mv .\jar-test.jar .\jar\
PS D:\idea_projects\out\production\ant-demo> cd .\jar\
# 使用 xf 选项来从 JAR 文件中提取文件到当前目录
PS D:\idea_projects\out\production\ant-demo\jar> jar xvf .\jar-test.jar
  已创建: META-INF/
  已解压: META-INF/MANIFEST.MF
  已解压: com/rosydawn/ant/AntTest.class
  已解压: com/rosydawn/ant/HelloWord.class
  已创建: resources/
  已创建: resources/properties/
  已解压: resources/properties/build.properties
  已创建: resources/xml/
  已解压: resources/xml/build.xml
# 也可以在前面的命令后面跟上 JAR 文件中的指定路径列表和提取部分文件。如果没有指定提取哪些文件,则默认提取所有文件
PS D:\idea_projects\out\production\ant-demo\jar> jar xvf .\jar-test.jar resources\xml\build.xml
  已解压: resources/xml/build.xml

更新 JAR 文件中的内容

jar 工具提供了一个 u 选项,您可以使用该选项通过修改其清单文件或添加文件来更新现有 JAR 文件的内容。基本命令是:jar uf jar-file input-file(s)

示例

我们将 HelloWord.java 的内容修改并重新编译:

package com.rosydawn.ant;

/**
 * @author Vincent Huang
 */
public class HelloWord {
    public static void main(String[] args) {
        System.out.println("Hello Vincent");
    }
}

然后使用 u 选项将编译输出的 HelloWord.class 更新到了 JAR 文件中:

PS D:\idea_projects\out\production\ant-demo\jar> cd ..
PS D:\idea_projects\out\production\ant-demo\jar> jar uvf .\jar-test.jar .\com\rosydawn\ant\HelloWord.class
正在添加: com/rosydawn/ant/AntTest.class(输入 = 559) (输出 = 349)(压缩了 37%)

清单文件

清单文件(manifest)是一个特殊的文件,可以包含 JAR 文件中的文件信息。通过裁剪清单包含的“元”信息,您可以使 JAR 文件服务于多种用途,比如支持多种功能,包括电子签名、版本控制、包密封性等。清单文件必须用 UTF-8 编码。JAR 文件中只能有一个清单文件,并且它始终具有路径名 META-INF/MANIFEST.MF。创建 JAR 文件时,默认清单文件仅包含以下内容:

Manifest-Version: 1.0
Created-By: 1.7.0_06 (Oracle Corporation)

以上这两行显示清单的条目采用 “header: value” 对的形式。header 的名称与其值之间用冒号分隔(冒号后面还有一个空格)。默认清单符合清单规范的 1.0 版本,由 JDK 的 1.7.0_06 版本创建。

清单还可以包含有关打包在存档中的其他文件的信息。清单中究竟应该记录哪些文件信息取决于您打算如何使用 JAR 文件。默认清单没有假设它应该记录关于其他文件的哪些信息。摘要信息不包括在默认清单中。要了解有关摘要和签名的更多信息,请参阅签名和验证 JAR 文件相关内容。

在创建 JAR 文件期间,可以使用 m 命令行选项将自定义信息添加到清单中。Jar工具会自动将路径名为 META-INF/MANIFEST.MF 的默认清单放入您创建的任何 JAR 文件中。您可以通过修改默认清单来启用特殊的 JAR 文件功能,例如,包密封性。通常,修改默认清单涉及向清单添加特殊用途的 header,以允许 JAR 文件执行特定的所需功能。

运行 JAR 文件中的应用程序

要想运行 JAR 文件中的应用程序,必须向 JAR 文件的清单中添加一个 Main-Class 的 header 指明哪个类是应用程序的入口点。该 header 采用以下形式:Main-Class: classname。该 header 的值 classname 是作为应用程序入口点的类的全限定名,该类需要具有一个 main 方法。该 header 具有一般形式:

Main-Class: MyPackage.MyClass

要修改清单,必须首先准备一个文本文件,该文本文件包含有要添加到清单文件中的内容。然后使用 jar 工具的 m 选项将文件中的信息添加到清单中。从中创建清单的文本文件必须以新行或回车结束。如果最后一行没有以新行或回车结束,则无法正确解析。基本命令的格式如下:

jar cfm jar-file manifest-addition input-file(s)

在清单中设置主类头之后,您可以使用 Java 启动器(java 命令)来运行 JAR 打包的应用程序。基本命令是:java -jar jar-file-jar 标志告诉启动器应用程序是以 jar 文件格式打包的。您只能指定一个 JAR 文件,该文件必须包含所有特定于应用程序的代码。在执行此命令之前,请确保运行时环境中有关于 JAR 文件中哪个类是应用程序入口点的信息。

示例

我们首先创建一个名为 Manifest.txt 的文本文件,其内容如下:

Main-Class: com.rosydawn.ant.HelloWord

注意:文本文件必须以新行或回车结束。如果最后一行没有以新行或回车结束,则无法正确解析。

然后创建一个 JAR 文件:

PS D:\idea_projects\out\production\ant-demo\jar> cd ..
# m 选项可以指定 JAR 文件的清单文件中的清单信息。这里使用 Manifest.txt 中的清单信息
PS D:\idea_projects\out\production\ant-demo> jar cvfm jar-test.jar .\Manifest.txt .\com\rosydawn\ant\*.class .\resources\
已添加清单
正在添加: com/rosydawn/ant/AntTest.class(输入 = 559) (输出 = 349)(压缩了 37%)
正在添加: com/rosydawn/ant/HelloWord.class(输入 = 566) (输出 = 350)(压缩了 38%)
正在添加: resources/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: resources/properties/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: resources/properties/build.properties(输入 = 33) (输出 = 29)(压缩了 12%)
正在添加: resources/xml/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: resources/xml/build.xml(输入 = 1509) (输出 = 610)(压缩了 59%)

如果 Manifest.txt 最后一行没有以新行或回车结束,则生成的 JAR 文件中的清单文件(META-INF/MANIFEST.MF)中仍是默认的内容。

然后就可以运行 JAR 文件中的应用程序了:

PS D:\idea_projects\out\production\ant-demo> java -jar .\jar-test.jar
Hello Vincent

e 选项(用于“entrypoint”)用于创建或重写清单的 Main-Class,可以在创建或更新 JAR 文件时使用。使用它来指定应用程序入口点,而无需编辑或创建清单文件。例如,此命令创建 Main.jar,其中清单中的 Main-Class 属性值设置为 foo.Main:

jar cfe Main.jar foo.Main foo/Main.class

示例如下:

PS D:\idea_projects\out\production\ant-demo> jar cvfe jar-test.jar com.rosydawn.ant.HelloWord com\rosydawn\ant\HelloWord.class .\com\rosydawn\ant\*.class .\resources\
已添加清单
正在添加: com/rosydawn/ant/HelloWord.class(输入 = 566) (输出 = 350)(压缩了 38%)
正在添加: com/rosydawn/ant/AntTest.class(输入 = 559) (输出 = 349)(压缩了 37%)
正在添加: resources/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: resources/properties/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: resources/properties/build.properties(输入 = 33) (输出 = 29)(压缩了 12%)
正在添加: resources/xml/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: resources/xml/build.xml(输入 = 1509) (输出 = 610)(压缩了 59%)
PS D:\idea_projects\out\production\ant-demo> java -jar .\jar-test.jar
Hello Vincent
PS D:\idea_projects\out\production\ant-demo> jar xvf .\jar-test.jar
  已创建: META-INF/
  已解压: META-INF/MANIFEST.MF
  已解压: com/rosydawn/ant/HelloWord.class
  已解压: com/rosydawn/ant/AntTest.class
  已创建: resources/
  已创建: resources/properties/
  已解压: resources/properties/build.properties
  已创建: resources/xml/
  已解压: resources/xml/build.xml
PS D:\idea_projects\out\production\ant-demo> cat .\META-INF\MANIFEST.MF
Manifest-Version: 1.0
Created-By: 1.8.0_171 (Oracle Corporation)
Main-Class: com.rosydawn.ant.HelloWord

向 JAR 文件的类路径添加类

您可能需要从 JAR 文件中引用其他 JAR 文件中的类。假如,一个 JAR 文件的清单引用了一个不同的 JAR 文件(或者几个不同的 JAR 文件),这个 JAR 文件需要指定其清单文件的 Class-Path 的 header。Class-Path header 采用以下形式:

Class-Path: jar1-name jar2-name directory-name/jar3-name

通过在清单文件中使用 Class-Path header ,可以避免在调用 java 命令运行应用程序时必须指定长的 -classpath 选项。 Class-Path header 指向本地网络上的类或 JAR 文件,而不是 JAR 文件中的 JAR 文件或通过 Internet 协议访问的类。要将 JAR 文件中的 JAR 文件中的类加载到类路径中,必须编写自定义代码来加载这些类。例如,如果 MyJar.jar 包含另一个名为 MyUtils.jar 的 jar 文件,则不能使用 MyJar.jar 清单中的 Class-Path header 将 MyUtils.jar 中的类加载到类路径中。

假如,我们希望将 MyUtils.jar 中的类加载到类路径中,以便在 MyJar.jar 中使用。这两个 JAR 文件位于同一目录中。我们首先创建一个名为 Manifest.txt 的文本文件,其内容如下:

Class-Path: MyUtils.ja

文本文件必须以新行或回车结束。如果最后一行没有以新行或回车结束,则无法正确解析。然后输入以下命令,创建一个名为MyJar.JAR的JAR文件:

jar cfm MyJar.jar Manifest.txt MyPackage/*.class

这将创建带有清单的 JAR 文件,清单包含以下内容:

Manifest-Version: 1.0
Class-Path: MyUtils.jar
Created-By: 1.7.0_06 (Oracle Corporation)

当您运行 MyJar.jar 时,MyUtils.jar 中的类现在被加载到类路径中。

设置包版本信息

您可能需要在 JAR 文件的清单中包含包版本信息。您可以在清单中使用以下 header 提供此信息:

Header 定义
Name 规范的名称
Specification-Title 规范的标题
Specification-Version 规范的版本
Specification-Vendor 规范的厂商
Implementation-Title 具体实现的标题
Implementation-Version 具体实现构建编号
Implementation-Vendor 具体实现的标厂商

可以为每个包分配一组这样的头。版本控制标头应该直接显示在包的名称标头下面。此示例显示所有版本控制标题:

Name: java/util/
Specification-Title: Java Utility Classes
Specification-Version: 1.2
Specification-Vendor: Example Tech, Inc.
Implementation-Title: java.util
Implementation-Version: build57
Implementation-Vendor: Example Tech, Inc.

有关包版本头的更多信息,请参阅包版本规范。

假如,我们希望在 MyJar.jar 的清单中包含上面示例中的头,首先创建一个名为 Manifest.txt 的文本文件,其内容如下:

Name: java/util/
Specification-Title: Java Utility Classes
Specification-Version: 1.2
Specification-Vendor: Example Tech, Inc.
Implementation-Title: java.util 
Implementation-Version: build57
Implementation-Vendor: Example Tech, Inc.

然后输入以下命令,创建一个名为 MyJar.jar 的 JAR 文件:

jar cfm MyJar.jar Manifest.txt MyPackage/*.class

这将创建带有清单的 JAR 文件,清单包含以下内容:

Manifest-Version: 1.0
Created-By: 1.7.0_06 (Oracle Corporation)
Name: java/util/
Specification-Title: Java Utility Classes
Specification-Version: 1.2
Specification-Vendor: Example Tech, Inc.
Implementation-Title: java.util 
Implementation-Version: build57
Implementation-Vendor: Example Tech, Inc.

密封 JAR 文件中的 package

JAR 文件中的 package 可以选择密封(seal),密封某个 package 意味着该 package 中定义的所有类必须归档在同一 JAR 文件中。如果希望密封一个 package 以确保软件中的类之间的版本一致性,或想保证某个 package 中的所有类都来自同一个代码源,您可以通过在清单文件中成对地添加 Name: toSeal/packagePath/Sealed: true 的清单 header,如下所示:

Name: myCompany/myPackage/
Sealed: true

myCompany/myPackage/ 是要密封的 package 的名称。请注意,包名称必须以 / 结尾。密封 JAR 指定密封该 JAR 定义的所有 package,除非对清单条目中具有 Sealed 属性的个别特定 package 进行显式重写。

假如,我们想在 JAR 文件 MyJar.jar中密封两个 package: firstPackage 和 secondPackage。我们首先创建一个名为 Manifest.txt 的文本文件,其内容如下:

Name: myCompany/firstPackage/
Sealed: true

Name: myCompany/secondPackage/
Sealed: true

文本文件必须以新行或回车结束。如果最后一行没有以新行或回车结束,则无法正确解析。然后输入以下命令,创建一个名为 MyJar.jar 的 JAR 文件:

jar cfm MyJar.jar Manifest.txt MyPackage/*.class

这将创建带有清单的 JAR 文件,清单包含以下内容:

Manifest-Version: 1.0
Created-By: 1.7.0_06 (Oracle Corporation)
Name: myCompany/firstPackage/
Sealed: true
Name: myCompany/secondPackage/
Sealed: true

使用清单属性增强安全性

以下 JAR 文件清单的 attribute 可用于帮助确保 Java Web Start 应用程序的安全性,只有 Permissions attribute 是必需的。

  • Permissions attribute 用于确保应用程序只请求在用于调用应用程序的 JNLP 文件中指定的权限级别。使用此 attribute 有助于防止有人重新部署使用您的证书所签名的应用程序,以及防止以不同的权限级别运行它。主 JAR 文件的清单中需要此属性。有关详细信息,请参阅 Java Platform Standard Edition Deployment Guide 中的 Permissions attribute。
  • Codebase attribute 用于确保 JAR 文件的代码库仅限于特定的域。使用此 attribute 可防止有人出于恶意目的在其他网站上重新部署您的应用程序。有关更多信息,请参阅 Java Platform Standard Edition Deployment Guide 中的 Codebase attribute 。
  • Application-Name attribute 用于提供已签名应用程序的安全提示中显示的标题。有关更多信息,请参阅 Java Platform Standard Edition Deployment Guide 中的 Application-Name attribute。
  • Application-Library-Allowable-Codebase attribute 用于标识希望在其中找到应用程序的位置。当 JAR 文件位于与 JNLP 文件或 HTML 页面不同的位置时,使用此属性可以减少安全提示中显示的位置数。有关详细信息,请参阅请参阅 Java Platform Standard Edition Deployment Guide 中的 Application-Library-Allowable-Codebase attribute。
  • Caller-Allowable-Codebase attribute 用于标识 JavaScript 代码可以从中调用应用程序的域。使用此 attribute 可防止未知 JavaScript 代码访问您的应用程序。有关详细信息,请参阅 Java Platform Standard Edition Deployment Guide 中的 Caller-Allowable-Codebase attribute。
  • Entry-Point attribute 用于标识允许用作 RIA 入口点的类。使用此 attribute 可防止未经授权的代码从 JAR 文件中的其他可用入口点运行。有关更多信息,请参阅请参阅 Java Platform Standard Edition Deployment Guide 中的 Entry-Point attribute。
  • Trusted-Only attribute 用于防止加载不受信任的组件。有关更多信息,请参阅请参阅 Java Platform Standard Edition Deployment Guide 中的 Trusted-Only attribute。
  • Trusted-Library attribute 用于允许在授权 Java 代码和沙盒 Java 代码之间进行调用,而无需提示用户获得权限。有关更多信息,请参阅 Java Platform Standard Edition Deployment Guide 中的 Trusted-Library attribute。

JAR 文件的签名与验证

您可以选择使用您的电子“signature”对 JAR 文件进行签名。验证您的签名的用户可以将安全权限授予您的 JAR 中的软件,通常它是不具备这种权限的。相反,您可以验证要使用的已签名 JAR 文件的签名。

理解签名和验证

Java 能够对 JAR 文件进行数字签名。对文件进行数字签名的原因与可能使用笔和对纸质文档进行签名的原因相同——让读者知道您编写了文档,或者至少文档已获得您的批准。

例如,当您在一封信上签名时,每个认出您签名的人都可以确认这封信是您写的。同样,当您对文件进行数字签名时,任何“识别”您的数字签名的人都知道该文件来自您。“识别”电子签名的过程称为验证(verification)。

当 JAR 文件被签名时,您还可以选择在签名上加盖时间戳。与在纸质文档上添加日期类似,签名的时间戳标识 JAR 文件的签名时间。时间戳可用来验证进行签名 JAR 文件的证书在签名时是否有效。

签名和验证文件的能力是 Java 平台安全体系结构的一个重要部分。安全性由运行时生效的安全策略控制。您可以将策略配置为向应用程序授予安全特权。例如,您可以授予应用程序执行通常禁止的操作的权限,受信任的应用程序可以拥有有效策略文件指定的自由。

Java 平台通过使用称为公钥和私钥的特殊数字来实现签名和验证。公钥和私钥成对出现,它们起着互补的作用。私钥是电子“笔”,你可以用它签署文件。顾名思义,您的私钥只有您自己知道,因此其他人无法“伪造”您的签名。使用私钥签名的文件只能通过相应的公钥进行验证。

然而,单靠公钥和私钥还不足以真正验证签名。即使您已经验证了签名文件包含匹配的密钥对,您仍然需要某种方法来确认公钥实际上来自它声称来自的签名者。
因此,还需要签名者在已签名 JAR 文件中所包含的证书。证书是来自公认证书颁发机构的数字签名声明,表示谁拥有特定公钥。认证机构是整个行业都信任的实体(通常是专门从事数字安全的公司),可以为密钥及其所有者签发证书。对于已签名的 JAR 文件,证书表明谁拥有 JAR 文件中包含的公钥。

当您对 JAR 文件进行签名时,您的公钥将与相关证书一起放在 JAR 文件中,以便任何想要验证您的签名的人都可以轻松地使用它。总结数字签名:

  • 签名者使用私钥对 JAR 文件进行签名。
  • 相应的公钥与证书一起放在 JAR 文件中,以便任何想要验证签名的人都可以使用它。
摘要和签名文件

当您对 JAR 文件进行签名时,JAR 文件中的每个文件都会在 JAR 文件的清单文件中获得一个摘要条目。下面是这样一个条目的示例:

Name: test/classes/ClassOne.class
SHA1-Digest: TD1GZt8G11dXY2p4olSZPc5Rj64=

摘要值是签名时文件内容的哈希值或编码表示。当且仅当文件本身更改时,文件摘要才会更改。当对 JAR 文件进行签名时,会自动生成一个签名文件并将其放置在 JAR 文件的 META-INF 目录中,该目录与包含清单文件的目录相同。具有扩展名为 .SF 的签名文件。以下是签名文件内容的示例:

Signature-Version: 1.0
SHA1-Digest-Manifest: h1yS+K9T7DyHtZrtI+LxvgqaMYM=
Created-By: 1.7.0_06 (Oracle Corporation)

Name: test/classes/ClassOne.class
SHA1-Digest: fcav7ShIG6i86xPepmitOVo4vWY=

Name: test/classes/ClassTwo.class
SHA1-Digest: xrQem9snnPhLySDiZyclMlsFdtM=

Name: test/images/ImageOne.gif
SHA1-Digest: kdHbE7kL9ZHLgK7akHttYV4XIa0=

Name: test/images/ImageTwo.gif
SHA1-Digest: mF0D5zpk68R4oaxEqoS9Q7nhm60=

如您所见,签名文件包含 JAR 文件的摘要条目,这些条目看起来类似于清单中的摘要条目。但是,清单中的摘要值是根据文件各自的内容计算的,但签名文件中的摘要值是从清单中的相应条目计算的。签名文件还包含整个清单的摘要值(请参见上面示例中的 SHA1 摘要清单头)。

在验证已签名的 JAR 文件时,将重新计算每个文件的摘要,并将其与清单中对应的摘要条目进行比较,以确保 JAR 文件的内容在签名后没有更改。作为附加检查,重新计算清单文件本身的摘要值,并与签名文件中记录的清单文件的摘要值进行比较。您可以在 JDK 的 Manifest Format 页面上阅读有关签名文件的其他信息文档。

签名块文件

除了签名文件之外,当 JAR 文件被签名时,签名块(signature block)文件会自动放在 META-INF 目录中。与清单文件或签名文件不同,签名块文件不是人眼可读的(human-readable)。

签名块文件包含两个验证所必需的元素:

  • 使用私钥所生成的 JAR 文件的数字签名。
  • 包含签名者公钥的证书,任何人都可以使用该证书验证该 JAR 文件。

签名块文件名通常具有 .DSA 扩展名,表明它们是由默认数字签名算法创建的。如果使用与其他标准算法关联的密钥进行签名,则可以使用其他文件扩展名。

相关文档

有关密钥、证书和证书颁发机构的更多信息,请参阅:

  • The JDK Security Tools
  • X.509 Certificates

有关 Java 平台安全体系结构的更多信息,请参阅以下相关文档:

  • Security Features in Java SE
  • Java SE Security
  • Security Tools

签名 JAR 文件

您可以使用 jarsigner 命令调用 JAR 签名和验证工具来对 JAR 文件进行签名,并在签名上加上时间戳。要对 JAR 文件进行签名,必须首先拥有私钥。私钥及其关联的公钥证书存储在名为 keystores 的受密码保护的密钥库中。密钥库可以保存许多签名者的密钥。密钥库中的每个密钥都可以通过别名进行标识,别名通常是拥有密钥的签名者的名称。例如,属于 Rita Jones 的密钥的别名可能为“rita”。用于签署 JAR 文件的命令的基本形式是:

jarsigner jar-file alias

在此命令中:

  • jar-file 是要签名的 jar 文件的路径名。
  • alias 是标识用于对 JAR 文件进行签名的私钥以及密钥的关联证书的别名。

jarsigner 工具将提示您输入密钥库和别名的密码。此命令假定要使用的密钥库位于主目录中名为 .keystore 的文件中。它将分别创建名为 x.SF 的签名和 x.DSA 的签名块文件,其中 x 是别名的前八个字母,全部转换为大写。这个基本命令将用签名的 JAR 文件覆盖原始 JAR 文件。

实际上,您可能希望使用一个或多个可用的命令选项。例如,鼓励在签名上加盖时间戳,以便用于部署应用程序的任何工具都可以验证用于签名 JAR 文件的证书在签名文件时是否有效。如果未包含时间戳,jarsigner 工具将发出警告。jarsigner 工具的选项位于JAR 文件路径名之前。下表介绍了可用的选项:

Option Description
-keystore url 如果不想使用默认的密钥库(路径为 ~/.keystore)时,该选项可以用来指定所用密钥库的位置。
-sigfile file 如果不想使用默认的密钥别名作为签名文件(.SF)和签名块文件(.DSA)的基本名称时,该选项可以用来指定签名文件和签名块文件的基本名称。 file 必须由大写的字母(A-Z)、数字(0-9)中划线(-)、和下划线(_)构成。
-signedjar file 如果不想让签名后的 JAR 文件替换源 JAR 文件,则可以使用该选项来指定签名后的 JAR 的路径。
-tsa url 使用 url 所识别的 Time Stamping Authority (TSA) 来生成签名的时间戳。
-tsacert alias 使用 alias 所代表的 TSA 的公钥证书创建签名的时间戳。
-altsigner class 该选项用来指定使用替代的签名机制来为签名打上时间戳。class 为全限定类名,表示所用的替代的代签名机制。
-altsignerpath classpathlist 该选项用来指定签名操作所用的 classpath 列表,包含类和 JAR 文件的列表。
示例

让我们看两个使用 jarsigner 工具对 JAR 文件进行签名的示例。在这些示例中,我们将假设如下:

  • 你的别名是“johndoe”(无名氏)。
  • 要使用的密钥库位于当前工作目录下名为“mykeys”的文件中。
  • 要用于标记签名时间的 TSA 位于 http://tsa.url.example.com。

在这些假设下,您可以使用此命令对名为 app.jar 的 JAR 文件进行签名:

jarsigner -keystore mykeys -tsa http://tsa.url.example.com app.jar johndoe

系统将提示您输入密钥库和别名的密码。由于该命令未使用 -sigfile 选项,因此生成的 .SF 和 .DSA 文件名为 JOHNDOE.SF 和 JOHNDOE.DSA。由于该命令未使用 -signedjar 选项,因此生成的签名文件将覆盖 app.jar 的原始版本。

让我们看看如果使用不同的选项组合会发生什么:

jarsigner -keystore mykeys -sigfile SIG -signedjar SignedApp.jar -tsacert testalias app.jar johndoe

签名和签名块文件将分别命名为 SIG.SF 和 SIG.DSA,签名的 JAR 文件 SignedApp.jar 将放在当前目录中。原始的未签名 JAR 文件将保持不变。此外,签名将加盖 TSA 的公钥证书(标识为 testalias)的时间戳。

JAR 签名和验证工具的完整参考页是在线的:Summary of Security Tools。

当证书自签名时,应用程序的发布者将显示为 UNKNOWN 。有关详细信息,请参阅 Is it safe to run an application from a publisher that is listed as UNKNOWN ?。

验证签名的 JAR 文件

通常,签名 JAR 文件的验证将由 Java运行时环境负责。使用解释器的 -jar 选项调用的已签名应用程序将由运行时环境进行验证。但是,您可以使用 jarsigner 工具自己验证已签名的 JAR 文件。例如,您可能希望这样做,以测试您准备的已签名 JAR 文件。用于验证已签名 JAR 文件的基本命令是:

jarsigner -verify jar-file

此命令将验证 JAR 文件的签名,并确保 JAR 文件中的文件从签名后没有更改。如果验证成功,您将看到以下消息:

jar verified.

如果尝试验证未签名的 JAR 文件,则会出现以下消息:

jar is unsigned. (signatures missing or not parsable)

如果验证失败,将显示相应的消息。例如,如果 JAR 文件的内容在 JAR 文件签名后发生了更改,则如果您尝试验证该文件,将产生类似于以下内容的消息:

jarsigner: java.lang.SecurityException: invalid SHA1 
signature file digest for test/classes/Manifest.class

如果签名 JAR 文件使用 %JRE.HOME%/lib/security/java.security 文件中 jdk.jar.disabledAlgorithms 安全属性中指定的任何算法,则 JDK 将签名 JAR 文件视为未签名。

你可能感兴趣的:(jar工具详解)