黄子涵

4.8 JAR文件

在将应用程序打包时,你一定希望只向用户提供一个单独的文件,而不是一个包含大量类文件的目录结构,Java归档(JAR)文件就是为此目的而设计的。一个JAR文件既可以包含类文件,也可以包含诸如图像和声音等其他类型的文件。此外,JAR文件是压缩的,它使用了我们熟悉的ZIP压缩格式。

创建JAR文件

可以使用jar工具制作JAR文件(在默认的JDK安装中,这个工具位于jdk/bin目录下)。创建一个新JAR文件最常用的命令使用以下语法:

jar cvf jarFileName file1 file2 . . .

例如:

jar cvf Calculatorclasses.jar *.class icon.gif

通常,jar命令的格式如下:

jar options file1 file2 . . .

jar程序选项

表(jar程序选项)列出了jar程序的所有选项。它们类似于UNIX tar命令的选项。

可以将应用程序和代码库打包在JAR文件中。例如,如果想在一个Java程序中发送邮件,就可以使用打包在文件javax.mail.jar中的一个库。

选项 说明
c 创建一个新的或者空的存档文件并加入文件。如果指定的文件名是目录,jar程序将会对它们进行递归处理
C 临时改变目录,例如:jar cvf jarFileName.jar -C classes *.class切换到classes子目录以便增加类文件
e 在清单文件中创建一个入口点
f 指定JAR文件名作为第二个命令行参数。如果没有这个参数,jar命令会将结果写至标准输出(在创建JAR文件时)或者从标准输入读取(在解压或者列出JAR文件内容时)
i 建立索引文件(用于加快大型归档中的查找)
m 将一个清单文件添加到JAR文件中。清单是对归档内容和来源的一个说明。每个归档有一个默认的清单文件。但是,如果想验证归档文件的内容,可以提供自己的清单文件
M 不为条目创建清单文件
t 显示内容表
u 更新一个已有的JAR文件
V 生成详细的输出结果
X 解压文件。如果提供一个或多个文件名,只解压这些文件;否则,解压所有文件
0 存储,但不进行ZIP压缩

清单文件

除了类文件、图像和其他资源外,每个JAR文件还包含一个清单文件(manifest),用于描述归档文件的特殊特性。

清单文件被命名为MANIFEST.MF,它位于JAR文件的一个特殊的META-INF子目录中。符合 标准的最小清单文件极其简单:

Manifest-Version: 1.0

复杂的清单文件可能包含更多条目。这些清单条目被分成多个节。第一节被称为主节(main section)。它作用于整个JAR文件。随后的条目用来指定命名实体的属性,如单个文件、包或者URL。它们都必须以一个Name条目开始。节与节之间用空行分开。例如:

Manifest-Version: 1.0
lines describing this archive

Name: Woozle.class
lines describing this file
Name: com/mycompany/mypkg/
lines describing this package

要想编辑清单文件,需要将希望添加到清单文件中的行放到文本文件中,然后运行:

jar cfm jarFileName manifestFileName . . .

例如,要创建一个包含清单文件的JAR文件,应该运行:

jar cfm MyArchive.jar manifest.mf com/mycompany/mypkg/*.class

要想更新一个已有的JAR文件的清单,则需要将增加的部分放置到一个文本文件中,然后执行以下命令:

jar ufm MyArchive.jar manifest-additions.mf

注释

请参看https://docs.oracle.com/javase/10/docs/specs/jar/jar.html获得有关JAR文件和清单文件格式的更多信息。

可执行JAR文件

可以使用jar命令中的e选项指定程序的入口点,即通常需要在调用java程序启动器时指定的类:

jar cvfe MyProgram.jar com.mycompany.mypkg.MainAppClass files to add

或者,可以在清单文件中指定程序的主类,包括以下形式的语句:

Main-Class: com.mycompany.mypkg.MainAppClass

不要为主类名增加扩展名.class

警告

清单文件的最后一行必须以换行符结束。否则,清单文件将无法被正确地读取。 常见的一个错误是创建了一个只包含Main-Class行而没有行结束符的文本文件。

不论使用哪一种方法,用户可以简单地通过下面的命令来启动程序:

java -jar MyProgram.jar

取决于操作系统的配置,用户甚至可以通过双击JAR文件图标来启动应用程序。下面是各种操作系统的操作方式:

  • 在Windows平台中,Java运行时安装程序将为”.jar”扩展名创建一个文件关联,会用javaw -jar命令启动文件(与java命令不同,javaw命令不打开shell窗口)。
  • 在Mac OS X平台中,操作系统能够识别".jar"扩展名文件。双击JAR文件时就会执 行Java程序。

不过,人们对JAR文件中的Java程序与原生应用还是感觉不同。在Windows平台 中,可以使用第三方的包装器工具将JAR文件转换成Windows可执行文件。包装器是一个Windows程序,有大家熟悉的扩展.exe,它可以查找和加载Java虚拟机(JVM),或者在没有找到JVM时会告诉用户应该做些什么。有许多商业的和开源的产品,例如,Launch4J (http://launch4j.soureforge.net)和 IzPack
http://izpack.org)。

多版本JAR文件

随着模块和包强封装的引入,之前可以访问的一些内部API不再可用。例如,JavaFX 8有一个内部类com.sun.javafx.css.CssParser。如果用它解析一个样式表,你会发现你的程序不再能正常编译了。补救很简单,只需要改用Java 9提供的javafx.css.CssParser。不过这样会有一个问题。你需要向Java 8和Java 9用户发布不同的应用程序,或者需要利用类加载和反射等一些技巧。

为了解决类似这样的问题,Java 9引入了多版本JAR(multi-release JAR),其中可以包含面向不同Java版本的类文件。

为了保证向后兼容,额外的类文件放在META-INF/versions目录中:

Application.class
BuildingBlocks.class
Util.class
META-INF
  |__ MANIFEST.MF (with line Multi-Release: true)
  |__ versions
  |__ 9
  |  |__ Application.class
  |  |__ BuildingBlocks.class
  |__ 10
       |__ BuildingBlocks.class

假设Application类使用了CssParser类。那么遗留版本的Application.class文件可以使用com.sun.javafx.css.CssParser,而Java 9
版本可以使用javafx.css.CssParser

Java 8完全不知道META-INF/versions目录,它只会加载遗留的类。Java 9读取这个JAR文件时,则会使用新版本。

要增加不同版本的类文件,可以使用--release标志:

jar uf MyProgram.jar --release 9 Application.class

要从头构建一个多版本JAR文件,可以使用-C选项,对应每个版本要切换到一个不同的类文件目录:

jar cf MyProgram.jar -C bin/8 . --release 9 -C bin/9 Application.class

面向不同版本编译时,要使用--release标志和-d标志来指定输出目录:

javac -d bin/8 --release 8 . . .

在Java 9中,-d选项会创建这个目录(如果原先该目录不存在)。

--release标志也是Java 9新增的。在较早的版本中,需要使用-source-target-boot-classpath标志。JDK现在为之前的两个API版本提供了符号文件。在Java 9中,编译时可以将--release设置为9、8或7。

多版本JAR并不适用于不同版本的程序或库。对于不同的版本,所有类的公共API都应当是一样的。多版本JAR的唯一目的是支持你的某个特定版本的程序或库能够在多个不同的JDK版本上运行。如果你增加了功能或者改变了一个API,那就应当提供一个新版本的JAR。

注释

javap之类的工具并没有改造为可以处理多版本JAR文件。如果调用:

javap -classpath MyProgram.jar Application.class

你会得到类的基本版本(毕竟,它与更新的版本应该有相同的公共API)。如果必须查看更新的版本,可以调用:

javap -classpath MyProgram.jar\!/META-INF/versions/9/Application.class

关于命令行选项的说明

Java开发包(JDK)的命令行选项一直以来都使用单个短横线加多字母选项名的形式,如:

java -jar . . .
javac -Xlint:unchecked -classpath . . .

jar命令是个例外,这个命令遵循经典的tar命令选项格式,而没有短横线:

jar cvf . . .

从Java 9开始,Java工具开始转向一种更常用的选项格式,多字母选项名前面加两个短横线,另外对于常用的选项可以使用单字母快捷方式。例如,调用Linux Is命令时可以提供 一个 “ human-readable ” 选项:
ls命令时可以提供一个“human-readable”选项:

ls --human-readable

或者

ls -h

在Java 9中,可以使用--version而不是-version,另外可以使用--class-path而不是-classpath

详细内容可以参见JEP 293增强请求(http://openjdk.java.net/jeps/293)。在所有清理工作中,作者还提出要标准化选项参数。带--和多字母的选项的参数用空格或者一个等号(=)分隔:

javac --class-path /home/user/classdir . . .

javac --class-path=/home/user/classdir . . .

单字母选项的参数可以用空格分隔,或者直接跟在选项后面:

javac -p moduledir . . .

javac -pmoduledir . . .

警告

后一种方式现在不能使用,而且一般来讲这也不是一个好主意。如果模块目录 恰好是arametersrocessor,这就很容易与遗留的选项发生冲突,这又何必呢?

无参数的单字母选项可以组合在一起:

jar -cvf MyProgram.jar -e mypackage.MyProgram */*.class

警告

目前不能使用这种方式。这肯定会带来混淆。假设javac有一个-c选项。那么javac -cp是指javac -c-p还是-cp

这就会带来一些混乱,希望过段时间能够解决这个问题。尽管我们想要远离这些古老的jar选项,但最好还是等到尘埃落定为妙。不过,如果你想做到最现代化,那么可以安全地使用jar命令的长选项:

jar --create --verbose --file jarFileName file1 file2 . . .

对于单字母选项,如果不组合,也是可以使用的:

jar -c -v -f jarFileName file1 file2 . . .
posted @ 2021-08-24 11:49  黄子涵  阅读(121)  评论(0编辑  收藏  举报