第13章 部署 Java 应用程序

第13章 部署 Java 应用程序

13.1 JAR 文件

Java 归档(JAR) 文件

  • 在将应用程序进行打包时,使用者一定希望仅提供给其一个单独的文件, 而不是一个含有大量类文件的目录,Java 归档(JAR) 文件就是为此目的而设计的。
  • JAR 文件是压缩的,它使用了大家熟悉的 ZIP 压缩格式。

pack200 是一种较通常的 ZIP 压缩算法更加有效的压缩类文件的方式。

13.1.1 创建 JAR 文件

创建 JAR 文件

  • 可以使用 jar 工具制作 JAR 文件(在默认的 JDK 安装中, 位于 jdk/bin 目录下)。
  • 可以将应用程序、程序组件(有时称为“ beans” —参见卷 II 第 11 章)以及代码库打包在 JAR 文件中。

常见命令格式

//常见命令格式
jar cvf JARFileName File1 File2 . . .
//例如:
jar cvf CalculatorClasses.jar *class icon.gif
//通常,jar 命令的格式如下:
jar options File File2 . . .

jar程序选项

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

13.1.2 清单文件

清单文件

//最小的符合标准的清单文件是很简单的:
Manifest-Version: 1.0
  • 除了类文件、图像和其他资源外,每个 JAR 文件还包含一个用于描述归档特征的清单文件 (manifest)。
  • 清单文件被命名为 MANIFEST.MF,它位于 JAR 文件的一个特殊 META-INF 子目录中。

复杂的清单文件

Manifest-Version: 1.0
描述这个归档文件的行

Name: Woozle.class
描述这个文件的行
Name: cora/mycompany/mypkg/
描述这个包的行
  • 复杂的清单文件可能包含更多条目,这些清单条目被分成多个节。
  • 第一节被称为主节 (main section) ,它作用于整个 JAR 文件。
  • 随后的条目用来指定已命名条目的属性,这些已命名的条目可以是某个文件、包或者 URL。它们都必须起始于名为 Name 的条目。
  • 节与节之间用空行分开。

编辑清单文件

  • 要想编辑清单文件,需要将希望添加到清单文件中的行放到文本文件中,然后运行命令。
  • 要想更新一个已有的 JAR 文件的清单,则需要将增加的部分放置到一个文本文件中,然后执行命令。
jar cfm JARFileName ManifestFileName . . .
//要创建一个包含清单的 JAR 文件
jar cfm MyArchive.jar manifest.mf com/mycompany/mypkg/(防注释)*.class
//要想更新一个已有的 JAR 文件的清单
jar ufin MyArchive.jar manifest-additions.mf

13.1.3 可执行 JAR 文件

程序的人口点

//使用命令指定
jar cvfe HyPrograni.jar com.myconipany.mypkg.MainAppClass files to add
//在清单文件中指定
Main-C1ass: com.nycompany.mypkg.MainAppClass
  • 可以使用jar 命令中的 e 选项指定程序的人口点,即通常需要在调用java 程序加载器时指定的类。
  • 或者,可以在清单中指定应用程序的主类。

启动应用程序

  • 不论哪一种方法,用户可以简单地通过下面命令来启动应用程序:
java -jar MyProgram.jarjava -jar MyProgram.jar

13.1.4 资源

相关的数据文件

  • 图像和声音文件。
  • 带有消息字符串和按钮标签的文本文件。
  • 二进制数据文件, 例如, 描述地图布局的文件。
  • 在 Java 中,这些关联的文件被称为资源(resource)。

非类文件操作步骤

  • 获得具有资源的 Class 对象,例如,AboutPanel.class。
  • 如果资源是一个图像或声音文件,那么就需要调用 getresource (filename) 获得作为URL 的资源位置,然后利用 getlmage 或 getAudioClip 方法进行读取。
  • 与图像或声音文件不同,其他资源可以使用 getResourceAsStream 方法读取文件中的数据。

定位资源文件

