铁文整理
10.1 JAR文件
在将应用程序进行打包时,使用者一定希望仅提供给其一个单独的文件,而不是一个含有大量类文件的目录,JAR文件就是为此目的而设计的。一个JAR文件既可以包含类文件,也可以包含诸如图像和声音这些其他类型的文件。此外,JAR文件是压缩的,它使用了大家熟悉的ZIP压缩格式。
提示:Java SE 5.0提供了—种新的压缩方案,被称为pack200,这是一种较通常的ZIP压缩算法更加有效的压缩类文件的方式。Sun声称,对类文件的压缩率接近90%。有关更加详细的信息请参看网站http://java.sun.com/javase/6/docs/technotes/guide/deployment/deployment-guide/pack200.html。
可以使用JAR工具制作JAR文件(在默认的JDK安装中,位于jdk/bin目录下)。创建一个新的JAR文件应该使用的常见命令格式为:
jar cvf JARFileName File1 File2 ...
例如:
jar cvf CalculatorClasses.jar *.class icon.gif
通常,jar命令的格式如下:
jar options Fiie1 File2 ...
表10-1列出了所有jar程序的可选项。它们类似于UNIX tar命令的选项。
表10-1 jar程序选项
选项 | 说明 |
c | 创建一个新的或者空的存档文件并加入文件。如果指定的文件名是目录,jar程序将会对它们进行递归处理。 |
C | 暂时改变目录,例如:jar cvf JARFfileName.jar -C classes **class改变classes子目录,以便增加这些类文件 |
e | 在清单中创建一个条目(请参看可执行的JAR文件) |
f | 将JAR文件名指定为第二个命令行参数。如果没有这个参数,jar命令会将结果写到标准输出上(在创建JAR文件时)或者从标准输入中读取它(在解压或者列出JAR文件内容时) |
i | 建立索引文件(用于加快对大型归档的査找) |
m | 将一个清单文件添加到jar文件中。清单是对存档内容和来源的说明。每个归档有一个默认的清单文件,但是,如果想验证归档文件的内容,可以提供自己的清单文件 |
M | 不为条目创建清单文件 |
t | 显示内容表 |
u | 更新一个已有的JAR文件 |
v | 生成详细的输出结果 |
x | 解压文件。如果提供一个或多个文件名,只解压这些文件,否则,解压所有文件 |
0 | 存储,不进行ZIP压缩 |
10.1.1 清单文件
除了类文件、图像和其他资源外,每个JAR文件还包含一个用于描述归挡特征的清单文件。
清单文件被命名为MANIFEST.MF,它位于JAR文件的一个特殊META-INFO子目录中。最小的符合标准的清单文件是很简单的:
Manifest-Version: 1.0
复杂的清单文件可能包含更多条目。这些清单条目被分成多个节。第一节被称为主节(main section)。它作用于整个JAR文件。随后的条目用来指定已命名条目的属性,这些已命名的条目可以是某个文件、包或者URL。它们都必须起始于名为Name的条目。节与节之间用空行分开。例如:
Manifest-Version: 1.0
描述这个归档文件的行
Name: Woozle.class
描述这个文件的行
Name: com/mycompany/mypkg/
描述这个包的行
要想编辑清单文件,需要将希望添加到清单文件中的行放到文本文件中,然后运行:
jar cfm JARFileName ManifestFileName ...
例如,要创建一个包含清单的JAR文件,应该运行:
jar cfm MyArchive.jar manifest.mf com/mycompany/mypkg/*.class
要想更新一个已有的JAR文件的清单,则需要将增加的部分放置到一个文本文件中,然后执行下列命令:
jar ufm MyArchive.jar manifest-additions.mf
注释:请参看http://java.sun.com/javase/6/docs/technotes/guides获得有关JAR文件和清单文件格式的更多信息。
10.1.2 可运行JAR文件
在Java SE 6中,可以使用jar命令中的e选项指定程序的条目点,即通常需要在调用Java程序加载器时指定的类:
jar cvfe MyProgram.jar com.mycompany.mypkg.MainAppClass files to add
用户可以简单地通过下面命令来启动应用程序:
java -jar MyProgram.jar
在旧的JDK版本中,必须指定应用程序的主类,下面是这个处理命令:
Main-Class: com.mycompary.mypkg.MainAppClass
不要将扩展名.class添加到主类名中。然后运行jar命令:
jar cvfm MyProgram.jar mainclass.mf files to add
警告:清单文件的最后一行必须以换行符结束,否则,清单文件将无法被正确地读取。常见的错误是创建了一个只包含Main-Class而没有行结束符的文本文件。
根据操作系统的配置,可以通过双击JAR文件图标来启动应用程序。下面是各种操作系统的操作方式:
-
在Windows平台中,Java运行时安装器将建立一个扩展名为.jar的文件与javaw -jar命令相关联来启动文件(与java命令不同,javaw命令不打开shell窗口)。
-
在Solaris平台中,操作系统能够识别JAR文件的“魔数”格式,并用java -jar命令启动它。
-
在Mac OS平台中,操作系统能够识别.jar扩展名文件。当双击JAR文件时就会执行,Java程序可以运行。
无论怎样,人们对JAR文件中的Java程序与本地文件有着不同的感觉。在Windows平台中,可以使用第三方的打包器工具将JAR文件转换成Windows可执行文件。打包器是一个大家熟知的扩展名为.exe的Windows程序,它可以査找和加载Java虚拟机,或者在没有找到JVM时告诉用户应该做些什么。有许多商业的和开放的原始产品,比如,JSmooth和Lauch4J。开放的原始安装生成器IzPack还包含了一个本地加载器。有关这个问题的更加详细的信息请参看网站http://www.javalobby.org/articles/java2exe。
在Macintosh平台中,这种情形处理起來会容易一写。应用程序打包工具MRJAppBuider可以将JAR文件转换成一个顶级的Mac程序,有关更加详细的信息请参看http://java.sun.com/developer/technicalArticles/JavaLP/JavaToMac3。
10.1.3 资源
在applet和应用程序中使用的类通常需要使用一些相关的数据文件,例如:
-
图像和声音文件。
-
带有消息字符串和按钮标签的文本文件。
-
二进制数据文件,例如,描述地图布局的文件。
在Java中,这些关联的文件被称为资源。
注释:在Windows中,术语“资源”有着更加特殊的含义。Windows资源也是由图像、按钮标签等组成,但是它们都附属于可执行文件,并通过标准的程序设计访问。相比之下,Java资源作为单独的文件存储,并不是作为类文件的一部分存储,对资源的访问和解释由每个类自己胜任。
例如,AboutPanel类显示了一条信息,如图10-1所示。
当然,在面板中的书名和版权年限将会在出版下一版图书时发生变化。为了易于追踪这个变化,希望将文本放置在一个文件中,而不是以字符串的形式硬写到代码中。
但是应该将about.txt这样的文件放在哪儿呢?显然,将它与其他程序文件一起放在JAR中是最方便的。
类加载器知道如何搜索类文件,直到在类路径、存档文件或Web服务器上找到为止。利用资源机制,对于非类文件也可以同样方便地进行操作。下面是必要的步骤:
-
获得具有资源的Class对象,例如,AboutPanel.class。
-
如果资源是一个图像或声音文件,那么就需要调用getResource(filename)获得作为URL的资源位置,然后利用getImage或getAudioClip方法进行读取。
-
与图像或声音文件不同,其他资源可以使用getResourceAsStream方法读取文件中的数据。
重点在于类加载器可以记住如何定位类,然后在同一位置査找关联的资源。
例如,要想利用about.gif图像文件制作图标,可以使用下列代码:
URL url = ResourceTest.class.getResource("about.gif");
Image img = Toolkit.getDefaultToolkit().getImage(url);
这段代码的含义是“在找到ResourceTest类的地方定位about.gif文件”。
要想读取about.txt文件,可以使用下列命令:
InputStream stream = ResourceTest.class.getResourceAsStream("about.txt");
Scanner in = new Scanner(stream);
除了可以将资源文件与类文件放在同一个目录中外,还可以将它放在子目录中。可以使用下面所示的层级资源名:
data/text/about.txt
这是一个相对的资源名,它会被解释为相对于加载这个资源的类所在的包。注意,必须使用“/”作为分隔符,而不要理睬存储资源文件的系统实际使用哪种目录分隔符。例如,在Windows文件系统中,资源加载器会自动地将“/”转换成“\”。
一个以“/”开头的资源名被称为绝对资源名,它的定位方式与类在包中的定位方式一样。
例如,资源“/corejava/title.txt”定位于corejava目录下(它可能是类路径的一个子目录,也可能位于JAR文件中,对applet来说在Web服务器上)。
文件的自动装载是利用资源加载特性完成的。没有标准的方法来解释资源文件的内容。每个程序必须拥有解释资源文件的方法。
另一个经常使用资源的地方是程序的国际化。与语言相关的字符串,如消息和用户界面标签都存放在资源文件中,每种语言对应一个文件。国际化AP将在卷II的第5章中进行讨论。这些API提供了组织和访问本地化文件的标准方法。
例10-1显示了这个程序的源代码。这个程序演示了资源加载。编译、创建JAR文件和执行这个程序的命令是:
javac ResourceTest.java
jar cvfm ResourceTest.jar ResourceTest.mf *.class *.gif *.txt
java -jar ResoorceTest.jar
将JAR文件移到另外一个不同的目录中,再运行它,以便确认程序是从JAR文件中,而不是从当前目录中读取的资源。
例10-1 ResourceTest.java
import java.awt.*;
import java.io.*;
import java.net.*;
import java.util.*;
import javax.swing.*;
/**
* @version 1.4 2007-04-30
* @author Cay Horstmann
*/
public class ResourceTest {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
ResourceTestFrame frame = new ResourceTestFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
});
}
}
/**
* A frame that loads image and text resources.
*/
class ResourceTestFrame extends JFrame {
public ResourceTestFrame() {
setTitle("ResourceTest");
setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
URL aboutURL = getClass().getResource("about.gif");
Image img = Toolkit.getDefaultToolkit().getImage(aboutURL);
setIconImage(img);
JTextArea textArea = new JTextArea();
InputStream stream = getClass().getResourceAsStream("about.txt");
Scanner in = new Scanner(stream);
while (in.hasNext())
textArea.append(in.nextLine() + "\n");
add(textArea);
}
public static final int DEFAULT_WIDTH = 300;
public static final int DEFAULT_HEIGHT = 300;
}
API:java.lang.Class 1.0
-
URL getResource(String name) 1.1
-
InputStream getResourceAsStream(String name) 1.1:找到与类位于同一位置的资源,返回一个可以加载资源的URL或者输入流。如果没有找到资源,则返回null,而且不会抛出异常或者发生I/O错误。
10.1.4 密封
在第4章曾经提到过,可以将Java包密封(seal)以保证不会有其他的类加入到其中。如果在代码中使用了包可见的类、方法和域,就可能希望密封包。如果不密封,其他类就有可能放在这个包中,进而访问包可见的特性。
例如,如果密封了com.mycompany.util包,就不能用下面的语句顶替密封包之外的类:
package com.mycompany.util;
要想密封一个包,需要将包中的所有类放到一个JAR文件中。在默认情况下,JAR文件中的包是没有密封的。可以在清单文件的主节中加入下面一行:
Sealed: true
来改变全局的默认设定。对于每个单独的包,可以通过在JAR文件的清单中增加—节,来指定是否想要密封这个包。例如:
Name: com/Mycompany/util/
Sealed: true
Name: com/mycompany/misc/
Sealed: false
要想密封一个包,需要创建一个包含清单指令的文本文件。然后用常规的方式运行jar命令:
jar cvfm MyArchive.jar manifest.mf files to add