介绍
JAR 文件是一种基于流行的 ZIP 文件格式的文件格式,用于将多个文件聚合为一个文件。 JAR 文件本质上是一个包含可选 META-INF 目录的 zip 文件。可以通过命令行 jar 工具或使用 Java 平台中的java.util.jar
API 创建 JAR 文件。 JAR 文件的名称没有限制,可以是特定平台上的任何合法文件名。
模块化 JAR 文件
模块化 JAR 文件是在顶级目录(或根)目录中具有模块描述符 module-info.class
的 JAR 文件。模块描述符是模块声明的二进制形式。 (注意 多版本 JAR 文件 部分进一步细化了模块化 JAR 文件的定义。)
部署在模块路径(而不是类路径)上的模块化 JAR 文件是一个 explicit 模块。依赖项和服务提供者在模块描述符中声明。如果模块化 JAR 文件部署在类路径上,那么它的行为就好像非模块化 JAR 文件一样。
部署在模块路径上的非模块化 JAR 文件是 automatic module 。如果 JAR 文件有一个主属性 Automatic-Module-Name
(请参阅 主要属性 ),那么该属性的值就是模块名称,否则模块名称是从 ModuleFinder.of(Path...)
中指定的 JAR 文件的名称派生的。
多版本 JAR 文件
多版本 JAR 文件允许单个 JAR 文件支持 Java 平台版本的多个主要版本。例如,一个多版本 JAR 文件可以同时依赖于 Java 8 和 Java 9 主要平台版本,其中一些类文件依赖于 Java 8 中的 API,而其他类文件依赖于 Java 9 中的 API。这使库和框架开发人员能够将 Java 平台版本的特定主要版本中 API 的使用与所有用户迁移到该主要版本的要求分离开来。库和框架开发人员可以逐步迁移到并支持新的 Java 功能,同时仍然支持旧功能。
多版本 JAR 文件由 main 属性标识:
Multi-Release: true
在 JAR清单 的主要部分中声明。
依赖于 Java 平台版本 9 或更高版本的类和资源文件可能位于 versioned directory 而不是顶级(或根)目录下。版本化目录位于 META-INF 目录 下,格式如下:
META-INF/versions/N
其中 N 是 Java 平台版本的主要版本号的字符串表示形式。具体N
必须符合规范:
N: | {1-9} {0-9} * |
---|
N
的值小于 9
的任何版本化目录都将被忽略,因为不符合上述规范的 N
的字符串表示形式也是如此。
多版本 JAR 中版本控制目录下的类文件,版本为 N
的类文件版本必须小于或等于与 Java 平台版本的第 N
主要版本关联的类文件版本。如果类文件的类是公共的或受保护的,则该类必须preside over具有相同完全限定名和访问修饰符的类,其类文件位于顶级目录下。通过逻辑扩展,这适用于版本低于 N
的版本化目录下的类文件的类(如果存在)。
如果多版本 JAR 文件部署在 Java 平台发布运行时的主要版本N
的类路径或模块路径(作为自动模块或显式多版本模块),则从该 JAR 文件加载类的类加载器将首先搜索对于 N
版本目录下的类文件,然后按降序排列的先前版本目录(如果存在),向下到 9
的较低主要版本界限,最后在顶级目录下。
由多版本 JAR 文件中的类导出的公共 API 在各个版本之间必须是exactly 相同的,因此至少为什么版本化目录下类文件的版本化公共类或受保护类必须主持顶级下类文件的类 -级目录。执行广泛的 API 验证检查既困难又昂贵,因为不需要 jar
工具等工具来执行广泛的验证,也不需要 Java 运行时来执行任何验证。此规范的未来版本可能会放宽完全相同的 API 约束以支持谨慎的演变。
META-INF
目录下的资源无法进行版本控制(例如服务配置)。
可以对多版本 JAR 文件进行签名。
Java 运行时的引导类加载器不支持多版本 JAR 文件。如果将多版本 JAR 文件附加到引导类路径(使用 -Xbootclasspath/a
选项),则将 JAR 视为普通 JAR 文件。
模块化多版本 JAR 文件
模块化多版本 JAR 文件是具有模块描述符 module-info.class
的多版本 JAR 文件,位于顶层目录(对于 模块化的 JAR 文件),或直接位于版本化目录中。
非导出包(未在模块描述符中声明为导出)中的公共类或受保护类不需要主持具有相同完全限定名称和访问修饰符的类,其类文件存在于顶级目录下。
模块描述符通常与任何其他类或资源文件没有区别。模块描述符可能出现在版本化区域下,但不出现在顶级目录下。这确保了,例如,只有 Java 8 版本的类可以出现在顶级目录下,而 Java 9 版本的类(包括,或者可能只包括模块描述符)可以出现在 9
版本目录下。
任何控制版本较低的模块描述符或顶层模块描述符的版本化模块描述符,例如 M
必须与 M
相同,但有两个例外:
- 主要版本描述符可以有不同的非
transitive
requires
java.*
和jdk.*
模块的子句;和 - 主持版本描述符可以有不同的
uses
子句,即使是在java.*
和jdk.*
模块之外定义的服务类型。
诸如 jar
工具之类的工具应该对版本化模块描述符执行此类验证,但不需要 Java 运行时来执行任何验证。
META-INF 目录
META-INF 目录中的以下文件/目录被 Java 平台识别和解释以配置应用程序、类加载器和服务:
MANIFEST.MF
用于定义包相关数据的清单文件。
INDEX.LIST
此文件由 jar 工具的新“-i"
选项生成,它包含应用程序中定义的包的位置信息。它是 JarIndex 实现的一部分,由类加载器用来加速它们的类加载过程。
x.SF
JAR 文件的签名文件。 'x' 代表基本文件名。
x.DSA
、x.RSA
或x.EC
与具有相同基本文件名的签名文件关联的签名块文件。该文件以 PKCS #7 结构存储相应签名文件的数字签名。
services/
该目录存储了部署在类路径上的 JAR 文件或作为自动模块部署在模块路径上的 JAR 文件的所有服务提供者配置文件。有关详细信息,请参阅 服务商发展 的规范。
versions/
此目录下包含 多版本 JAR 文件的版本化类和资源文件。
名称-值对和部分
在我们详细了解各个配置文件的内容之前,需要定义一些格式约定。在大多数情况下,清单文件和签名文件中包含的信息表示为受 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: | 空间 *otherchar newline *continuation |
continuation: | 空间*otherchar newline |
alphanum: | {A-Z } | {a-z } | {0-9 } |
headerchar: | alphanum | - | _ |
otherchar: | any UTF-8 character except NUL, CR and LF |
- 注意:为防止通过直接电子邮件发送的文件损坏,标题不会以四个字母“发件人”开头。
上述规范中定义的非终结符号将在以下规范中引用。
JAR清单
概述
JAR 文件清单包含一个主要部分,后面是各个 JAR 文件条目的部分列表,每个部分由换行符分隔。主要部分和各个部分都遵循上面指定的部分语法。他们每个人都有自己特定的限制和规则。
主要部分包含有关 JAR 文件本身以及应用程序的安全和配置信息。它还定义了适用于每个单独清单条目的主要属性。此部分中的任何属性的名称都不能等于“
Name
”。本节以空行结束。各个部分定义了此 JAR 文件中包含的包或文件的各种属性。并非 JAR 文件中的所有文件都需要在清单中作为条目列出,但必须列出所有要签名的文件。不得列出清单文件本身。每个部分必须以名称为“
Name
”的属性开头,并且该值必须是文件的相对路径,或者引用存档外部数据的绝对 URL。如果同一个文件条目有多个单独的部分,这些部分中的属性将被合并。如果某个属性在不同的部分有不同的值,最后一个被识别。
不理解的属性将被忽略。这些属性可能包括应用程序使用的特定于实现的信息。
清单规格:
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} |
在上面的规范中,可以出现在主要部分的属性被称为主要属性,而可以出现在单独部分的属性被称为每条目属性。某些属性可以同时出现在主要部分和各个部分中,在这种情况下,每个条目的属性值会重写指定条目的主要属性值。两种类型的属性定义如下。
主要属性
主要属性是清单的主要部分中存在的属性。他们分为以下不同的组:
- 一般主要属性
- Manifest-Version:定义清单文件版本。该值是合法的版本号,如上述规范中所述。
- 创建者:定义生成此清单文件的 java 实现的版本和供应商。此属性由
jar
工具生成。 - Signature-Version:定义jar文件的签名版本。该值应该是有效的 version-number 字符串。
- Class-Path:此属性的值指定此应用程序需要的库的相对 URL。 URL 由一个或多个空格分隔。应用程序类加载器使用此属性的值来构造其内部搜索路径。有关详细信息,请参阅 类路径属性 部分。
- Automatic-Module-Name:如果此 JAR 文件部署为模块路径上的自动模块,则定义模块名称。有关详细信息,请参阅
automatic modules
的规范。 - Multi-Release:此属性定义此 JAR 文件是否为 多版本 JAR 文件。如果值为 "true" ,忽略大小写,则 JAR 文件将由 Java 运行时和工具作为多版本 JAR 文件进行处理。否则,如果该值不是“true”,则忽略此属性。
- 为独立应用程序定义的属性:此属性由捆绑到可执行 jar 文件中的独立应用程序使用,Java 运行时可以通过运行“
java -jar x.jar
”直接调用这些文件。- Main-Class:该属性的值是启动器将在启动时加载的主应用程序类的类名。该值必须 not 将
.class
扩展名附加到类名。 - Launcher-Agent-Class:如果此属性存在,则其值为在调用应用程序主方法之前启动的java agent 的类名。此属性可用于将 java 代理打包在与应用程序相同的可执行 JAR 文件中的情况。代理类以
java.lang.instrument
包摘要中指定的两种形式之一定义公共静态方法名称agentmain
。附加属性(例如Can-Retransform-Classes
)可用于指示代理所需的功能。
- Main-Class:该属性的值是启动器将在启动时加载的主应用程序类的类名。该值必须 not 将
- 为 包版本控制和密封 信息定义的属性:这些属性的值适用于 JAR 文件中的所有包,但可以被每个条目的属性覆盖。
- Implementation-Title:该值是一个字符串,它定义了扩展实现的标题。
- Implementation-Version:该值是一个字符串,它定义了扩展实现的版本。
- Implementation-Vendor:该值是一个字符串,定义了维护扩展实现的组织。
- Specification-Title:该值是一个字符串,定义了扩展规范的标题。
- Specification-Version:该值是一个字符串,定义了扩展规范的版本。
- Specification-Vendor:该值是一个字符串,定义了维护扩展规范的组织。
- 密封:此属性定义此 JAR 文件是否密封。该值可以是“true”或“false”,忽略大小写。如果它被设置为“true”,那么 JAR 文件中的所有包都默认为密封的,除非它们被单独定义。另请参阅 包装密封 部分。
每个条目的属性
每个条目属性仅适用于与清单条目相关联的单个 JAR 文件条目。如果相同的属性也出现在主要部分,则每个条目属性的值将重写主要属性的值。例如,如果 JAR 文件 a.jar 具有以下清单内容:
Manifest-Version: 1.0
Created-By: 1.8 (Oracle Inc.)
Sealed: true
Name: foo/bar/
Sealed: false
这意味着归档在 a.jar 中的所有包都是密封的,除了包 foo.bar 不是。
每个条目的属性分为以下几组:
- 为文件内容定义的属性:
- 为包版本控制和密封信息定义的属性:这些是上面定义的同一组属性,作为定义扩展包版本控制和密封信息的主要属性。当用作每个条目的属性时,这些属性会重写主要属性,但仅适用于清单条目指定的单个文件。
- 为 beans 对象定义的属性:
- Java-Bean:定义特定的 jar 文件条目是否为 Java Beans 对象。该值应为“true”或“false”,忽略大小写。
- 为签名定义的属性:这些属性用于签名和验证目的。更多细节在这里。
- x-Digest-y:此属性的名称指定用于计算相应 jar 文件条目的摘要值的摘要算法的名称。该属性的值存储实际的摘要值。前缀“x”指定算法名称,可选的后缀“y”指示摘要值应针对哪种语言进行验证。
- Magic:这是一个可选属性,应用程序可以使用它来指示验证器应如何计算清单条目中包含的摘要值。此属性的值是一组逗号分隔的上下文特定字符串。详细说明在这里。
签名的 JAR 文件
概述
可以使用命令行 jarsigner 工具或直接通过 java.security
API 对 JAR 文件进行签名。如果 JAR 文件由 jarsigner 工具签名,则每个文件条目(包括 META-INF
目录中的非签名相关文件)都将被签名。签名相关文件为:
META-INF/MANIFEST.MF
META-INF/*.SF
META-INF/*.DSA
META-INF/*.RSA
META-INF/*.EC
META-INF/SIG-*
请注意,如果此类文件位于 META-INF
子目录中,则它们不被视为与签名相关。这些文件名的不区分大小写的版本是保留的,也不会被签名。
可以使用 java.security
API 对 JAR 文件的子集进行签名。已签名的 JAR 文件与原始 JAR 文件完全相同,只是其清单已更新并且在 META-INF
目录中添加了两个附加文件:签名文件和签名块文件。当不使用 jarsigner 时,签名程序必须同时构造签名文件和签名块文件。
对于在已签名 JAR 文件中签名的每个文件条目,只要它不存在于清单中,就会为其创建一个单独的清单条目。每个清单条目都列出一个或多个摘要属性和一个可选的 魔法属性 。
签名文件
每个签名者都由扩展名为 .SF
的签名文件表示。该文件的主要部分类似于清单文件。它由一个主要部分组成,其中包括签名者提供的信息,但不特定于任何特定的 jar 文件条目。除了 Signature-Version
和 Created-By
属性(参见 主要属性 ),主要部分还可以包括以下安全属性:
- x-Digest-Manifest-Main-Attributes(其中x是
java.security.MessageDigest
算法的标准名称):该属性的值是清单主要属性的摘要值。 - x-Digest-Manifest(其中 x 是
java.security.MessageDigest
算法的标准名称):该属性的值是整个清单的摘要值。
主要部分之后是单个条目的列表,这些条目的名称也必须出现在清单文件中。每个单独的条目必须至少包含清单文件中相应条目的摘要。
出现在清单文件中但未出现在签名文件中的路径或 URL 不用于计算。
签名验证
如果签名有效,则 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 文件验证失败。
存储在
x-Digest-Manifest
属性中的清单文件的摘要值可能不等于当前清单文件的摘要值的原因之一是它可能包含文件签名后新添加文件的部分。例如,假设在生成签名(以及签名文件)之后,一个或多个文件被添加到 JAR 文件中(使用 jar 工具)。如果 JAR 文件由不同的签名者再次签名,则清单文件会更改(jarsigner 工具会为新文件添加部分)并创建一个新的签名文件,但原始签名文件不会改变。如果生成签名时 JAR 文件中的所有文件自那时起都没有更改,则对原始签名的验证仍然被认为是成功的,如果签名文件的非标头部分中的摘要值就是这种情况等于清单文件中相应部分的摘要值。对于清单中的每个条目,根据“名称:”属性中引用的实际数据计算的摘要验证清单文件中的摘要值,该属性指定相对文件路径或 URL。如果任何摘要值不匹配,则 JAR 文件验证失败。
示例清单文件:
Manifest-Version: 1.0
Created-By: 1.8.0 (Oracle 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)
魔法属性
验证给定清单条目上的签名的另一个要求是验证者了解该条目的清单条目中的 Magic 密钥对值的一个或多个值。
Magic 属性是可选的,但要求解析器在验证条目的签名时了解条目的 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 查询的结果是嵌入在文档中的脚本,而不是文档本身,并且脚本是动态生成的。这两条信息指示如何计算哈希值以与清单的摘要值进行比较,从而比较有效的签名。
在第二个示例中,Magic 值表示检索到的文档可能已经针对特定语言进行了内容协商,并且要验证的摘要取决于检索到的文档所用的语言。
数字签名
数字签名是 .SF
签名文件的签名版本。这些是不打算由人类解释的二进制文件。
数字签名文件与 .SF 文件具有相同的文件名,但扩展名不同。扩展名因签名者私钥的算法而异。
.RSA
(PKCS7 签名,用于 RSA 或 RSASSA-PSS 密钥).DSA
(PKCS7 签名,用于 DSA 密钥).EC
(PKCS7 签名,用于 EC 或 EdDSA 密钥)
上面未列出的签名算法的数字签名文件必须位于 META-INF
目录中并具有前缀“SIG-
”。对应的签名文件(.SF
文件)也必须有相同的前缀。
对于那些不支持外部签名数据的格式,文件应包含 .SF
文件的签名副本。因此,一些数据可能会重复,验证者应该比较这两个文件。
支持外部数据的格式要么引用 .SF
文件,要么使用隐式引用对其执行计算。
每个 .SF
文件可能有多个数字签名,但这些签名应由同一法律实体生成。
文件扩展名可以是 1 到 3 个 alphanum 个字符。无法识别的扩展名将被忽略。
关于清单和签名文件的说明
以下是适用于清单和签名文件的附加限制和规则的列表。
- 属性:
- 在所有情况下,对于所有部分,不理解的属性将被忽略。
- 属性名称不区分大小写。然而,生成清单和签名文件的程序应该使用本规范中显示的案例。
- 属性名称不能在一个部分中重复。
- 版本:
- Manifest-Version 和 Signature-Version 必须是第一个,而且恰恰是那种情况(这样它们就可以很容易地被识别为魔术字符串)。除此之外,主要部分中属性的顺序并不重要。
- 订购:
- 各个清单条目的顺序并不重要。
- 各个签名条目的顺序并不重要,除非被签名的摘要按该顺序排列。
- 线长:
- 在 UTF8 编码形式下,任何行的长度都不能超过 72 个字节(不是字符)。如果一个值会使初始行比这更长,它应该在额外的行上继续(每行以一个空格开头)。
- 错误:
- 如果无法根据此规范解析文件,则应输出警告,并且不应信任任何签名。
- 限制:
- 由于header名称不能连续,所以header名称的最大长度为70字节(名称后必须有一个冒号和一个SPACE)。
- NUL、CR 和 LF 不能嵌入到标头值中,NUL、CR、LF 和“:”不能嵌入到标头名称中。
- 实现应支持 65535 字节(非字符)标头值,以及每个文件 65535 个标头。它们可能会耗尽内存,但不应有低于这些值的硬编码限制。
- 签名者:
- 从技术上讲,不同的实体可以使用不同的签名算法来共享一个签名文件。这违反了标准,额外的签名可能会被忽略。
- 算法:
- 该标准没有强制要求摘要算法或签名算法。但是,必须至少支持 SHA-256 和 SHA1 摘要算法中的一种。
JAR索引
概述
从1.3开始,引入了JarIndex来优化网络应用类加载器的类搜索过程,尤其是小程序。最初,applet 类加载器使用简单的线性搜索算法在其内部搜索路径上搜索每个元素,该搜索路径由“ARCHIVE”标记或“Class-Path”主属性构建。类加载器下载并打开其搜索路径中的每个元素,直到找到类或资源。如果类加载器试图找到不存在的资源,则必须下载应用程序或小程序中的所有 jar 文件。对于大型网络应用程序和小程序,这可能会导致启动缓慢、响应迟缓和网络带宽浪费。 JarIndex 机制收集一个applet 中定义的所有jar 文件的内容,并将信息存储在applet 类路径上第一个jar 文件的索引文件中。第一个jar文件下载完成后,applet类加载器将使用收集到的内容信息进行jar文件的高效下载。
现有的 jar
工具得到了增强,能够检查 jar 文件列表并生成关于哪些类和资源驻留在哪个 jar 文件中的目录信息。此目录信息存储在根 jar 文件的 META-INF
目录中名为 INDEX.LIST
的简单文本文件中。当类加载器加载根 jar 文件时,它会读取 INDEX.LIST
文件并使用它来构建从文件和包名称到 jar 文件名列表的映射哈希表。为了找到一个类或资源,类加载器查询哈希表以找到合适的 jar 文件,然后在必要时下载它。
一旦类加载器在特定的 jar 文件中找到了一个 INDEX.LIST
文件,它就会始终信任其中列出的信息。如果找到特定类的映射,但类加载器无法通过链接找到它,则会抛出未指定的错误或 RuntimeException。发生这种情况时,应用程序开发人员应该在扩展上重新运行 jar
工具,以便将正确的信息放入索引文件中。
为了防止给应用程序增加过多的空间开销并加快内存中哈希表的构建,INDEX.LIST 文件保持尽可能小。对于具有非空包名称的类,映射记录在包级别。通常一个包名映射到一个 jar 文件,但是如果一个特定的包跨越多个 jar 文件,那么这个包的映射值将是一个 jar 文件列表。对于非空目录前缀的资源文件,映射也记录在目录级别。只有包名为空的类和位于根目录中的资源文件才会在单个文件级别记录映射。
索引文件规格
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 and LF |
blankline: | newline newline |
newline: | CR LF | LF | CR (not followed by LF ) |
digit: | {0-9 } |
INDEX.LIST
文件是通过运行 jar -i.
生成的。有关详细信息,请参阅 jar 手册页。
向后兼容性
新的类加载方案完全向后兼容在当前扩展机制之上开发的应用程序。当类加载器加载第一个 jar 文件并在 META-INF
目录中找到一个 INDEX.LIST
文件时,它将构建索引哈希表并使用新的扩展加载方案。否则,类加载器将简单地使用原始的线性搜索算法。
类路径属性
应用程序的清单可以指定一个或多个引用 JAR 文件的相对 URL 和它需要的其他库的目录。这些相对 URL 将相对于包含应用程序从中加载的代码库(“context JAR”)进行处理。
应用程序(或者更一般地,JAR 文件)通过清单属性 Class-Path
指定它需要的库的相对 URL。如果在主机 Java 虚拟机上找不到其他库的实现,此属性会列出用于搜索其他库实现的 URL。这些相对 URL 可能包括应用程序所需的任何库或资源的 JAR 文件和目录。不以“/”结尾的相对 URL 被假定为引用 JAR 文件。例如,
Class-Path: servlet.jar infobus.jar acme/beans.jar images/
在 JAR 文件的清单中最多可以指定一个 Class-Path
标头。
如果满足以下条件,则 Class-Path
条目有效:
通过根据上下文 JAR 的 URL 解析它,它可以用于创建
URL
。它是相对的,而不是 absolute ,即它不包含方案组件,但从文件系统加载上下文 JAR 的情况除外,在这种情况下,出于兼容性原因,允许使用
file
方案。此条目表示的 JAR 文件或目录的位置包含在上下文 JAR 的包含目录中。不允许使用“
../
”导航到父目录,从文件系统加载上下文 JAR 的情况除外。
无效条目将被忽略。有效条目根据上下文 JAR 解析。如果生成的 URL 无效或引用无法找到的资源,则会将其忽略。重复的 URL 将被忽略。
生成的 URL 被插入到类路径中,紧跟在上下文 JAR 的 URL 之后。例如,给定以下类路径:
a.jar b.jar
如果 b.jar
包含以下 Class-Path
清单属性:
Class-Path: lib/x.jar a.jar
那么这样一个URLClassLoader
实例的有效搜索路径将是:
a.jar b.jar lib/x.jar
当然,如果 x.jar
有自己的依赖项,那么这些依赖项将根据相同的规则添加,对于每个后续 URL 依此类推。在实际实现中,JAR 文件依赖项被延迟处理,以便 JAR 文件在需要时才真正打开。
包装密封
JAR 文件和包可以是可选的 sealed ,这样包就可以在一个版本中强制执行一致性。
密封在 JAR 中的包指定该包中定义的所有类必须来自同一个 JAR。否则,将抛出 SecurityException
。
密封的 JAR 指定由该 JAR 定义的所有包都是密封的,除非专门为包重写。
密封包通过清单属性 Sealed
指定,其值为 true
或 false
(不区分大小写)。例如,
Name: javax/servlet/internal/
Sealed: true
指定 javax.servlet.internal
包是密封的,并且该包中的所有类都必须从同一个 JAR 文件加载。
如果缺少此属性,则包密封属性是包含 JAR 文件的密封属性。
密封的 JAR 通过相同的清单标头 Sealed
指定,其值再次为 true
或 false
。例如,
Sealed: true
指定此存档中的所有包都是密封的,除非在清单条目中使用 Sealed
属性明确覆盖特定包。
如果缺少此属性,则假定 JAR 文件not 是密封的,以实现向后兼容性。然后系统默认检查包头以获取密封信息。
包密封对于安全性也很重要,因为它将对受包保护的成员的访问限制为仅限于包中定义的那些源自同一 JAR 文件的类。
未命名的包是不可密封的,因此要密封的类必须放在它们自己的包中。