参数选择 API
JD018 cherami cherami@163.net
概览
应用程序需要参数和配置数据以适应不同用户和环境的需要。
java.util.prefs 包给应用程序提供了存储和恢复用户和系统参数及配置数据的方法。数据不断地存入独立实现的后备存储中。有两个分立的参数节点树,一个用于用户参数,另一个用于系统参数。
所有修改参数数据的方法都允许异步操作。它们可能立即返回并且修改最终会传递给后备存储。
flush方法能用于强制刷新后备存储。
Preferences类中的方法可以在单一JVM下在没有外部同步的情况下通过多线程并发调用。如果这个类被多个使用同一后备存储区存储参数数据的JVM并发调用,数据存储不会被破坏,但是不能保证数据的一致性。
更多细节,可以参看下面的链接:
参数选择API和其它机制的比较在引入参数选择API前,开发者可能选择一种特别的方式管理参数和配置数据:使用下面描述的参数API或者JNDI API。 通常,参数和配置数据被存储在配置文件里面,这些文件可以通过java.util.PropertiesAPI访问。然而这些文件应该放在何处或者它们应该调用什么没有标准。使用这种机制,备份用户数据或者将它们从一个机器传递到另一个机器将异常的困难。随着应用程序的增加,文件名称冲突可能性就增加。而且,这种机制在没有本机磁盘的平台下是没有用的,或者需要将数据存储在外部的数据存储器上(例如企业范围的LDAP目录服务)。在少数情况下,开发者通过JNDI(Java名字和目录接口)API访问目录服务存储用户参数和配置数据。(不像参数API,JNDI允许允许使用任何后备存储(后端中立)。虽然JNDI是异常的强大,但是它也很巨大,它由5个包和83各类组成。JNDI不提供参数数据应该存储在目录名字空间的什么地方的政策。参数API和JNDI都没有提供一个简单的,普遍地,后端中立的参数管理工具。参数选择API提供了一个这样的工具,它结合了参数选择API的简单和JNDI的后端中立。它在难以达到的后备数据存储方面提供充分的内置政策防止名字冲突、鼓励一致性并且促进健壮性。参看: 设计 FAQ
使用注意事项这一节包含的材料不是参数选择API规范的一部分,它只是提供了一些如何使用参数选择API的例子。在类里面获得Preferences对象
在类里面获得Preferences 对象下面的例子阐明了如何在类里面获得Preferences对象(系统和用户)。这个例子只能在实例方法里面工作注意static final 成员而不是它的字面上的字符串用来作为关键字的名字(NUM_ROWS 和 NUM_COLS)。这降低了由于关键字名的印刷上的错误引起的运行时错误的可能性。同时注意为每个参数提供的合理的缺省值。这些缺省值在参数值没有被设置或者后备存储不可达的情况下被返回。package com.acme.widget;import java.util.prefs.*;public class Gadget { // Preference keys for this package private static final String NUM_ROWS = "num_rows"; private static final String NUM_COLS = "num_cols"; void foo() { Preferences prefs = Preferences.userNodeForPackage(this); int numRows = prefs.getInt(NUM_ROWS, 40); int numCols = prefs.getInt(NUM_COLS, 80); ... }}上面的例子为每个用户获取一个参数,如果想获取系统值,foo 中的第一行应该用下面的程序代替: Preferences prefs = Preferences.systemNodeForPackage(this);在静态方法中获取Preferences对象前一节的例子阐明了在类里面获取Preferences对象,在实例方法中。在一个静态方法或者静态初始化程序里面,你需要明确的提供包名 : Static String ourNodeName = "/com/acme/widget"; static void foo() { Preferences prefs = Preferences.userRoot().node(ourNodeName); ... }只在需要系统参数的时候在静态初始化程序里面获取一次系统参数是可以让人接受的: static Preferences prefs = Preferences.systemRoot().node(ourNodeName);通常为一个用户参数做同样的事情也是可以让人接受的,但是如果代码用于服务器,在其中多个用户可能并发或者连续运行该程序,这样的情况下就不行了。在一个这样的系统下, userNodeForPackage 和 userRoot 为调用者返回一个合适的节点,因此在一个适当的时间从一个适当地线程调用userNodeForPackage 或者 userRoot 是很危险的。在这样一个服务器环境下最终使用的代码片断应该是在要使用它们之前才获取用户参数对象,这是一个好的,保守的惯例。就像先前的例子那样做。
原子化校正参数选择API不提供像“事务”这样的多个参数原子地被修改的数据库操作。有时候我们需要将两个或更多参数当作一个单位修改。例如,假设存储窗口的x和y坐标,完成原子性的修改的唯一方法是在一个参数里面同时存储两个值。很多编码方法都是可以的,下面是一个简单的例子: int x, y; ... prefs.put(POSITION, x + "," + y);当读取一个像这样的 "复合参数"就需要解码。出于健壮性的考虑,应该允许被破坏(不可解析)的值被读取: static int X_DEFAULT = 50, Y_DEFAULT = 25; void baz() { String position = prefs.get(POSITION, X_DEFAULT + "," + Y_DEFAULT); int x, y; try { int i = position.indexOf(','); x = Integer.parseInt(coordinates.substring(0, i)); y = Integer.parseInt(position.substring(i + 1)); } catch(Exception e) { // Value was corrupt, just use defaults x = X_DEFAULT; y = Y_DEFAULT; } ... }探测后备存储的状态通常应用程序代码不需要知道后备存储是否可用。它总应该是可用的,但是如果它不可用,代码将使用缺省值代替后备存储中的参数值继续执行。非常罕见的情况下,一些高级的程序可能想在后备存储不可用的时候改变它的行为(或者简单的拒绝继续运行)。下面是一个通过试图修改参数值并强制将结果写回后备存储的方法探测后备存储是否可用的方法。 private static final String BACKING_STORE_AVAIL = "BackingStoreAvail"; private static boolean backingStoreAvailable() { Preferences prefs = Preferences.userRoot().node(""); try { boolean oldValue = prefs.getBoolean(BACKING_STORE_AVAIL, false); prefs.putBoolean(BACKING_STORE_AVAIL, !oldValue); prefs.flush(); } catch(BackingStoreException e) { return false; } return true; }设计 FAQ下面是有关参数选择API设计的FAQ。参数选择API如何涉及参数API? 它有意代替大多数人通常使用的参数API,校正了它的不足同时保持了它的简洁。在使用参数API时,程序员必须明确的指定每个参数文件的路径,但是并没有标准的定位或命名协定。参数文件是“脆弱”的,因为它们可以被手工编辑但是很容易被粗心的编辑破坏。参数文件不能容易的用于除了文件系统外的其他持续机制。总之,参数API工具不合适。参数选择API如何涉及JNDI?像JNDI,参数选择API提供后端中立的持久的关键字-值对数据访问。然而JNDI远远要强大,并且相对的庞大。JNDI对于需要它的能力的企业应用合适。参数选择API有意成为简单的,普遍地,后端中立的参数管理工具,使得任何 Java 应用都可以容易的根据用户参数定制它的行为并且从一个进程到另一个进程上维护一个小的状态参数。为什么所有的get方法都需要调用程序传入一个缺省值?这强制应用程序作者提供一个合理的缺省值,因而应用程序即使在存储器不可用时也有一个合理的可能值运行。如何决定什么方法需要抛出一个BackingStoreException异常?只有那些在语义上绝对需要和后备存储器通信的方法需要抛出这个异常。通常应用程序不需要调用这些方法。只要这些方法被避免,应用程序即使在后备存储不可用的情况下也可以运行,这是一个清楚地数据目标。为什么这个API不在多虚拟机并行访问上提供更强的保证? 类似的,为什么这个API允许多个Preferences更新合并为一个单一的“事务”,所有的或者没有语义学上的概念? 虽然这个API提供基本的持续数据存储,但它无意代替数据库。它在标准的参数/配置存储上实现这些是危险的,它的大多数不提供像数据库那样的保证和功能。这样的存储在这个API想要达到的目的上已经被证明是适当的。为什么这个API是关键字和节点名大小写敏感的,而其他有相同功能的API(例如WIN32注册表和LDAP)不是?在Java语言里面,大小写敏感的字符串关键字是很普遍的。特别地,它们是由将要被这个API取代的Properties类提供的。以大小写敏感的方式使用Properties对于人们来说是不平常的。例如Java的包名(也是大小写敏感的)有时也作为关键字。这个设计使得以大小写敏感的关键字在后备存储上实现Preferences的系统程序员的工作变得复杂是公认的,但是这是被考虑过的值得的代价,因为更多的程序员将使用参数选择API而不是实现它。为什么这个API不使用Java 2的集合结构?这个API设计用于一个非常精确的目的并为了该目的进行了优化。由于缺乏普通类型(参看 JSR-14), 这个API对于普通用户将不方便。如果强制符合Map API将使得它缺乏编译时的类型安全性。同时也没有期望与其他Map实现互用(尽管如果这个假设是错误的也可以很简单的实现一个适配器类纠正这个假设)。参数选择API的设计和Map是如此相似,以至于熟悉后者的程序员在使用前者上应该没有任何困难。
为什么 put 和 remove 方法不返回以前的值?
这两种方法被期望即使是后备存储不可用的情况下也可以执行。如果它们需要返回以前的值,那么这个期望就是不可能的。更进一步的,如果这个API在一些通用的后端数据存储上实现的话将给性能带来负面的影响。
为什么这个API允许但是不要求存储缺省值?
这个功能在企业设置里面是作为标准被要求的,参数选择的成本效益管理贯穿整个企业,但是在自管理的单一用户设置上这个要求就太过分了。
为什么这个API不包含读写可串行化对象的方法?
串行化对象有些脆弱:如果读某个特性的程序的版本和写该特性的程序版本不同,那么对象可能是不完全连续的(或者根本就不连续)。使用这个API存储串行化对象不是不可能的,但是我们不鼓励这样做,并且没有提供一个方便的方法。
为什么Preferences是一个抽象类而不是一个接口?
在向上兼容性上增加新方法的能力的价值超过了Preferences不能作为一个"mixin" (也就是说并不是任何类都可以作为一个Preferences对象进行服务)的缺点。同时,这也避免了个别的类对静态方法的需要。 (接口是不能包含静态方法的。)
<!-- Body text ends here -->
<!-- ============================================================== -->
Copyright © 2001
All Rights Reserved.
Java Software