移动测试开发 JAR 文件规范详解

opentest-oper@360.cn · 2020年08月20日 · 723 次阅读

一、介绍

JAR 文件是基于 ZIP 文件格式的一种文件格式,用来将许多文件整合成一个文件。一个 JAR 文件本质上是包含可选目录 META-INF 的 zip 文件,可以通过命令行 jar 工具或者在 Java 平台上使用 java.util.jar 中的 API 来创建。JAR 文件的命名没有严格的要求,可以是特定平台上的任意合法文件名称。
在很多场景中,JAR 文件不仅仅用来对 java class 文件和资源文件进行归档,还被用来作应用程序及其扩展的构建块。如果包含 META-INF 目录,则是用来存储包信息和扩展配置数据,配置数据包括安全信息,版本控制信息,扩展信息和服务信息等。

二、META-INF 目录

在 Java 2 平台会识别和解释 META-INF 目录下面的文件和目录,以配置应用程序、扩展、类加载器和服务。
MANIFEST.MF 是用来定义扩展和包相关数据的清单文件。
INDEX.LIST 通过 jar 工具的-i 选项来生成,它包含应用程序及其扩展程序中的包的位置信息,同时它是 JarIndex 实现的一部分,类加载器使用它来提高类的加载速度。
x.SF 是 JAR 文件的签名文件,x 表示基本文件名称。
x.DSA 是于 x.SF 关联的签名块文件,它们有相同的基本文件名。此文件存储相应签名文件的数字签名。
services/ 该目录存储所有服务提供程序配置文件。

三、名称 - 值对和节

在我们深入每个配置文件的细节之前,需要定义一些格式约定。
在大部分场景中,包含在清单文件和签名文件中的信息表示为受 RFC822 标准启发的所谓的 “名称:值” 对。“名称:值” 对也被称为头和属性。名称 - 值对组称为 “节”,每一 “节” 通过空行分离。
任何形式的二进制数据都表示为 base64。行的长度超过 72 字节的二进制数据需要延续。摘要和签名就是二进制数据的例子。
实现应该最多支持 65535 字节的头值。
本文档中的所有规范使用相同的语法,其中终端符号以固定宽度字体显示,非终端符号以斜体字体显示。

节规范:

section:                       *header +newline
nonempty-section:      +header +newline
newline:                      CR LF | LF | CR (not followed by LF)
header:                       name : value
name:                         alphanum *headerchar
value:                          SPACE *otherchar newline *continuation
continuation:              SPACE *otherchar newline
alphanum:                  {A-Z} | {a-z} | {0-9}
headerchar:                alphanum | - | _
otherchar:                  any UTF-8 character except NUL, CR and LF
;Also: To prevent mangling of files sent via straight e-mail, no
;header will start with the four letters "From".

上述规范中定义的非终端符号将在以下规范中引用。

四、JAR Manifest

1.概述
JAR 文件的清单由主节和拥有多个私有 JAR 文件条目的单独节列表,每行通过换行符分隔。主节和单独节都遵循上面指定的节语法。它们都有自己特定的限制和规则。
主节包括包括 JAR 文件自身的安全和配置信息,以及此 JAR 文件所属应用程序及扩展。清单文件同样定义了每个单独清单条目的主属性。每节中没有属性可以使用 “Name” 作为名称。每一节通过空行结束。
单独节定义了 JAR 文件中包和文件的各种属性。不是所有在 JAR 文件中的文件都需要被作为条目列在清单中,但是所有被签名的文件必须被列出。清单文件自身不需要被列出。每节必须以名称 “Name” 的属性作为开始,并且值必须是该文件的的相对路径或者是应用存档外部数据的绝对 URL。
如果相同条目有多个单独节,则这些单独节中的属性会被合并。如果不同节中的相同属性有不同的值,则识别最后一个。
不理解的属性会被忽略。这些属性可能包含应用程序使用的实现特定信息。

2.清单文件规范

