什么是JAR文件?
JAR文件格式基于流行的ZIP文件格式,并且用于将许多文件聚合为一个文件。 与ZIP文件不同,JAR文件不仅用于归档和分发,还用于库,组件和插件的部署和封装,并且直接由编译器和JVM等工具使用。 JAR中包含的特殊文件(如清单和部署描述符)指导工具如何处理特定的JAR。
可以使用JAR文件:
- 用于分发和使用类库
- 作为应用程序和扩展的构建块
- 作为组件,小程序或插件的部署单元
- 用于打包与组件关联的辅助资源
JAR文件格式具有许多优点和功能,而传统的存档格式(例如ZIP或TAR)没有提供其中的许多优点和功能。 这些包括:
- 安全。 您可以对JAR文件的内容进行数字签名。 然后,识别您的签名的工具可以选择授予您的软件安全特权,而该特权本来是不会的,并可以检测代码是否遭到篡改。
- 减少下载时间。 如果将小程序捆绑在JAR文件中,则浏览器可以在单个HTTP事务中下载小程序的类文件和相关资源,而不必为每个文件打开新的连接。
- 压缩。 JAR格式允许您压缩文件以进行有效存储。
- 透明的平台扩展。 Java Extensions Framework提供了一种可以向Java核心平台添加功能的方法,该平台使用JAR文件打包扩展。 (Java 3D和JavaMail是Sun开发的扩展的示例。)
- 包装密封。 可以选择将JAR文件中存储的软件包密封起来,以增强版本的一致性和安全性。 密封软件包意味着必须在同一JAR文件中找到该软件包中定义的所有类。
- 软件包版本控制。 JAR文件可以保存有关其包含的文件的数据,例如供应商和版本信息。
- 可移植性。 处理JAR文件的机制是Java平台核心API的标准部分。
压缩和未压缩的JAR
jar
工具(有关详细信息,请参见jar工具 )默认情况下会压缩文件。 通常,未压缩的JAR文件的加载速度比压缩的JAR文件更快,这是因为消除了在加载过程中对文件进行解压缩的需要,但是对于未压缩的文件,通过网络下载的时间可能会更长。
META-INF目录
大多数JAR文件都包含一个META-INF目录,该目录用于存储程序包和扩展配置数据,例如安全性和版本信息。 Java 2平台可以识别和解释META-INF目录中的以下文件或目录,以配置应用程序,扩展和类加载器:
- 清单文件 清单文件定义了与扩展和软件包有关的数据。
- INDEX.LIST。 该文件由
jar
工具的新-i
选项生成,并且包含应用程序或扩展中定义的软件包的位置信息。 它是JarIndex实现的一部分,由类加载器用来加速类加载过程。 - xxx .SF。 这是JAR文件的签名文件。 占位符xxx标识签名者。
- xxx .DSA。 与签名文件关联的签名块文件存储用于签名JAR文件的公共签名。
jar
工具
要使用JAR文件执行基本任务,请使用Java开发工具包中随附的Java存档工具( jar
工具)。 您可以使用jar
命令调用jar
工具。 表1显示了一些常见的应用程序:
表1. jar
工具的常用用法
功能 | 命令 |
从单个文件创建一个JAR文件 | jar cf jar文件输入文件... |
从目录创建JAR文件 | jar cf jar文件目录名 |
创建未压缩的JAR文件 | jar cf0 jar文件目录名 |
更新一个JAR文件 | jar uf jar文件输入文件... |
查看JAR文件的内容 | jar tf jar文件 |
提取JAR文件的内容 | jar xf jar文件 |
从JAR文件中提取特定文件 | jar xf jar文件存档文件... |
运行打包为可执行JAR文件的应用程序 | java -jar app.jar |
可执行JAR
可执行jar文件是存储在经过特殊配置的JAR文件中的自包含Java应用程序,可以由JVM直接执行,而无需首先提取文件或设置类路径。 要运行存储在不可执行的JAR中的应用程序,必须将其添加到类路径中,并按名称调用该应用程序的主类。 但是,通过使用可执行的JAR文件,我们可以运行应用程序而无需将其解压缩或知道主入口点。 可执行JAR有助于Java应用程序的轻松分发和执行。
创建可执行的JAR
创建可执行的JAR很容易。 首先将所有应用程序代码放在一个目录中。 假设您应用程序中的主类是com.mycompany.myapp.Sample
。 您想要创建一个包含应用程序代码并标识主类的JAR文件。 为此,请在某处(不在您的应用程序目录中)创建一个名为manifest
的文件,并在其中添加以下行:
Main-Class: com.mycompany.myapp.Sample
然后,像这样创建JAR文件:
jar cmf manifest ExecutableJar.jar application-dir
一切就足够了-现在可以使用java -jar
执行JAR文件ExecutableJar.jar。
可执行JAR必须通过清单文件的Class-Path
标头引用其所需的所有其他依赖JAR。 如果使用-jar
选项,则JVM将忽略环境变量CLASSPATH和命令行上指定的任何类路径。
启动可执行JAR
现在,我们已经将应用程序打包到一个名为ExecutableJar.jar的可执行JAR中,我们可以使用以下命令从文件直接启动该应用程序:
java -jar ExecutableJar.jar
包装封口
将程序包密封在JAR文件中意味着必须在同一JAR文件中找到该程序包中定义的所有类。 这允许包作者在打包的类之间强制版本一致性。 密封还提供了一种检测代码篡改的安全措施。
要密封软件包,请为该软件包添加一个Name
标头,然后在JAR清单文件中添加一个值为“ true”的Sealed
标头。 与可执行JAR一样,可以在创建JAR时通过使用适当的标头元素指定清单文件来密封JAR,如下所示:
Name: com/samplePackage/
Sealed: true
Name
标头标识软件包的相对路径名。 它以“ /”结尾,以区别于文件名。 Name
标头之后的所有标头(中间无空白行)适用于Name
标头中指定的文件或包。 在上面的示例中,由于Sealed
标头出现在Name
标头之后,中间没有空白行,因此Sealed
标头将被解释为仅适用于com/samplePackage
包。
如果您尝试从密封包所在的JAR文件之外的其他来源加载密封包中的类,则JVM将抛出SecurityException
。
扩展包装
扩展将功能添加到Java平台,并且JAR文件格式内置了扩展机制。 扩展机制允许JAR文件通过清单文件中的Class-Path
标头指定其他所需的JAR文件。
假设extension1.jar和extension2.jar是同一目录中的两个JAR文件,其中extension1.jar的清单包含以下标头:
Class-Path: extension2.jar
该头文件指示extension2.jar中的类用作扩展类,以用于extension1.jar中的类。 extension1.jar中的类可以调用extension2.jar中的类,而无需extension2.jar成为类路径的一部分。
当加载使用扩展机制的JAR时,JVM会有效地自动将Class-Path
头中引用的JAR添加到类路径中。 但是,扩展JAR路径被解释为相对路径,因此通常,扩展JAR必须与引用它的JAR存储在同一目录中。
例如,假设类ExtensionClient
,它引用类ExtensionDemo
,被称为ExtensionClient.jar一个JAR文件中捆绑,并且该类ExtensionDemo
在ExtensionDemo.jar捆绑在一起。 为了将ExtensionDemo.jar视为扩展,必须在ExtensionClient.jar清单的Class-Path
标头中列出ExtensionDemo.jar,如下所示:
Manifest-Version: 1.0
Class-Path: ExtensionDemo.jar
此清单中的Class-Path
标头的值为ExtensionDemo.jar(未指定路径),指示ExtensionDemo.jar与ExtensionClient JAR文件位于同一目录中。
JAR文件中的安全性
可以使用jarsigner
工具或直接通过java.security
API对JAR文件进行签名。 已签名的JAR文件与原始JAR文件完全相同,除了已更新其清单,并将两个附加文件添加到META-INF目录中,分别是签名文件和签名阻止文件。
使用存储在密钥库数据库中的证书对JAR文件进行签名。 密钥库中存储的证书受密码保护,必须将其提供给jarsigner
工具才能对JAR文件进行签名。
图1.密钥库数据库
JAR的每个签名者都由签名文件表示,该文件在JAR文件的META-INF目录中具有扩展名.SF。 该文件的格式类似于清单文件-一组RFC-822标头。 如下所示,它由一个主要部分组成,该部分包括签名者提供的但并非特定于任何特定JAR文件条目的信息,其后是清单文件中也必须存在的各个条目的列表。 为了验证来自签名的JAR的文件,将签名文件中的摘要值与针对JAR文件中相应条目计算的摘要进行比较。
清单1.已签名的JAR中的清单文件和签名文件
Contents of signature file META-INF/MANIFEST.MF
Manifest-Version: 1.0
Created-By: 1.3.0 (Sun Microsystems Inc.)
Name: Sample.java
SHA1-Digest: 3+DdYW8INICtyG8ZarHlFxX0W6g=
Name: Sample.class
SHA1-Digest: YJ5yQHBZBJ3SsTNcHJFqUkfWEmI=
Contents of signature file META-INF/JAMES.SF
Signature-Version: 1.0
SHA1-Digest-Manifest: HBstZOJBuuTJ6QMIdB90T8sjaOM=
Created-By: 1.3.0 (Sun Microsystems Inc.)
Name: Sample.java
SHA1-Digest: qipMDrkurQcKwnyIlI3Jtrnia8Q=
Name: Sample.class
SHA1-Digest: pT2DYby8QXPcCzv2NwpLxd8p4G4=
数字签名
数字签名是.SF
签名文件的签名版本。 数字签名文件是二进制文件,与.SF
文件具有相同的文件名,但具有不同的扩展名。 扩展名取决于数字签名的类型(RSA,DSA或PGP)以及用于签名JAR的证书的类型。
密钥库
要签名JAR文件,首先必须具有一个私钥。 私钥及其关联的公钥证书存储在称为keystores
的受密码保护的数据库中。 JDK包含用于创建和修改密钥库的工具。 密钥库中的每个密钥都可以由别名标识,别名通常是拥有密钥的签名者的名字。
使用唯一的别名访问所有密钥库条目(密钥和可信证书条目)。 使用keytool -genkey
命令将实体添加到密钥库中以生成密钥对(公钥和私钥)时,将指定别名。 后续的keytool
命令必须使用相同的别名来引用实体。
例如,要生成别名为“ james”的新公钥/私钥对并将公钥包装为自签名证书,则可以使用以下命令:
keytool -genkey -alias james -keypass jamespass
-validity 80 -keystore jamesKeyStore
-storepass jamesKeyStorePass
此命令序列指定后续命令访问密钥库“ jamesKeyStore”中与别名“ james”相关联的私钥所需的初始密码“ jamespass”。 如果密钥库“ jamesKeyStore”不存在, keytool
将自动创建它。
jarsigner工具
jarsigner
工具使用密钥库来生成或验证JAR文件的数字签名。
假设您已经按照上面的示例创建了密钥库“ jamesKeyStore”,并且其中包含一个别名为“ james”的密钥,则可以使用以下命令对JAR文件进行签名:
jarsigner -keystore jamesKeyStore -storepass jamesKeyStorePass
-keypass jamespass -signedjar SSample.jar Sample.jar james
该命令从名为“ jamesKeyStore”的密钥库中使用密码“ jamesKeyStorePass”获取别名为“ james”且密码为“ jamespass”的密钥,并对Sample.jar文件进行签名,从而创建一个签名的JAR SSample.jar。
jarsigner
工具还可以验证已签名的JAR文件。 此操作比对JAR文件进行签名要简单得多。 只需执行以下命令:
jarsigner -verify SSample.jar
如果未篡改已签名的JAR文件,则jarsigner
工具将告知您JAR已通过验证; 否则,它将抛出SecurityException
指示无法验证哪些文件。
还可以使用java.util.jar
和java.security
API以编程方式对JAR进行签名。 另外,您可以使用诸如Netscape Object Signing Tool之类的工具。
您还可以使用java.util.jar
和java.security
API以编程方式对JAR进行签名。 (请参阅相关主题的详细信息)。 或者,您可以使用诸如Netscape对象签名工具之类的工具。
JAR索引
如果将应用程序或小程序捆绑到多个JAR文件中,则类加载器将使用简单的线性搜索算法来搜索类路径的每个元素,这可能需要类加载器下载并打开许多JAR文件,直到找到类或资源为止。 如果类加载器尝试查找不存在的资源,则必须下载应用程序或小程序中的所有JAR文件。 对于大型网络应用程序和小程序,这可能会导致启动缓慢,响应缓慢以及网络带宽浪费。
从JDK 1.3开始,JAR文件格式已支持索引,以优化在网络应用程序(尤其是小应用程序)中搜索类的过程。 JarIndex机制收集在applet或应用程序中定义的所有JAR文件的内容,并将该信息存储在第一个JAR文件的索引文件中。 在下载第一个JAR文件之后,小应用程序类加载器将使用收集的内容信息来有效下载JAR文件。 此目录信息存储在根JAR文件的META-INF目录中名为INDEX.LIST的简单文本文件中。
创建一个JarIndex
您可以通过在jar
命令中指定-i
选项来创建JarIndex。 假设我们有一个目录结构,如下图所示:
图2. JarIndex
您将使用以下命令为JarIndex_Main.jar,JarIndex_test.jar和JarIndex_test1.jar创建索引文件:
jar -i JarIndex_Main.jar JarIndex_test.jar SampleDir/JarIndex_test1.jar
INDEX.LIST文件具有简单的格式,其中包含每个索引的JAR文件中包含的包或类的名称,如清单2所示:
清单2. JarIndex INDEX.LIST文件示例
JarIndex-Version: 1.0
JarIndex_Main.jar
sp
JarIndex_test.jar
Sample
SampleDir/JarIndex_test1.jar
org
org/apache
org/apache/xerces
org/apache/xerces/framework
org/apache/xerces/framework/xml4j
摘要
JAR格式不仅仅是存档格式。 它具有许多功能,可以提高Java应用程序的效率,安全性和组织性。 由于这些功能内置于核心平台(包括编译器和类加载器)中,因此开发人员可以利用JAR文件格式的功能来简化和改进其开发和部署过程。