//例如,要想利用 about.gif 图像文件制作图标
URL url = ResourceTest.class.getResource("about.gif");
Image img = new ImageIcon(url).getImage():
//含义是"在找到 ResourceTest 类的地方查找 about.gif 文件"
InputStream stream = ResourceTest.class.getResourceAsStream(about.txt");
Scanner in = new Scanner(stream,"UTF-8"):

层级资源名

data/text/about.txt
  • 这是一个相对的资源名,它会被解释为相对于加载这个资源的类所在的包。

注意,必须使用“/” 作为分隔符,而不要理睬存储资源文件的系统实际使用哪种目录分隔符。(Windows 中会转换成“\”)

/corejava/title.txt
  • 一个以“ /” 开头的资源名被称为绝对资源名。它的定位方式与类在包中的定位方式一样。

13.1.5 密封

密封概念

  • 可以将 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 应用首选项的存储

概念

  • 应用用户通常希望能保存他们的首选项和定制信息,以后再次启动应用时再恢复这些配置。

13.2.1 属性映射

属性映射

//属性映射对于指定程序的配置选项很有用。
Properties settings - new Properties;
settings.setProperty("width", "200");
settings.setProperty("title", "Hello, World!");
  • 属性映射(property map) 是一种存储键 / 值对的数据结构。属性映射通常用来存储配置信息。
  • 实现属性映射的 Java 类名为 Properties。

3个特性

  • 键和值是字符串。
  • 映射可以很容易地存人文件以及从文件加载。
  • 有一个二级表保存默认值。

储存和加载

  • 可以使用 store方法将属性映射列表保存到一个文件中。
//将属性映射保存在文件 program.properties 中。
OutputStream out = new FileOutputStream("program.properties") ;
//第二个参数是包含在这个文件中的注释。
settings.store(out, "Program Properties");

//输出
#IProgran Properties
#Mon Apr 30 07:22:52 2007
width=200
title=Hello, World!
  • 要从文件加载属性,可以使用以下调用:
InputStream in = new FileInputStream("prograni.properties");
settings.load(in);

储存位置

  • 习惯上,会把程序属性存储在用户主目录的一个子目录中。目录名通常以一个点号开头(在 UNIX 系统中,) 这个约定说明这是一个对用户隐藏的系统目录。
  • 找出用户的主目录,可以调用 System.getProperties 方法,它恰好也使用一个 Properties 对象描述系统信息。
//主目录包含键 "usenhome",另一种方法读取单个键
String userDir = System.getProperty("user.home");

两种默认值机制

  • 第一种方法是,查找一个字符串的值时可以指定一个默认值,这样当键不存在时就会自动使用这个默认值。
String title = settings.getProperty("title", "Default title");
  • 可以把所有默认值都放在一个二级属性映射中,并在主属性映射的构造器中提供这个二级映射。
Properties defaultSettings = new Properties();
defaultSettings.setProperty("width", "300");
defaultSettings.setProperty("height", "200");
defaultSettings.setProperty("titie", "Default title");
Properties settings = new Properties(defaultSettings);

历史问题

  • 出于历史上的原因,Properties 类实现了 Map<Object,Object>。
  • 最好坚持使用 getProperty 和 setProperty 方法,这些方法会处理字符串,而不是对象。
  • 属性映射是没有层次结构的简单表。 通常会用类似 window.main.color、window.main.title 等键名引入一个伪层次结构 。
  • 如果存储复杂的配置信息, 就应当使用 Preferences 类。

13.2.2 首选项 API

属性文件缺点

  • 有些操作系统没有主目录的概念,所以很难找到一个统一的配置文件位置。
  • 关于配置文件的命名没有标准约定,用户安装多个 Java 应用时,就更容易发生命名冲突。

Preferences 类

  • 有些操作系统有一个存储配置信息的中心存储库 (Windows
    中的注册表) 。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 getFloat(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()
  • 目前没有办法找出一个特定键对应的值的类型。

中心存储库两个问题

  • 它们会变成充斥着过期信息的“ 垃圾场”。
  • 配置数据与存储库纠缠在一起,以至于很难把首选项迁移到新平台。

第二个问题解决方案

  • Preferences 类为第二个问题提供了一个解决方案。可以通过调用以下方法导出一个子树 (或者比较少见的,也可以是一个节点) 的首选项:
void exportSubtree(OutputStream out)
void exportNode(OutputStream out)
  • 数据用 XML 格式保存。
  • 可以通过调用以下方法将数据导人到另一个存储库:
void importPreferences(InputStreain in)

13.3 服务加载器

插件

  • 通常,提供一个插件时,程序希望插件设计者能有一些自由来确定如何实现插件的特性。另外还可以有多个实现以供选择。
  • 利用 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 = ServiceLoader.1oad(Cipher.cl ass);
  • 服务加载器的 iterator 方法会对服务提供的所有实现返冋一个迭代器。在循环中,选择一个适当的对象来完成服务。
public static Cipher getCipher(int minStrength) {
	for (Cipher cipher : cipherLoader) { // Implicitly calls cipherLoader.iteratorO
		if (cipher.strength0 >= minStrength) return cipher;
	}
	return null ;
}

13.4 applet

13.5 Java Web Start

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值