manifest-file:                 main-section newline *individual-section
main-section:                  version-info newline *main-attribute
version-info:                  Manifest-Version : version-number
version-number :               digit+{.digit+}*
main-attribute:                (any legitimate main attribute) newline
individual-section:            Name : value newline *perentry-attribute
perentry-attribute:            (any legitimate perentry attribute) newline
newline :                      CR LF | LF | CR (not followed by LF)
digit:                         {0-9} 

在上面的规范中,可以出现在主节中的属性称为主属性,而可以出现在单独部分的属性称为每个条目的属性。某些属性可以同时出现在主节和单独节中,在这种情况下,每个条目的属性值会覆盖指定条目的主属性值。这两种类型的属性定义如下。

3.主属性
主属性是清单中出现在主节中的属性。他们可以分为以下几类:
① 一般主属性

Manifest-Version:定义清单版本。如上面规范所述,值是何方的版本号
Create-By:定义生成此清单文件的java实现版本和供应商。该属性由jar工具生成。
Signature-Version:定义jar文件的签名版本。该值应该是一个有效的版本号字符串。
Class-Path:此属性的值指定此应用程序或扩展所需的扩展名或依赖库的相对url。url由一个或多个空格分隔。应用程序或扩展类加载器使用此属性的值来构造其内部搜索路径。参见Class-Path属性。

② 为独立应用程序定义的属性
这个属性被捆绑到可执行 jar 文件中的独立应用程序使用,java 运行时可以通过运行 “java -jar x.jar” 直接调用这些可执行 jar 文件。

Main-class:此属性的值是启动程序在启动时加载的主应用程序类的类名。该值不能将.class扩展追加到类名。

③ 为 applet 定义的属性
弃用:这些属性和实现它的机制可能会在未来的版本中被删除。这些属性被绑定到 JAR 文件中的 applet 使用,以定义该 applet 所依赖的扩展的需求、版本和位置信息。

Extension-List:此属性指示applet需要的扩展。此属性中列出的每个扩展都有一组附加属性,applet使用这些属性来指定需要的扩展的版本和供应商。
<extension>-Extension-Name:此属性是扩展的唯一名称。Java插件将此属性的值与已安装扩展清单中的extension - name属性进行比较,以确定是否安装了该扩展。
<extension>-Specification-Version:该属性指定了applet所需的最小扩展规范版本。Java插件将比较此属性的值与已安装扩展的Specification-Version属性,以确定该扩展是否是最新的。
<extension>-Implementation-Version:这个属性指定了applet所需要的最小扩展实现版本号。Java插件将比较此属性的值与已安装扩展的实现版本属性,以查看是否需要下载更近期的实现。
<extension>-Implementation-Vendor-Id:如果applet需要来自特定厂商的实现,则此属性可用于标识扩展实现的厂商。Java插件将此属性的值与已安装扩展的Implementation-Vendor-Id属性进行比较。
<extension>-Implementation-URL:此属性指定一个URL,如果尚未安装所需的扩展版本,则可以使用该URL获取扩展的最新版本。

④ 为扩展标识定义的属性
扩展使用此属性定义其惟一标识。

Extension-Name: 此属性指定Jar文件中包含的扩展名的名称。名称应该是唯一的标识符,如包含扩展的主包的名称。

⑤ 为扩展、包版本和封装信息定义的属性
这些属性定义了 JAR 文件所属的扩展的特性。这些属性的值适用于 JAR 文件中的所有包,但是可以被每个条目的属性覆盖。

Implementation-Title:值是定义扩展实现标题的字符串。
Implementation-Version:值是定义扩展实现版本的字符串。
Implementation-Vendor:值定义维护扩展实现的供应商。
Implementation-Vendor-Id:值是一个字符串id,惟一地定义了维护扩展实现的供应商。这个属性可能在将来的版本中被忽略。
Implementation-URL: 此属性定义可从其中下载扩展实现的URL。这个属性可能在将来的版本中被忽略。
Specification-Title:值是定义扩展规范标题的字符串。
Specification-Version: 值是定义扩展规范版本的字符串。
Specification-Vendor: 值是一个字符串,它定义维护扩展规范的供应商。
Sealed:这个属性定义了这个JAR文件是否是密封的。值可以是“true”或“false”,大小写被忽略。如果它被设置为“true”,那么JAR文件中的所有包默认都是密封的,除非它们分别被定义。

