Java核心技术卷阅读随笔--第13章【Java应用程序】
Java应用程序
到目前为止,我们已经能够熟练地使用 Java 程序语言的大部分特性,并且对 Java 图形 编程的基本知识也有所了解。现在准备创建提交给用户的应用程序, 至此需要知道如何将这 些应用程序进行打包, 以便部署到用户的计算机上。传统的部署方式是使用 applet, 这应该 归功于在 Java 出现的最初几年中对其给予的大肆吹捧。applet 是一种特殊的 Java 程序, 它允 许通过网络下载,并可以在浏览器中运行。其目的在于让用户不再为安装软件烦恼,并且可 以通过支持 Java 的计算机或者其他具有 Internet 连接的设备使用这些软件。
由于种种原因,applet 并没有实现上述目标。因此,本章首先介绍打包应用的指令。然后展示应用如何存储配置信息和用户首选项。另外还会学习如何使用 ServiceLoader 类在应用 中加载插件。
接下来, 我们再来讨论 applet, 介绍创建或维护 applet 时需要了解的有关知识。我们还会讨论 Java Start 机制 这是一种基于 Internet 的应用发布方法,很多方面都与 applet 很类似,不过更适合不在 Web 页面中的程序。
13.1 jar文件
在将应用程序进行打包时,使用者一定希望仅提供给其一个单独的文件,而不是一个含 有大量类文件的目录,Java 归档(JAR) 文件就是为此目的而设计的。一个 JAR 文件既可以 包含类文件,也可以包含诸如图像和声音这些其他类型的文件。此外, JAR 文件是压缩的, 它使用了大家熟悉的 ZIP 压缩格式。
提示:pack200 是一种较通常的 ZIP 压缩算法更加有效的压缩类文件的方式。Oracle 声 称, 对类文件的压缩率接近 90%。有关更加详细的信息请参看网站 http://doc.oracle.com/ javase/1.5.0/docs/guide/deployment/deployment-guide/pack200.html。
13.1.1 创建JAR 文件
可以使用 jar 工具制作 JAR 文件(在默认的 JDK 安装中,位于 jdk/bin 目录下)。创建一 个新的 JAR 文件应该使用的常见命令格式为:
jar cvf JARFileName File1 File2... 例如: jar cvf CalculatorClass.jar *.class icon.gif
通常,jar命令的格式如下:
jar options File1 File2 ...
表 13- 1 列出了所有 jar 程序的可选项。它们类似于 UNIX tar 命令的选项。
可以将应用程序、 程序组件 (有时称为“ beans”— 参见卷 II 第 11 章) 以及代码库打 包在 JAR 文件中。例如, JDK 的运行时库包含在一个非常庞大的文件 rt.jar 中。
13.1.2 清单文件
除了类文件、 图像和其他资源外, 每个 JAR 文件还包含一个用于描述归档特征的清单文 件 (manifest) 。
清单文件被命名为 MANIFEST.MF , 它位于 JAR 文件的一个特殊 META-INF 子目录中。 最小的符合标准的清单文件是很简单的:
Manifest-Version: 1.0
复杂的清单文件可能包含更多条目。这些清单条目被分成多个节。第一节被称为主节 ( main section ) 。 它作用于整个 JAR 文件。随后的条目用来指定已命名条目的属性,这些已命 名的条目可以是某个文件、 包或者 URL。 它们都必须起始于名为 Name 的条目。节与节之间用空行分开。例如:
Manifest-Version: 1.0 描述这个归档文件的行 Name: Woozle.class 描述这个文件的行 Name: cora/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://docs.orade.com/javase/8/docs/technotes/guides/jar 获得有关 JAR 文件 和清单文件格式的更多信息。
13.1.3 可执行JAR文件
可以使用jar 命令中的 e 选项指定程序的人口点, 即通常需要在调用java 程序加载器时 指定的类
jar cvfe MyProgram.jar com.mycompany.mypkg.MainAppClass files to add 或者, 可以在清单中指定应用程序的主类,包括以下形式的语句: Main-C1ass: com.mycompany.mypkg.MainAppClass
不要将扩展名 .class 添加到主类名中.
警告: 清单文件的最后一行必须以换行符结束。 否则, 清单文件将无法被正确地读取。 常见的错误是创建了一个只包含 Main-Class 而没有行结束符的文本文件。
不论哪一种方法,用户可以简单地通过下面命令来启动应用程序:
java -jar MyProgram.jar
根据操作系统的配置, 用户甚至可以通过双击 JAR 文件图标来启动应用程序。下面是各 种操作系统的操作方式:
• 在 Windows 平台中,Java 运行时安装器将建立一个扩展名为 jar 的文件与 javaw -jar 命令相关联来启动文件(与java 命令不同,javaw 命令不打开 shell 窗口)。
• 在 Solaris 平台中, 操作系统能够识别 JAR 文件的“ 魔法数” 格式,并用java -jar 命 令启动它。
• 在 Mac OS X 平台中,操作系统能够识别 .jar 扩展名文件。当双击 JAR 文件时就会执 行 Java 程序可以运行。
无论怎样,人们对 JAR 文件中的 Java 程序与本地文件有着不同的感觉。在 Windows 平台中, 可以使用第三方的包装器工具将 JAR 文件转换成 Windows 可执行文件。包装器是一 个大家熟知的扩展名为 .exe 的 Windows 程序,它可以查找和加载 Java 虚拟机(JVM) ,或者 在没有找到 JVM 时告诉用户应该做些什么。有许多商业的和开源的产品,例如, LaunCh4J ( http://launch4j.sourceforge.net) 和 IzPack (http://izpack.org)。
在 Macintosh 平台中,这种情形处理起来会容易一些。Jar Bundler 工具是 XCode 的一部 分,可以将 JAR 文件转换为一个一流的 Mac 应用。
13.1.4 资源
在 applet 和应用程序中使用的类通常需要使用一些相关的数据文件, 例如:
• 图像和声音文件。
• 带有消息字符串和按钮标签的文本文件。
• 二进制数据文件, 例如, 描述地图布局的文件。
在 Java 中,这些关联的文件被称为资源(resource)。
注释: 在 Windows 中, 术语“ 资源” 有着更加特殊的含义。Windows 资源也是由图像、 按钮标签等组成,但是它们都附属于可执行文件,并通过标准的程序设计访问。相比之 下,Java 资源作为单独的文件存储, 并不是作为类文件的一部分存储。对资源的访问和 解释由每个程序自己完成。
例如, AboutPanel 类显示了一条信息, 如图 13-1 所示。
当然,在面板中的书名和版权年限将会在出版下一版 图书时发生变化。为了易于追踪这个变化,希望将文本放 置在一个文件中,而不是以字符串的形式硬写到代码中。 但是应该将 about.txt 这样的文件放在哪儿呢? 显然, 将它与其他程序文件一起放在 JAR 文件中是最方便的。 类加载器知道如何搜索类文件,直到在类路径、 存档 文件或 web 服务器上找到为止。利用资源机制, 对于非类 文件也可以同样方便地进行操作。下面是必要的步骤:
1 ) 获得具有资源的 Class 对象,例如, AboutPanel.class。
2 ) 如果资源是一个图像或声音文件, 那么就需要调用 getresource (filename) 获得作为 URL 的资源位置,然后利用 getlmage 或 getAudioClip 方法进行读取。
3 ) 与图像或声音文件不同,其他资源可以使用 getResourceAsStream 方法读取文件中的 数据。
重点在于类加载器可以记住如何定位类,然后在同一位置査找关联的资源。
例如,要想利用 about.gif 图像文件制作图标,可以使用下列代码:
URL url = ResourceTest.class.getResource("about.gif"); Image img = new Imagelcon(url).getlmage():
这段代码的含义是“ 在找到 ResourceTest 类的地方查找 about.gif 文件”。 要想读取 about.txt 文件,可以使用下列命令:
InutStream stream = ResourceTest.class.getResourceAsStream("about.txt"); Scanner in = new Scanner(streara,"UTF-8"):
除了可以将资源文件与类文件放在同一个目录中外,还可以将它放在子目录中。可以使 用下面所示的层级资源名:
data/text/about.txt
这是一个相对的资源名,它会被解释为相对于加载这个资源的类所在的包。注意, 必须 使用“ /” 作为分隔符,而不要理睬存储资源文件的系统实际使用哪种目录分隔符。例如,在 Windows 文件系统中, 资源加载器会自动地将“ / ” 转换成"\"。
一个以“ / ” 开头的资源名被称为绝对资源名。它的定位方式与类在包中的定位方式一 样。例如,资源
/corejava/title.txt
定位于 corejava 目录下(它可能是类路径的一个子目录,也可能位于 JAR 文件中,对 applet 来说在 web 服务器上) 。
文件的自动装载是利用资源加载特性完成的。没有标准的方法来解释资源文件的内容。 每个程序必须拥有解释资源文件的方法。
另一个经常使用资源的地方是程序的国际化。 与语言相关的字符串, 如消息和用户界面 标签都存放在资源文件中,每种语言对应一个文件。 国际化 API ( internationalizationAPI) 将 在卷 II 的第 5 章中进行讨论。这些 API 提供了组织和访问本地化文件的标准方法。
程序清单 13-1 显示了这个程序的源代码(略过)。这个程序演示了资源加载。编译、创建 JAR 文件和执行这个程序的命令是
javac resource/ResourceTest.java jar cvfm ResourceTest.jar resource/ResourceTest.mf resource/*.class resource/*.gif resource/*.txt java -jar ResourceTest.jar
将 JAR 文件移到另外一个不同的目录中,再运行它, 以便确认程序是从 JAR 文件中而 不是从当前目录中读取的资源。
API java.lang.Class 1.0
• URL getResource(String name) 1.1
• InputStream getResourceAsStream(String name) 1.1
找到与类位于同一位置的资源, 返回一个可以加载资源的 URL 或者输入流。如果没 有找到资源, 则返回 null , 而且不会抛出异常或者发生 I/O 错误。
13.1.5 密封
在第 4 章曾经提到过, 可以将 Java 包密封 ( seal ) 以保证不会有其他的类加人到其中。
如果在代码中使用了包可见的类、方法和域,就可能希望密封包。如果不密封, 其他类就有 可能放在这个包中,进而访问包可见的特性。
例如, 如果密封了 com.mycompany.util 包, 就不能用下面的语句顶替密封包之外的类:
package com.mycompany.util ;
要想密封一个包,需要将包中的所有类放到一个 JAR 文件中。在默认情况下,JAR 文件 中的包是没有密封的。可以在清单文件的主节中加入下面一行:
Sealed: true
来改变全局的默认设定。对于每个单独的包,可以通过在 JAR 文件的清单中增加一节, 来指 定是否想要密封这个包。例如:
Name: com/mycoinpany/util/ Sealed: true Name: com/myconpany/misc/ Sealed: false
要想密封一个包,需要创建一个包含清单指令的文本文件。然后用常规的方式运行 jar 命令:
jar cvfm MyArchive.jar manifest.mf files to add
13.2 应用首选项的存储
应用用户通常希望能保存他们的首选项和定制信息, 以后再次启动应用时再恢复这些配 置。首先我们来介绍 Java 应用的传统做法, 这是一种简单的方法,将配置信息保存在属性文 件中。然后我们会介绍首选项 API, 它提供了一个更加健壮的解决方案。
13.2.1 属性映射
属性映射( property map) 是一种存储键 / 值对的数据结构。属性映射通常用来存储配置 信息,它有 3 个特性:
• 键和值是字符串。
• 映射可以很容易地存入文件以及从文件加载。
• 有一个二级表保存默认值。
实现属性映射的 Java 类名为 Properties。
属性映射对于指定程序的配置选项很有用。例如:
Properties settings = new Properties。; settings.setProperty("width", "200"); settings.setProperty("title", "Hello, World!");
可以使用 store方法将属性映射列表保存到一个文件中。在这里, 我们将属性映射保存 在文件 program.properties 中。第二个参数是包含在这个文件中的注释。
OutputStream out = new FileOutputStream("program.properties"); settings.store(out, "Program Properties");
这个示例会给出以下输出:
#Program Properties #Mon Apr 30 07:22:52 2007 width=200 title=Hello, World!
要从文件加载属性,可以使用以下调用:
InputStrean in = new FileinputStream("prograni.properties"); settings.load(in);
习惯上, 会把程序属性存储在用户主目录的一个子目录中。目录名通常以一个点号开头 (在 UNIX 系统中) ,这个约定说明这是一个对用户隐藏的系统目录。我们的示例程序就遵循 这个约定。
要找出用户的主目录,可以调用 System.getProperties 方法,它恰好也使用一个 Properties 对象描述系统信息。主目录包含键 "usenhome"。 还有一个便利方法可以读取单个键:
String userDir = System.getProperty("user.home");
可以为程序属性提供默认值, 这是一个很好的想法,因为用户有可能手动编辑这个文 件。Properties 类有两种提供默认值的机制。第一种方法是, 查找一个字符串的值时可以指定 一个默认值,这样当键不存在时就会自动使用这个默认值。
String title = settings.getProperty("title", "Default title");
如果属性映射中有一个 "title" 属性, title 就会设置为相应的字符串。否则,title 会设置 为 "Default title"。
如果觉得在每个 getProperty 调用中指定默认值太过麻烦, 可以把所有默认值都放在一个 二级属性映射中,并在主属性映射的构造器中提供这个二级映射。
Properties defaultSettings = new Properties();
defaultSettings.setProperty("width", "300"); defaultSettings.setProperty("height", "200"); defaultSettings.setProperty("titie", "Default title");
... Properties settings = new Properties(defaultSettings);
没错, 如果为 defaultSettings 构造器提供另一个属性映射参数,甚至可以为默认值指定 默认值, 不过一般不会这么做。
程序清单 13-2 显示了如何使用属性来存储和加载程序状态。程序会记住框架位置、 大小 和标题。也可以手动编辑主目录中的文件 .corejava/program.properties 把程序的外观改成你希 望的样子。 略过
警告: 出于历史上的原因,Properties 类实现了 Map<Object,Object〉。因此, 可以使用Map 接口的get 和put 方法。不过,get 方法会返回类型 Object, 而 put 方法允许插入任何对象。最好坚持使用 getProperty 和 setProperty 方法,这些方法会处理字符串,而不是对象。
注释: 属性映射是没有层次结构的简单表。 通常会用类似 window.main.color、window, main.title 等键名引入一个伪层次结构 。不过 Properties 类没有提供方法来组织这样一个 层次结构。如果存储复杂的配置信息, 就应当使用 Preferences 类, 请看下一节。
API java.util.Properties 1.0 略过
API java.lang.System 1.0 略过
注释: 可以在 Java 运行时目录的 security/java.policy 文件中找到可以自由访问的系统属 性名。
13.2.2 首选项API
我们已经看到,利用 Properties 类可以很容易地加载和保存配置信息。不过,使用属性 文件有以下缺点:
• 有些操作系统没有主目录的概念, 所以很难找到一个统一的配置文件位置。
• 关于配置文件的命名没有标准约定, 用户安装多个 Java 应用时,就更容易发生命名冲突。
有些操作系统有一个存储配置信息的中心存储库。最著名的例子就是 Microsoft Windows 中的注册表。Preferences 类以一种平台无关的方式提供了这样一个中心存储库。在 Windows 中, Preferences 类使用注册表来存储信息;在 Linux 上, 信息则存储在本地文件系统中。当 然,存储库实现对使用 Preferences 类的程序员是透明的。
Preferences 存储库有一个树状结构, 节点路径名类似于 /com/mycompany/myapp。类似 于包名, 只要程序员用逆置的域名作为路径的开头, 就可以避免命名冲突。实际上, API 的 设计者就建议配置节点路径要与程序中的包名一致。
存储库的各个节点分别有一个单独的键 / 值对表,可以用来存储数值、字符串或字节数 组,但不能存储可串行化的对象。API 设计者认为对于长期存储来说, 串行化格式过于脆弱, 并不合适。当然,如果你不同意这种看法,也可以用字节数组保存串行化对象。
为了增加灵活性,可以有多个并行的树。每个程序用户分别有一棵树;另外还有一棵系 统树, 可以用于存放所有用户的公共信息。 Preferences 类使用操作系统的“ 当前用户” 概念 来访问适当的用户树。
若要访问树中的一个节点,需要从用户或系统根开始:
Preferences root = Preferences.userRoot(); 或 Preferences root = Preferences.systemRoot():
然后访问节点。可以直接提供一个节点路径名:
Preferences node = root.node("/com/mycompany/myapp"):
如果节点的路径名等于类的包名,还有一种便捷方式来获得这个节点。只需要得到这个类的一个对象,然后调用:
Preferences node = Preferences.userNodeForPackage (obj .getClass());
或
Preferences node = Preferences.systemNodeForPackage(obj .getClass()):
一般来说, Obj 往往是 this 引用。
一旦得到了节点,可以用以下方法访问键 / 值表:
String get(String key, String defval) int getInt(String key, int defval) long getLong(String key, long defval) float get FI oat(String key, float defval) double getDouble(String key, double defval) boolean getBoolean(String key, boolean defval) byte[] getByteArray(String key, byte[] defval)
需要说明的是, 读取信息时必须指定一个默认值, 以防止没有可用的存储库数据。 之所 以必须有默认值, 有很多原因。可能由于用户从未指定过首选项, 所以没有相应的数据。某 些资源受限的平台可能没有存储库, 移动设备有可能与存储库暂时断开了连接。
相对应地,可以用如下的 put 方法向存储库写数据:
put(String key, String value) putInt(String key, int value)
等等。 可以用以下方法枚举一个节点中存储的所有键:
String[] keys()
目前没有办法找出一个特定键对应的值的类型。 类似 Windows 注册表这样的中心存储库通常都存在两个问题:
• 它们会变成充斥着过期信息的“ 垃圾场”。
• 配置数据与存储库纠缠在一起, 以至于很难把首选项迁移到新平台。
Preferences 类为第二个问题提供了一个解决方案。可以通过调用以下方法导出一个子树 (或者比较少见的,也可以是一个节点)的首选项:
void exportSubtree(OutputStream out) void exportNode(OutputStream out)
数据用 XML 格式保存。可以通过调用以下方法将数据导人到另一个存储库:
void importPreferences(InputStreain in)
下面是一个示例文件: 略过
如果你的程序使用首选项, 要让用户有机会导出和导入首选项, 从而可以很容易地将设 置从一台计算机迁移到另一台计算机。程序清单 13-3 中的程序展示了这种技术。这个程序只 保存了主窗口的位置、 大小和标题。试着调整窗口的大小, 然后退出并重启应用。窗口的状 态与之前退出时是一样的。略过
API java.util.prefs.Preferences 1.4 略过
13.3 服务加载
有时会开发采用插件体系结构的应用。 有些平台支持这种方法, 如 OSGi (http://oSgi. org), 并用于开发环境、应用服务器和其他复杂的应用中。这些平台超出了本书讨论的范畴, 不过 JDK 还提供了一个加载插件的简单机制, 这里就来介绍这个内容。
通常, 提供一个插件时, 程序希望插件设计者能有一些自由来确定如何实现插件的特 性。另外还可以有多个实现以供选择。 利用 ServiceLoader 类可以很容易地加载符合一个公共接口的插件。
定义一个接口(或者, 如果愿意也可以定义一个超类), 其中包含服务的各个实例应当提 供的方法。例如,假设你的服务要提供加密。
package serviceLoader; public interface Cipher { byte[] encrypt(byte[] source, byte[] key) ; byte[] decrypt(byte[] source, byte[] key); int strength。; }
服务提供者可以提供一个或多个实现这个服务的类, 例如:略过
实现类可以放在任意包中, 而不一定是服务接口所在的包。每个实现类必须有一个无参 数构造器。
现在把这些类的类名增加到 META-INF/services 目录下的一个 UTF-8 编码文本文件中, 文件名必须与完全限定类名一致。在我们的例子中, 文件 META-INF/services/serviceLoader. Cipher 必须包含这样一行:
serviceLoader.impl .CaesarCipher
在这个例子中, 我们提供了一个实现类。还可以提供多个类, 以后可以从中选择。
完成这个准备工作之后, 程序可以如下初始化一个服务加载器:
public static ServiceLoader<Cipher> cipherLoader = ServiceLoade r.1oad(Cipher.class);
这个初始化工作只在程序中完成一次。 服务加载器的 iterator 方法会对服务提供的所有实现返冋一个迭代器。(有关迭代器的更 多信息参见第 9 章。)最容易的做法是使用一个增强的 for 循环进行遍历。在循环中, 选择一 个适当的对象来完成服务。
API java.util.ServiceLoader<S> 1.6 略过
13.4 applet
applet 是包含在 HTML 页面中的 Java 程序。HTML 页面必须告诉浏览器要加载哪些 applet, 另 外 每 个 applet 要放在 Web 页面的什么位置。 可以想见,需要使用 applet 的标记 ( tag) 必须告诉浏览器从哪里得到类文件, 以及这个 applet 在 Web 页面上如何定位(大小、 位置等)。然后浏览器再从 Internet (或者从用户机器上的某个目录)获取类文件, 并自动运行 applet。
最初开发 applet 时, 必须使用 Sun 的 HotJava 浏览器才能査看包含 applet 的 Web 页面。 很自然地, 很少有用户愿意只是为了尝试一个新的 Web 特性去使用另一个浏览器。后来 Netscape 在它的 Navigator 浏览器中包含了一个 Java 虚拟机,那时 Java applet 才开始流行。 Microsoft 的 Internet Explorer 紧随其后。遗憾的是, Internet Explorer 中的 Java 支持很快就落 伍了,只能用于一些过期的 Java 版本,后来干脆取消了。
为了解决这个问题,Sun Microsystems 开发了“ Java Plug-in”。通过使用浏览器扩展机制, 可以把插件插入不同的浏览器, 允许这些浏览器使用外部 Java 运行时环境执行 Java applet。 多年来,这个解决方案都能满足要求, applet 常用于教学工具、企业应用和一些游戏。 遗憾的是,尽管不时会发现 Java 虚拟机的安全漏洞并被恶意利用, 但 Sun Microsystems 和后来的 Oracle 在修补这些漏洞方面却动作迟缓, 举措不力。由于不安全的 JVM 会置用户于危 险当中,所以浏览器制造商让 Java 的使用更有难度。有些浏览器除了最新版本的 Java 插件 外甚至完全禁用其他 Java 插件,另外一些浏览器则不再支持插件体系结构。Oracle 的反应也很让人失望。它开始要求所有 applet 都有数字签名(有关内容见 13.4.9 节。)
如今,开发人员要部署 Java applet 会很困难, 用户运行 applet 也不容易。因此,我们相 信, 只有那些需要维护遗留 applet 的读者才会对下面的小节感兴趣。
注释: 要在浏览器中运行这一章中的 applet, 需要安装当前版本的 Java Plug-in, 并 确 保 浏览器与插件连接。要测试 applet, 还需要适当地配置插件, 让它信任本地文件。有关 指令参见 2.5 节。
13.4.1 一个简单的applet
按传统, 我们首先编写一个 NotHelloWorld 程序,这里把它写为一个 applet。applet 就是 一个扩展了 java.applet.Applet 类的 Java 类。在本书中,我们将使用 Swing 来实现 applet。这 里的所有 applet 都将扩展 JApplet 类,它是 Swing applet 的超类。如图 13-2 所示, JApplet 是 Applet 类的一个直接子类。
注释: 如果你的 applet 包含 Swing 组件, 必须扩展 JApplet 类。普通 Applet 中的 Swing 组件不能正确绘制。
程序清单 13- 4显示了 applet 版本“ Not Hello World” 的代码。注意这个代码与第 10 章中 的相应程序很类似。不过, 由于 applet 在 web 页面中, 所以没有必要指定退出 applet 的方法。略过
要执行 applet , 需要完成以下 3 个步骤: 1 ) 将 Java 源文件编译为类文件。 2 ) 将类打包到一个 JAR 文件中(参见 13.1.1 节。) 3 ) 创建一个 HTML 文件,告诉浏览器首先加载哪个类文件, 以及如何设定 applet 的 大小
下面给出这个文件的内容:
<applet dass="applet/NotHel1olilorld.class" archi ve="NotHel1oWorld.jar" width="300"
height="300">
</applet>
在浏览器中査看 applet 之前, 最好先在 JDK 自带的 applet viewer ( applet 查看器)程序中 进行测试。要使用 applet 查看器测试我们的示例 applet, 可以在命令行输入:
appletviewer NotHelloWorldApplet.html
applet 查看器程序的命令行参数是 HTML 文件 名,而不是类文件。图 13-3 给出了显示这个 applet 的 applet 查看器。
applet 查看器很适用于测试的第一阶段, 不 过总会有某个时候需要在真正的浏览器中运行 applet , 来查看用户实际看到的样子。具体来说, applet 査看器程序只会显示 applet , 而 不 会 显 示 周 围 的 HTML 文本。 如果你的 HTML 文件包含 多个 applet 标记, applet 查 看 器 就 会 弹 出 多 个 窗口。
要正确地査看 applet, 只 需 要 把 HTML 文件加 载到浏览器(如图 13M 所示)。如果 applet 没有出 现, 则需要安装 JavaPlug-in,并允许它加载无签名的本地 applet,参见 2.5 节的介绍。
提示: 如果修改了 applet 并重新编译, 就需要重新启动浏览器, 这样才会加载新的类文 件。只是刷新 HTML 页面并不会加载新代码。 调试 applet 时这会很麻烦。可以利用 Java 控制台避免烦人的浏览器重启。启动 Java 控制台, 并执行 x 命令, 这会清空类加栽器缓 存。然后可以重新加载 HTML 页面, 这样就会使用新的 applet 代码。在 Windows 上, 可 以在 Windows 控制面板打开 Java Plug-in 控制。在 Linux 上, 可以运行 jcontrol, 请求显 示 Java 控制台。加栽 applet 时控制台就会弹出。
很容易把一个图形化 Java 应用转换为可以嵌入在 Web 页面中的 applet。基本上来说, 所 有用户界面代码都可以保持不变。
下面给出具体的步骤: 1 ) 建立一个HTML 页面, 其中包含加载 applet 代码的适当标记。 2 ) 提供 JApplet 类的一个子类。将这个类标记为 public。否则 applet 将无法加载。 3 ) 删去应用中的 main 方法。不要为应用构造框架窗口。你的应用将在浏览器中显示。 4 ) 把所有初始化代码从框架窗口移至 applet 的 init 方法。不需要明确构造 applet 对象, 浏览器会实例化 applet 对象并调用 init 方法。 5 ) 删除 setSize 调用;对 applet 来说, 用 HTML 文件中的 width 和 height 参数就可以指 定大小。 6 ) 删除 setDefaultCloseOperation 调用。applet 不能关闭;浏览器退出时 applet 就会终止 运行。 7 ) 如果应用调用 setTitle, 则删除这个方法调用。applet 没有标题栏。(当然,可以用 HTML title 标记为 Web 页面本身指定标题。) 8 ) 不要调用 setVisible(true)。applet 会自动显示。
API java.applet.Applet 1.0 略过
13.4.2 applet HTML 标记和属性
下面是一个最简形式的 applet 标记示例:
<applet code="applet/NotHelloWorld.class" archive="NotHelloWorld. jar"
width="300" height="100"x/applet>
可以在 applet 标记中使用以下属性:
希望同一个页面上的两个 applet 相互直接通信时 name 属性也很重要。为每个当 前 applet 实例指定一个名字,将这个字符串传递到 AppletContext 接口的 getApplet 方 法。本章后面还会讨论这种 applet 间通信 ( inter-applet communication ) 机制。
注释: 在 www.javaworld.com/javatips/jw-javatip80.html 中, Francis Lu 使用 JavaScript-Java 通信解决了一个年代久远的问题: 如何调整 applet 的大小, 而不限于只能通过硬编 码 width 和 height 属性来指定。这是 Java 与 JavaScript 集成的一个很好的例子。
13.4.3 使用参数向 applet 传递信息
与应用可以使用命令行信息一样,applet 可以使用内嵌在 HTML 文件中的参数。这是利 用 HTML param 标记以及所定义的属性来完成的。例如,假设想让 Web 页面确定 applet 中使 用的字体样式。可以使用以下 HTML 标记:
<applet code="FontParamApplet.class" ...> <param name="font" value="Helvetica"/> </applet>
然后使用 Applet 类的 getParameter 方法得到参数的值:
public class FontParamApplet extends JApplet { public void init(){ String fontName = getPatameter("font"); ... } ... }
注释: 只能在 applet 的 init 方法中调用 getParameter 方法, 而不能在构造器中调用。执行 applet 构造器时, 参数还没有准备好。 由于大多数重要 applet 的布局都由参数确定, 所 以建议不要为 applet 提供构造器,要把所有初始化代码放在 init 方法中。
参数总是作为字符串返回。 如果需要数值类型, 则需要将字符串转换为数值。可以调用 适当的方法采用标准方式进行转换, 如 Integer 类的 parselnt。 例如, 如果想为字体增加一个 size 参数, HTML 代码可能如下所示
<applet code="PontParamApplet.class" ...> <param name="font" value="Helvetica"> <param name="size" value="247> </applet>
下面的源代码显示了如何读取这个整数参数:
public class FontParamApplet extends JApplet { public void init() { String fontName = getParameter("font"); int fontSize = Integer.parseInt(getParameter("size")); ... } }
注释: param 标记中的 name 属性值与 getParameter 方法的参数匹配时, 会使用不区分大 小写的比较。
除了要确保代码中的参数匹配之外, 还要检 查是否缺少 size 参数。可以简单地测试是否为 null 来达到目的。例如:
int fontsize; String sizeString = getParameter("size"); if (sizeString = null) fontSize = 12; else fontSize = Integer.parseint(sizeString):
下面给出一个经典的 applet , 它会使用参数绘 制一个直方图,如图 13-5 所示。 这个 applet 从 HTML 文件中的 param 值得到 直方柱的标签和高度。 下面给出图 13-5 相应的 HTML 文件:略过
也可以在 applet 中建立一个字符串数组和一个数值数组, 不过使用参数机制有两个好处。Web 页面上的 N —个 applet 可以有多个副本, 分别显示不同的图表: 只需要在页面上放置两个 applet 标记, 分别用一组不同的参数。 另外, 可以改变要绘制图表的数据。必须承 认, 行星直径在相当长的时间内都不会改变, 不过假设你的 Web 页面包含每周销量数据的图 表呢? 由于使用纯文本, 所以更新 Web 页面很容易。而每周编辑和重新编译一个 Java 文件 就要麻烦多了。
实际上, 有很多商业 JavaBeans 组件 ( beans) 可以提供比这个 applet 更精美的图表。 如 果购买这样一个 bean, 可以把它放在你的 Web 页面中, 为它提供参数, 而无须知道这个 applet 是如何呈现图表的。
程序清单 13-5 是这个图表 applet 的源代码。 需要说明, init 方法读取了参数, 并由 paintComponent 方法绘制图表。略过
API java.applet.Applet 1.0 略过
13.4.4 访问图像和音频文件
applet 可以处理图像和音频。 写作这本书时, 图像必须是 GIF、 PNG 或 JPEG 格式,音 频文件必须是 AU、AIFF、 WAV 或 MIDI。另外也支持动画 GIF , 可以显示动画。
要用相对 URL 指定图像和音频文件的位置。通常可以通过调用 getDocumentBase 或 getCodeBase 方法得到基 URL , 前一个方法会得到包含这个 applet 的 HTML 页面的 URL, 后 者会得到 applet 的 codebase 属性指定的 URL。
可以为 getlmage 或 getAudioClip 方法提供基 URL 和文件位置。例如:
Image cat = getlmage(getDocumentBase(), "images/cat.gif");
AudioGip meow = getAudioClip(getDocumentBase(), "audio/meow.au");
第 10 章中已经见过如何显示一个图像。要播放一段音频, 只需要调用它的 play 方法。 还可以调用 Applet 类的 play 方法而无须先加载这段音频。
play(getDocumentBase(), "audio/meow.au");
API java.applet.Applet 1.0 略过
13.4.5 applet 上下文
applet 在浏览器或 applet 查看器中运行。applet 可以要求浏览器为它做些事情, 例如,获 取一个音频剪辑,在状态栏中显示一个简短的消息,或者显示一个不同的 Web 页面。浏览器 可能会执行这些请求,也可能将其忽略。例如,如果一个在 applet 查看器中运行的 applet 要 求 applet 査看器程序显示一个 Web 页面, 那么什么也不会发生。
要与浏览器通信,applet 可以调用 getAppletContext 方法。这个方法会返回一个实现 AppletContext 接口的对象可以认为 AppletContext 接口的具体实现是 applet 与外围浏览器 之间的一个通信渠道。除了 getAudioClip 和 getlmage 之外,AppletContext 接口还包含很多有 用的方法,将在接下来的几节中讨论。
13.4.6 applet 间通信
一个 Web 页面可以包含多个 applet。如果一个 Web 页面包含来自同一个 codebase 的多 个 applet,它们可以相互通信。很自然地, 这是一项高级技术,你可能很少会用到。
如果为 HTML 文件中的各个 applet 指定 name 属性,可以使用 AppletContext 接口 的 getApplet 方法来得到这个 applet 的一个引用。 例如, 如果 HTML 文件中包含以下 标记:
<app1et code="Chart.class" width="100" height="100" name="Chart1">
则以下调用
Applet chartl = getAppletContext().getApplet("Chartl");
会提供这个 applet 的一个引用。如何使用这个引用呢? 假设 Chart 类中有一个方法可以接受 新数据并重绘图表,可以通过适当的类型转换来调用这个方法:
((Chart) chartl).setData(3, "Earth", 9000);
还可以列出一个 Web 页面上的所有 applet, 不论它们是否有 name 属性。getApplets 方法 会返回一个枚举对象。 下面给出一个循环,它会打印当前页面上所有 applet 的类名:略过
applet 不能与不同 Web 页面上的其他 applet 通信。
13.4.7 在浏览器中显示信息项
可以访问外围浏览器的两个区域:状态栏和 Web 页面显示区, 这都要使用 AppletContext 接口的方法。
可以用 showStatus 方法在浏览器底部的状态栏中显示一个字符串。例如:
showStatus("Loading data . . . please wait");
提示:从我们的经验来看, showStatus 的作用有限。浏览器也会经常使用状态栏, 它会 用类似“ Applet running” 之类的话菝盖原先的消息。 可以使用状态栏显示不太重要的消 息,如 “ Loading data . . . please wait”, 而不能显示用户不能遗漏的消息。
可以用 showDocument 方法告诉浏览器显示一个不同的 Web 页面。有很多方法可以达到 这个目的。最简单的办法是调用 showDocument 并提供一个参数, 即你想要显示的 URL:
URL u = new URL("http://horstmann.com/index.html "):
getAppletContext().showDocument(u):
这个调用的问题在于,它会在当前页面所在的同一个窗口中打开新 Web 页面,因此会替 换你的 applet。要返回原来的 applet, 用户必须点击浏览器的后退按钮。
可以在 showDocument 调用中提供第二个参数告诉浏览器在另一个窗口中显示文档(见 表 13-2) 。如果提供了特殊字符串 "_blank",浏览器会用这个文档打开一个新窗口,而不是 替换当前文档。更重要的是, 如果利用 HTML 中的框架特性,可以把一个浏览器窗口分割为 多个框架,每个框架都有自己的名字。可以把 applet 放在一个框架中,让它在其他框架中显 示文档。下一节会给出这样的一个例子。
注释:applet 查看器不显示 Web 页面。showDocument 方法在 applet 查看器中会被忽略。
API java.applet.Applet 1.2 略过
API java.applet.AppletContext 1.0 略过
13.4.8 沙箱
从一个远程站点加载代码再在本地执行时,安全会变得非常重要。访问一个 Web 页面会 自动启动这个页面上的所有 applet。
点击一个链接会启动 Java Web Start 应用。如果访问一个 Web 页面或点击一个链接会执 行用户计算机上的任意代码,有恶意的人就能轻松地窃取机密信息、 访问金融数据,或者利 用用户的机器发送垃圾邮件。
为了确保 Java 技术不会被恶意利用,Java 提供了一个精巧的安全模型, 我们将在卷 II 中 详 细 讨 论。安 全 管 理 器 会 检 査 对 所 有 系 统 资 源 的 访 问。默 认 情 况 下,它 只允许无害的操作。要允许其他操作, 用户必须明确地批准 applet 或应用运行。
远程代码在所有平台上能做什么? 如果只是显示图像和播放声音, 获得用户的按键和鼠 标点击,将用户输入发回给加载代码的主机,这些通常都没有问题。这对于显示资料数据或 者与教育程序或游戏交互来说够用了。这个受限的执行环境通常称为“ 沙箱”。沙箱中运行 的代码不能改变或刺探用户的系统。
具体来说, 沙箱中的程序有以下限制:
• 它们绝对不能运行任何本地可执行程序。
• 它们不能读写本地计算机的文件系统。
• 它们不能査找有关本地计算机的信息, 不过所用的 Java 版本和一些无害的操作系统细 节除外。特别是,沙箱中的代码不能査找用户的名字、e-mail 地址,等等。
• 远程加载的程序需要用户同意与下载这个程序的服务器以外的主机通信;这个服务器称为源主机 (originating ftost) 这个规则通常称为“ 远程代码只能与家人通话”。这 个规则可以保护用户防止代码刺探内部网资源。
• 所有弹出窗口都带有一个警告消息。这个消息是一个安全特性, 确保用户不会把这个 窗口误认为一个本地应用。这是因为会担心不设防的用户可能访问一个 Web 页面, 被 诱骗运行远程代码, 然后键入密码或信用卡号,这可能会发回给 Web 服务器。在早 期版本的 JDK 中,这个消息看上去很严重:“ 不能信任的 Java Applet 窗口”。后来的 版本把这个警告稍稍放松了一点:“ 未授权的 Java Applet 窗口”,后来又改为“ 警告: Java Applet 窗口”。现在是一个警告性的小三角,只有最敏锐的用户才会注意到。
沙箱概念没有原先那么有意义。过去, 任何人都可能部署沙箱代码, 只有需要得到许可 在沙箱外执行的代码才需要有数字签名。 如今, 不论是否在沙箱中运行,通过 Java Plug-in 执行的所有代码都必须有数字签名。
13.4.9 签名代码
applet 或 Java Web Start 应用的 JAR 文件必须有数字签名。签名的 JAR 文件带有一个证 书,指示签名者的身份。加密技术可以确保这个证书不可伪造,而且能检测出对签名文件的 任何篡改。
例如,假设你收到由 yWorlcs GmbH 开发并签名的一个应用,它使用 Thawte 签发的一个 证书来签名(如图 13-6 所示)。接收到应用时,可以确保以下几个方面:
1 ) 代码与签名时是一样的;没有第三方篡改这个代码。
2 ) 签名确实来自 yWorks。
3 ) 证书确实由 Thawte 签发。( Java Plug-in 知道如何检查 Thawte 和很多其他证书发行商 的证书,另外也可以安装候选的“ 根证书”。)
如果点击“ 更多信息” 链接,会告诉你这个应用运行时不会有 Java 通常的安全限制。要 安装和运行这个应用吗? 实际上这取决于你对 yWorks GmbH 的信任。
要从某个支持的发行商得到一个证书,可能每年要付数百美元, 一些证书发行商还要求 提供公司或企业的执照证明。过去,一些 Java 开发人员会生成他们自己的证书用来对代码签 名。当然,Java Plug-in 没有办法检査这些证书的准确性。不过,过去 Java Plug-in 会为用户 提供证书。这没有太大意义, 因为很少有用户了解安全与不安全的证书之间的差别。
现在已 经不再支持不安全的证书。 如果想要发布一个 Java applet 或 Web Start 应用, 你将别无选择。必须从 Java Plug-in 支 持的一个证书发行商得到一个证书, 用它对你的 JAR 文件签名。
如果你在一家公司任职,你的公司很有可能已经与一个证书发行商建立了关系,可以直 接订购一个代码签名证书。如果没有,很有必要货比三家,因为具体的价格有很大差异,有 些发行商对于向个人签发证书要求比较宽松。 你的证书会提供相关的安装说明, 指出如何把它安装在一个 Java 密钥库中, 这是一个受 密码保护的文件, 可以在签名过程中从这个密钥库获取证书。要保证这个密钥库文件和密码 的安全。
接下来,需要确定希望得到什么权限。你可以选择沙箱权限和完全权限。建立一个清单 文件(参见 13.1.2 节)。
包含 Permissions:sandbox 或 Permissions: all-permissions, 例如:
Manifest-Version: 1.0 Permissions: all-permissions
接下来运行 jar工具:
jar cvfR MyApplet.jar Manifest.mf mypackage/*.c1ass
HTML 文件的 applet 元素应当有属性 archive= "MyAppletjarM。 最后,对 JAR 文件签名。命令如下:
jarsigner -keystore keystorefile -tsa timestampURL MyApplet .jar keyalias
需要向证书发行商询问时间戳 URL。密钥别名由证书发行商签名。运行以下命令:
keytool -keystore keystorefile -list
来得到密钥别名。还可以用 keytool 命令的 -changealias 选项修改别名。(关于 keytool 的更多 信息, 参见卷 2的第 9 章)。 现在把签名的 JAR 文件和包含 applet 元素的 HTML 文件放到你的 Web 服务器上
注释: 可以详细地控制为 Java 应用授予的权限, 有关内容将在卷 II 的第 1 2 章讨论。不 过,这个体系没有以对消费者来说有意义的方式投入使用。Java Plug-in 只提供了两个安 全级别:沙箱和完全权限。
13.5 Java Web Start
Java Web Start 是一项在 Internet 上发布应用程序的技术。Java Web Start 应用程序包含下 列主要特性:
• Java Web Start 应用程序一般通过浏览器发布。只要 Java Web Start 应用程序下载到本地就可以启动它,而不需要浏览器。
• Java Web Start 应用程序并不在浏览器窗口内。它将显示在浏览器外的一个属于自己的 框架中。
• Java Web Start 应用程序不使用浏览器的 Java 实现。浏览器只是在加载 Java Web Start 应用程序描述符时启动一个外部应用程序。这与启动诸如 Adobe Acrobat 或 RealAudio 这样的辅助应用程序所使用的机制一样。
• 数字签名应用程序可以被赋予访问本地机器的任意权限。未签名的应用程序只能运行 在“ 沙箱” 中,它可以阻止具有潜在危险的操作。
13.5.1 发布 Java Web Start 应用
要想准备一个通过 Java Web Start 发布的应用程序, 应该将其打包到一个或多个 JAR 文 件中。然后创建一个 Java Network Launch Protocol ( JNLP ) 格式的描述符文件。将这些文件 放置在 Web 服务器上。
还需要确保 Web 服务器对扩展名为 .jnlp 的文件报告一个 application/x-java-jnlp-file 的 MIME 类型(浏览器利用 MIME 类型确定启动哪一种辅助应用程序)。更详细的有关信息请 参看 Web 服务器文档。
提示:要想体验 Java Web Start, 需 要 从 jakarta.apache.org/tomcat 上安装 Tomcat。Tomcat 是一个 servlet 和 JSP 页面的容器, 也提供网页服务。它被预配置为服务于 JNLP 文件所 对应的 MIME 类型。
试着用 Java Web Start 发布第 12 章中开发的计算器应用程序。步骤如下:
1 ) 编译程序。
javac -classpath :jdk/jre/lib/javaws.jar webstart/*.java
2 ) 使用下列命令创建一个 JAR 文件:
jar cvfe Calculator,jar webstart.Calculator webstart/*.class
3 ) 使用下列内容准备启动文件 Calculator.jnlp:略过
注意, 版本号必须是 1.6.0, 而不是 6.0。
启动文件格式很容易理解,无须说明。要想了解全部的规范, 请参看 www.orade.com/ technetwork/java/javase/javawebstart。
4 ) 如 果 使 用 Tomcat, 则 在 Tomcat 安 装 的 根 目 录 上 创 建 一 个 目 录 tomcat/webapps/ calculator。创建子目录 tomcat/webapps/calculator/WEB-INF, 并且将最小的 web.xml 文件放 置在 WEB-INF 子目录下:略过
5 ) 将 JAR 文件和启动文件放入 tomcat/webapps/calculator 目录。
6 ) 按照 2.5 节描述的过程,在 Java 控制面板中将 URLhttp://localhost:8080 增加到可信 站点列表。或者, 可以按 13.4.9 介绍的过程为 JAR 文件签名。 7 ) 在 towcat/bin 目录执行启动脚本来启动 Tomcat。
8 ) 将浏览器指向 JNLP 文件。 例如, 如果使用 Tomcat , 则 访 问 http://localhost:8080/ calculator/Calculator.jnlp。
如果已经对浏览器完成了 Java Web Start 的有关配置,应该能看到 Java Web Start 的启动窗口(见图 13-7) 。
如果你的浏览器不知道如何处理 JNLP 文件,可能会提供一个选项将它们与一个应用关 联。如果是这样,请选择jdk/bin/javaws。否则, 明确如何将 MIME 类型 application/x-javajnlp-file 与 javaws 应用关联。还可以试着重新安装可以做到这一点的 JDK。
9 ) 稍后,计算器就会出现, 所带的边框表明这是一个 Java 应用程序,如图 13-8 所示
10 ) 当再次访问 JNLP 文件时, 应用程序将从缓存中取出。可以利用 Java 插件控制面板查看缓存内容, 如图 13-9 所示。 在 Windows 系统的 Windows 控制面板中可以看到 Java 插件 控制面板。在 Linux 下, 可以运行 jdk/jre/bin/ControlPanel。
提示: 如果在测试 JNLP 配置期间不想运行 web 服务器, 可以通过执行下列命令临时性 地覆盖启动文件中的 URL:
javaws -codebase file://programDirectory JNLPfile
例如, 在 UNIX中, 可以从包含 JNLP 文件的目录中直接地发出下列命令: javaws -codebase file:// ' pwd ' Calculator.jnip
当然, 没有告诉用户再次运行应用程序时要启动缓存视图。可以利用安装器安装桌面和 菜单的快捷键。 将下列代码添加到 JNLP 文件中:
<shortcut>
<desktop/>
<menu submenu="Accessories"/>
</shortcut〉
当用户首次下载应用程序时, 将会显示“ desktop integration warning” , 如图 13-10 所示。 还应该为菜单快捷键和启动屏幕提供一个图标。Oracle 建议提供 32 x 32 和 64 x 64 的图 标。把这些图标文件与 JNLP 和 JAR 文件一起 放在 Web 服务器上。将下列代码添加到 JNLP 文件的 information 节中:
<icon href="calc_icon32.png" width="32" height="32" /> <icon href="calc_icon64 .png" width="64" height="64" />
注意, 这些图标与应用程序图标无关。如 果想让应用程序也有图标,需要将一个独立的 图标图像添加到 JAR 文件中, 并在框架类中 调用 setlconlmage 方法。请看程序清单 13- 1。
13.5.2 JNLP API
JNLP API 允许未签名的应用程序在沙箱中运行,同时通过一种安全的途径访问本地资 源。 例如, 有一些加载和保存文件的服务。应用程序不能査看系统文件,也不能指定文件 名。然而, 可以弹出一个文件对话框, 程序用户可以从中选择文件。在文件对话框弹出之 前, 应用程序不能浏览文件系统, 也不能指定文件名。取而代之的是, 系统将弹出文件对话 框,由程序的用户从中选择文件。在文件对话框弹出之前, 程序的用户会得到警告并且必须 同意继续处理, 如图 13-11 所示。而且,API 并不给予程序访问 File 对象的权限。特别是, 应用程序没有办法找到文件的位置。因此,程序员需要通过工具实现“ 打开文件” 和“ 保存 文件” 的操作,但是对于不信任的应用程序, 系统应尽可能地将信息隐藏起来。
API 提供了下面的服务:
• 加载和保存文件
• 访问剪贴板
• 打印
•下载文件
• 在默认的浏览器中显示一个文档
• 保存和获取持久性配置信息
• 确信只运行一个应用程序的实例
要访问服务, 需要使用 ServiceManager, 如下所示:
FileSaveService service = (FileSaveService) ServiceManager.1ookup("javax.jnlp.Fi1eSaveService"):
如果服务不可用, 调用将拋出 UnavailableServiceException。
注释: 如果想要编译使用了 JNLPAPI 的程序, 那就必须在类路径中包含 javaws.jar 文件。 这个文件在 JDK 的 jre/lib 子目录下。
现在, 讨论一些最常用的 JNLP 服务。要保存文件,需要为文件对话框提供文件的初始 路径名和文件扩展类型、要保存的数据和建议的文件名。例如:
service.saveFi1eDialog("." , newString[] { "txt" }, data, "calc.txt");
数据必须由 InputStream 传递。这可能有些困难。在程序清单 13-6中,程序使用下面的 策略:
1 ) 创建 ByteArrayOutputStream 用于存放需要保存的字节。 2 ) 创建 PrintStream 用于将数据传递给 ByteArrayOutputStream。 3 ) 将要保存的数据打印到 PrintStream。 4 ) 建立 ByteArraylnputStream 用于读取保存的字节。 5 ) 将上面的流传递给 saveFileDialog 方法。 卷 2 的第 1 章将阐述更多有关流的知识。现在,可以忽略示例中关于流的细节。
要想从文件中读取数据, 需要使用 FileOpenService。 它的 openFileDialog 对话框 接收应用程序提供的初始路径名和文件扩展名, 并返回一个 FileContents 对象。 然后 调用 getlnputStream 和 getOuputStream 方法来读写文件数据。 如果用户没有选择文件, openFileDialog 方法将返回 null。
FileOpenService service = (FileOpenService) ServiceManager.1ookup("javax.jnlp.Fi1eOpenService"); FileContents contents = service.openFileDialog(".", new String[] { "txt" }); if (contents !=null) { InputStream in = contents.getInutStream(); ... }
注意, 应用程序不知道文件名或文件所放置的位置。相反地, 如果想要打开一个特定的 文件, 需要使用 ExtendedService:
ExtendedService service = (ExtendedService) ServiceManager.1ookup("javax. jnlp.ExtendedService"); FileContents contents = service.openFile(new Fi1e("c:\\autoexec.bat")); if (contents != null) { OutputStream out = contents.getOutputStream(); ... }
程序的用户必须同意文件访问。如图 13-12 所示。 要想在默认浏览器中显示一个文档, 就需要使用 BasicService 接口。注意, 有些系统可 能没有默认的浏览器。
BasicService service = (BasicService) ServiceManager.1ookup(" javax.jnlp.BasicService"); if (service.isWebBrowserSupported ()) service.showDocument(url); else . . .
处于起步阶段的 PersistenceService 允 许应用程序保存少量的配置信息, 并且在 应用程序下次运行时恢复。这种机制类似 于 HTTP 的 cookie, 使用 URL 键 进 行 持 久性存储。URL 并不需要指向一个真正的 web 资源, 服务仅仅使用它们来构造一个 方便的具有层次结构的命名机制。对于任 何给定的 URL 键,应用程序可以存储任意 的二进制数据(存储可能会限制数据块的 大小)。
因为应用程序是彼此分离的, 一个特定的应用程序只能使用从 codebase 开始的 URL 键 值(codebase 在 JNLP 文件中指定)。例如, 如果一 应用程序是从 http://myserver.com/apps 下载的, 那么它只能使用 http://myserver.com/apps/subkeyl/subkey2/...形式的键。访问其他键 值的企图终将会失败。
应用程序可以调用 BasicService 类的 getCodeBase 方法查看它的 codebase。 可以调用 PersistenceService 中的 create 方法建立一个新的键:
URL url = new URL(CodeBase, "mykey"); service.create(url, maxSize);
要想访问与某个特定键关联的信息, 需要调用 get 方法。 这个方法将返回一个 FileContents 对象, 通过这个对象可以对键数据进行读写。例如:
FileContents contents = service.get(url); InputStream in = contents.getlnputStream(); OutputStream out = contents.getOutputStream(true); // true = overwrite
遗憾的是, 无法轻而易举地知道这个键是已经存在还是需要创建。可以假定这个键已经 存在, 并调用 get 方法。如果抛出 FileNotFoundException, 就说明需要创建。
注释: Java Web Start 应用程序和 applet 都可以使用 常规的打印 API 进行打印。这时会弹出一个安全对 话框来询问用户是否允许访问打印机。有关更多的 打印 API 的信息,请参看卷 2 的第 7 章。
程序清单 13-6 对计算器应用程序的功能做了一点 改进。这个计算器有一个虚拟的纸带来记录所有的计算 过程。用户可以保存或读取计算历史信息。为了演示持 久性存储功能, 应用程序允许设置框架的标题。如果再 次运行程序, 应用程序会从持久性存储中得到标题(如 H 13-13 所示)。略过
API javax.jnlp.ServiceManager 略过
API javax.jnlp.BasicService 略过
API javax.jnlp.FileContents 略过
API javax.jnlp.FileOpenService 略过
API javax.jnlp.FileSaveService 略过
API javax.jnlp.PersistenceService 略过
至此, 我们结束了对 Java 软件部署的讨论。这一卷最后一章将介绍一个重要内容: 并发编程。