4.每个条目的属性
每个条目的属性只应用于与清单条目相关联的单个 JAR 文件条目。如果同一属性也出现在主节中,那么每个条目属性的值将覆盖主属性的值。例如,如果 JAR 文件 a.jar 有以下清单内容:

Manifest-Version: 1.0
Created-By: 1.2 (Sun Microsystems Inc.)
Sealed: true
Name: foo/bar/
Sealed: false

这意味着归档在 a.jar 中的所有包都是密封的,除了包 foo.bar 不是。
每个条目的属性分为以下几组:
① 定义文件内容的属性
Content-Type:此属性可用于指定 JAR 文件中特定文件条目的 MIME 类型和数据子类型。值应该是 type/subtype 形式的字符串。
例如,“image/bmp” 是一个带有 bmp(表示位图) 子类型的图像类型。这将把文件条目指示为图像,并将数据存储为位图。RFC 1521 和 1522 讨论并定义 MIME 类型。

② 定义版本和封装信息的属性
这些属性与上面定义的用于定义扩展包版本控制和封装信息的主属性相同。当作为每个条目属性使用时,这些属性将覆盖主属性,但仅应用于清单条目指定的单个文件。

③ 定义 bean 对象的属性
Java-Bean: 定义特定的 jar 文件条目是否是 Java bean 对象。值应该是 “true” 或 “false”,大小写被忽略。

④ 定义签名的属性
这些属性用于签名和验证目的。更多细节在这里。
x-Digest-y:此属性的名称指定用于计算对应 jar 文件条目的摘要值的摘要算法的名称。此属性的值存储实际的摘要值。前缀'x'指定算法名称,可选后缀'y'表示应该根据哪种语言验证摘要值。
Magic: 这是一个可选属性,应用程序可以使用它来指示 verifier 应该如何计算清单项中包含的摘要值。这个属性的值是一组逗号分隔的上下文特定字符串。

五、签名的 JAR 文件

1.概述
可以使用命令行 jarsigner 工具或直接通过 java.security API 对 JAR 文件进行签名。如果 JAR 文件是由 jarsigner 工具签名的,那么每个文件条目 (包括 META-INF 目录中与非签名相关的文件) 都将被签名。签名的相关文件有:

META-INF/MANIFEST.MF
META-INF/*.SF
META-INF/*.DSA
META-INF/*.RSA
META-INF/SIG-*

注意,如果这些文件位于 META-INF 子目录中,则不认为它们与签名相关。这些文件名(不区分大小写)的版本会被保留,也不会被签名。
可以使用 java.security API 对 JAR 文件的子集进行签名。签名 JAR 文件与原始 JAR 文件完全相同,除了它的清单被更新和两个附加文件被添加到 META-INF 目录:一个签名文件和一个签名块文件。如果不使用 jarsigner,签名程序必须同时构造签名文件和签名块文件。
对于签名 JAR 文件中的每个文件条目,会在清单文件中为它创建一个单独的清单条目。每个清单条目列出一个或多个摘要属性和一个可选的 Magic 属性。

2.签名文件
每个签名者都由一个扩展名为. sf 的签名文件表示。该文件的主要部分与清单文件类似。它由一个主要节组成,其中包含签名者提供的信息,但并不特定于任何特定的 jar 文件条目。除了签名版本(Signature-Version)和创建属性(Created-By,参见主属性),主节还可以包括以下安全属性:
x-Digest-Manifest-Main-Attributes:其中 x 是 java.security.MessageDigest 算法的标准名称,此属性的值是清单文件的主属性的摘要值。
x-Digest-Manifest :其中 x 是 java.security.MessageDigest 算法的标准名称,此属性的值是整个清单的摘要值。
主节后面是单个条目的列表,这些条目的名称也必须出现在清单文件中。每个单独的条目必须至少包含清单文件中对应条目的摘要。出现在清单文件但没有出现在签名文件中的路径或 url 不会在计算中使用。

2.1 签名验证
如果签名是有效的,并且在签名生成之后,JAR 文件中的任何文件都没有被更改,那么就会发生成功的 JAR 文件验证。JAR 文件验证包括以下步骤:
① 在第一次解析清单时,验证在签名文件上的签名。为了提高效率,这种验证应该被记忆。注意,此验证仅验证签名说明本身,而不是实际的归档文件。

② 如果签名文件中存在 x-Digest-Manifest 属性,则根据根据整个清单计算的摘要验证该值。如果签名文件中存在多个 x-Digest-Manifest 属性,验证其中至少有一个与计算的摘要值匹配。

③ 如果签名文件中不存在 x-Digest-Manifest 属性,或者在前面的步骤中计算的摘要值都不匹配,那么将执行优化较少的验证:
Ⅰ.如果签名文件中存在 x-Digest-Manifest-Main-Attributes 条目,则根据根据清单文件中的主要属性计算的摘要验证该值。如果计算失败,则 JAR 文件验证失败。这项决定可以因其效率而被记忆。如果签名文件中不存在 x-Digest-Manifest-Main-Attributes 条目,那么它的不存在不会影响 JAR 文件验证,并且清单的主属性也不会被验证。
Ⅱ.根据根据清单文件中相应条目计算的摘要值,验证签名文件中每个源文件信息部分中的摘要值。如果任何摘要值不匹配,则 JAR 文件验证失败。

④ 对于清单中的每个条目,根据根据在 “Name:” 属性中引用的实际数据计算的摘要验证清单文件中的摘要值,该属性指定一个相对文件路径或 URL。如果任何摘要值不匹配,则 JAR 文件验证失败。
清单文件示例:

Manifest-Version: 1.0
Created-By: 1.7.0 (Sun Microsystems Inc.)

Name: common/class1.class
SHA-256-Digest: (base64 representation of SHA-256 digest)

Name: common/class2.class
SHA1-Digest: (base64 representation of SHA1 digest)
SHA-256-Digest: (base64 representation of SHA-256 digest)

相应的签名文件示例:

Signature-Version: 1.0
SHA-256-Digest-Manifest: (base64 representation of SHA-256 digest)
SHA-256-Digest-Manifest-Main-Attributes: (base64 representation of SHA-256 digest)

Name: common/class1.class
SHA-256-Digest: (base64 representation of SHA-256 digest)

Name: common/class2.class
SHA-256-Digest: (base64 representation of SHA-256 digest)

2.2 Magic 属性
验证给定清单条目上签名的另一个要求是验证者理解该条目的清单条目中 Magic 键对值的值。Magic 属性是可选的,但如果解析器正在验证条目的签名,则必须理解该条目的 Magic key 的值。
Magic 属性的值是一组逗号分隔的特定于上下文的字符串。逗号之前和之后的空格将被忽略。大小写被忽略,magic 属性的确切含义是依赖于具体的应用程序。这些值指示如何计算清单条目中包含的散列值,因此对于签名的正确验证至关重要。关键字可以用于动态或嵌入式内容,可以用于多语言文档的多个散列,等等。
下面是清单文件中使用 Magic 属性的两个例子:

Name: http://www.example-scripts.com/index#script1
SHA-256-Digest: (base64 representation of SHA-256 hash)
Magic: JavaScript, Dynamic

Name: http://www.example-tourist.com/guide.html
SHA-256-Digest: (base64 representation of SHA-256 hash)
SHA-256-Digest-French: (base64 representation of SHA-256 hash)
SHA-256-Digest-German: (base64 representation of SHA-256 hash)
Magic: Multilingual

在第一个示例中,这些 Magic 的值可能表明 http 查询的结果是嵌入到文档的脚本中 (而不是文档本身),而且脚本是动态生成的。这两个信息说明了如何计算哈希值来比较清单的摘要值,从而比较有效签名。
在第二个示例中,这个值表示检索到的文档可能是针对特定语言进行内容协商的,而要验证的摘要取决于所检索到的文档是用哪种语言编写的。

3.数字签名
数字签名是. sf 签名文件的已签名版本。这些是二进制文件,人类无法解释。
数字签名文件的文件名与.sf 文件相同,但扩展名不同。扩展取决于数字签名的类型。例如:

.RSA (PKCS7 signature, SHA-256 + RSA)
.DSA (PKCS7 signature, DSA)

包括上面没有列出的签名算法的数字签名文件必须在 META-INF 目录和前缀 “SIG-”。相应的签名文件 (.sf 文件) 也必须具有相同的前缀。
对于那些不支持外部签名数据的格式,该文件应由. sf 文件的签名副本组成。因此,有些数据可能是重复的,验证者应该比较两个文件。
支持外部数据的格式要么引用. sf 文件,要么使用隐式引用对其执行计算。
每个. sf 文件可以有多个数字签名,但是这些签名应该由同一法律实体生成。
文件扩展名可以是 1 到 3 个字母数字字符。未识别的扩展将被忽略。

六、Manifest 和签名文件的说明

下面是应用于清单和签名文件的附加限制和规则列表。
解析前:如果文件的最后一个字符是 EOF 字符 (代码 26),则 EOF 被视为空格。附加了两个新行 (一个用于编辑器,编辑器不会在最后一行的末尾放一个新行,另一个用于语法不必对最后一个条目进行特殊处理,因为它后面可能没有空行)。
属性:在所有情况下,对于所有部分,不理解的属性将被忽略;属性名称不区分大小写,然而,生成清单和签名文件的程序应该使用本规范中所示的情况;属性名不能在节中重复。
版本:Manifest-Version 和 Signature-Version 必须是第一个,除此之外,主节中属性的顺序并不重要。
顺序:单个清单条目的顺序不重要;单个签名条目的顺序并不重要,除非被签名的摘要按此顺序。
行长度:以 utf8 编码的形式,行长度不能超过 72 字节 (不是字符)。如果一个值使初始行比这个长,那么它应该在额外的行上继续 (每个行以一个空格开始)。
错误:如果不能根据此规范解析文件,则应该输出一个警告,并且所有签名都不可信。
限制:因为头名称不能换行,头名称的最大长度是 70 字节 (名称后面必须有冒号和空格);NUL、CR 和 LF 不能嵌入 header 值中,NUL、CR、LF 和":"不能嵌入 header 名称中;实现应该支持 65535 字节 (不是字符) 的头值,以及每个文件 65535 个头,它们可能会耗尽内存,但不应该在这些值以下硬编码限制。
签名者:不同的实体可以使用不同的签名算法来共享单个签名文件,这在技术上是可能的。但是这违反了标准,额外的签名可能会被忽略。
算法:本标准不强制或限制摘要或签名算法;必须支持至少一种摘要算法;如果摘要算法、签名算法或密钥大小受到 jdk.jar.disabledAlgorithms 安全属性的限制,JAR 将被视为未签名的。注意,jdk.jar.disabledAlgorithms 安全属性被 JDK 引用实现,它不能保证被其他实现检查和使用。

七、JAR 索引

1.概述
从 1.3 开始,引入 JarIndex 来优化网络应用程序,特别是 applet 的类加载器的类搜索过程。最初,applet 类加载器使用一个简单的线性搜索算法来搜索其内部搜索路径上的每个元素,内部搜索路径是由 “ARCHIVE” 标签或 “class - path” 主属性构造的。类加载器加载并在其搜索路径中打开每个元素,直到找到类或资源为止。如果类加载器试图找到一个不存在的资源,那么必须下载应用程序或 applet 中的所有 jar 文件。对于大型网络应用程
序和 applet,这可能导致启动缓慢、响应迟缓和浪费网络带宽。JarIndex 机制收集 applet 中定义的所有 jar 文件的内容,并将信息存储在索引文件中,该索引文件位于 applet 类路径的第一个 jar 文件中。加载第一个 jar 文件后,applet 类加载器将使用收集到的内容信息来高效地加载 jar 文件。

现有的 jar 工具被增强,使其能够检查 jar 文件列表并生成关于哪些类和资源驻留在哪些 jar 文件中的目录信息。这个目录信息存储在根 jar 文件的 META-INF 目录中的一个名为 INDEX.LIST 的简单文本文件中。当类加载器加载根 jar 文件时,它读取 INDEX.LIST 文件,并使用它构造哈希表,哈希表是从文件和包名称到 jar 文件名列表的映射。为了找到类或资源,类装入器查询散列表以查找适当的 jar 文件,然后在必要时加载它。

2.索引文件规范
INDEX.LIST 文件包含一个或多个节,每节由一个空行分隔。每节定义一个特定 jar 文件的内容,头文件定义 jar 文件路径名,后面是一个包或文件名列表,每行一个。所有 jar 文件路径都相对于根 jar 文件的代码基。这些路径名的解析方式与当前扩展机制对绑定扩展的解析方式相同。
UTF-8 编码用于支持索引文件中的文件或包名称中的非 ASCII 字符。
具体规范如下:

index file :            version-info blankline section*
version-info :          JarIndex-Version: version-number
version-number :        digit+{.digit+}*
section :               body blankline
body :                  header name*
header :                char+.jar newline
name :                  char+ newline
char :                  any valid Unicode character except NULL, CR andLF
blankline:              newline newline
newline :               CR LF | LF | CR (not followed by LF)
digit:                  {0-9}

INDEX.LIST 文件是运行 jar -i 生成的。有关更多细节,请参阅 jar 手册。

3.向后兼容
新的类加载方案完全向后兼容在当前扩展机制之上开发的应用程序。当类加载器加载第一个 jar 文件,并在 META-INF 目录中找到 INDEX.LIST 文件,它将构造索引哈希表并为扩展使用新的加载方案,否则,类加载器将只使用原始的线性搜索算法。

八、服务提供者

1.概述
在 META-INF/services 目录中的文件是服务提供者配置文件。服务是一组众所周知的接口和 (通常是抽象的) 类。服务提供者是服务的特定实现。提供程序中的类通常实现在服务本身中定义的接口和类的子类。服务提供者可以以扩展的形式安装在 Java 平台的实现中,也就是说,将 jar 文件放在任何通常的扩展目录中。提供程序还可以通过将它们添加到 applet 或应用程序类路径,或者通过其他特定于平台的方法来提供。

服务由抽象类表示。给定服务的提供程序包含一个或多个具体类,这些类使用数据和具体的代码逻辑扩展此服务类。这个 provider 类通常不是整个 provider 本身,而是一个代理,它包含足够的信息来决定 provider 是否能够满足特定的请求,以及可以根据需要创建实际 provider 的代码。提供者类的细节往往是高度订制服务的;没有一个类或接口可以统一它们,因此没有定义这样的类。这里强制执行的唯一要求是,提供程序类必须有一个零参数的构造函数,以便在查找期间可以实例化它们。

2.提供者配置文件
服务提供者通过在资源目录 META-INF/services 中放置一个提供者配置文件来标识自己。文件的名称应该由抽象服务类的完全限定名组成。该文件应该包含一个以换行符分隔的惟一具体提供程序类名称列表。空格和制表符以及空白行都将被忽略。注释字符是'#' (0x23);在每行中,第一个注释字符之后的所有字符都将被忽略。该文件必须用 UTF-8 编码。

3.例子
假设我们有一个名为 java.io. si . charcodec 的服务类。它有两种抽象的方法:

public abstract CharEncoder getEncoder(String encodingName);
public abstract CharDecoder getDecoder(String encodingName);

每个方法都返回一个适当的对象,如果它不能转换给定的编码,则返回 null。典型的 CharCodec 提供程序将支持多种编码。如果 sun.io.StandardCodec 是 CharCodec 服务的提供者,那么它的 jar 文件将包含文件 META-INF/services/java.io.spi.CharCodec。该文件将包含一行:

sun.io.StandardCodec    # Standard codecs for the platform

为一个给定的编码名称定位编码器,内部的 I/O 代码会做这样的事情:

CharEncoder getEncoder(String encodingName) {
       Iterator ps = Service.providers(CharCodec.class);
       while (ps.hasNext()) {
           CharCodec cc = (CharCodec)ps.next();
           CharEncoder ce = cc.getEncoder(encodingName);
           if (ce != null)
               return ce;
       }
       return null;
   }

提供程序查找机制总是在调用者的安全上下文中执行。受信任的系统代码通常应该从权限安全上下文中调用该类中的方法。

九、Class-Path 属性

应用程序的清单可以指定一个或多个相对 url,引用它需要的其他库的 JAR 文件和目录。这些相对 url 被当作相对于加载应用程序的代码基进行处理。

应用程序 (或者更常见的是 JAR 文件) 通过 manifest 属性 Class-Path 指定它需要的库的相对 url。如果在宿主 Java 虚拟机上找不到其他库的实现,系统将根据此属性搜索它们的 url。这些相对 url 可能包括应用程序所需的任何库或资源的 JAR 文件和目录。不以斜杠 (/) 结尾的相对 url 被认为引用 JAR 文件。例如:

Class-Path: servlet.jar infobus.jar acme/beans.jar images/

在 JAR 文件的清单中最多只能指定一个类路径头。目前,出于安全考虑,url 必须相对于 JAR 文件的代码库。因此,远程可选包将源自与应用程序相同的代码库。

每个相对 URL 都根据加载包含应用程序或库的代码库解析。如果结果 URL 无效或引用了无法找到的资源,则将忽略它。

生成的 URL 用于扩展应用程序、applet 或 servlet 的类路径,方法是在类路径中紧跟着包含 JAR 文件的 URL 插入 URL。省略任何重复的 url。例如,给定以下类路径:
a.jar b.jar
假设 b.jar 包含以下类路径清单属性:
Class-Path: x.jar a.jar
结果是应用程序类路径如下:
a.jar b.jar x.jar
如果 x.jar 有自己的依赖项,那么将根据后续每个 URL 的相同规则添加这些依赖项。在实际的实现中,JAR 文件依赖项被延迟处理,因此 JAR 文件直到需要时才打开。

十、包封装

JAR 文件和包可以选择密封,以便包可以在版本中强制一致性。封装在 JAR 中的包指定在该包中定义的所有类必须来自同一 JAR,否则,抛出 SecurityException。密封的 JAR 指定由该 JAR 定义的所有包都是密封的,除非为特定的包重写。密封的包是通过 manifest 属性 sealed 来指定的,它的值是 true 或者 false(大小写无关)。例如:

Name: javax/servlet/internal/
Sealed: true

这指定了 javax.servlet.internal 包是密封的,并且包中的所有类都必须从同一个 JAR 文件加载。
如果缺少这个属性,那么包密封属性就是包含 JAR 文件的属性。
密封的 JAR 是通过相同的清单头密封指定的,其值同样为 true 或 false。例如:

Sealed: true

这指定此归档中的所有包都是密封的,除非为清单项中具有 sealed 属性的特定包显式重写。
如果缺少这个属性,为了向后兼容,假设 JAR 文件不是密封的。然后,系统默认检查包头的密封信息。
包密封对于安全性也很重要,因为它将对受包保护的成员的访问限制为仅对来自同一 JAR 文件的包中定义的类的访问。
未命名的包是不可密封的,因此要密封的类必须放在它们自己的包中。

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
暂无回复。
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册