你是否曾经编写了一个程序,却在复检的时候发现它的配置过程很不合理?你是否曾经使用过配置文件,却发现它们不能满足描述应用程序的需要?你是否为了解决几个特殊问题而创建过临时配置补丁,却把花费更多时间、开发普遍适用方案的希望寄托到了未来?
如果你的回答是肯定的,那么,你和大多数其他Java程序员一样幸运,有一些工具能够帮助你解决这些问题。如果你的回答是否定的,关于属性文件局限的讨论也许能够让你信服――还有更好的方法可供使用。
属性文件是Java编程和运行环境的一个重要组成部分。然而,当一个程序员需要的功能远远超过Properties类提供的简单名字-值对时,他需要有更丰富的表现手法。通常,Java程序员扩展属性文件的方法是为属性本身的名字或值(或两者同时)增加额外的语义信息。很多时候,这种看来有效的方法会使问题越来越复杂。
为说明问题,请利用属性把一系列的值赋给单个名字。让我们假定你想要管理一组名称服务器,可能采用的属性文件内容如下:
hosts_1=ns.foo.com
hosts_2=ns.bar.com
hosts_3=ns.acme.com
代码很简单。改变名字-值对中名字的含义之后,你可以轻松地编写出把“hosts_”开头的名字当成“hosts”列表中一个元素的程序。
下面,我们来看看一个更复杂的例子。假设你有同一Bean类InternetHost的两个不同实例:实例A关联到一个Web服务器的列表;实例B关联到一个名称服务器的列表。要从同一个文件配置这两个实例,一种可能的方案如下:
name_hosts_1=ns.foo.com
name_hosts_2=ns.bar.com
name_hosts_3=ns.acme.com
web_hosts_1=www.foo.com
web_hosts_2=www.bar.com
web_hosts_3=www.acme.com
这种方法行得通,但总是给人以拼拼凑凑的感觉。如果你还不相信的话,稍微增加一点问题的复杂性:让这些列表中的某个元素自己也成为一个列表;或者,使得下划线字符(“_”)在名字-值对中合法。在这些情况下,简单的属性文件变得非常复杂。
作为一个细心的读者,你可能已经发现,InternetHost各个实例的命名方式逐渐模糊。为了把前三个属性赋值给实例A,把后三个属性赋值给实例B,你必须用某种与具体实例无关的方法告诉实例它们该用哪一组属性值。如果用直接编码的方式,让实例A寻找以“name_”开头的属性,让实例B寻找以“Web_”开头的属性,那么,这两个实例将不再属于同一对象类。
最后的例子还显示出另外一个问题。这就是,如何来调用实例A?简单地叫它“A”?到哪里去寻找它?它是本地实例还是远程接口?是否存在指向它的全局静态引用?如是,如何访问实例B(或者,那是否是“B”)?
解决这些问题的方案是使用一个组件配置和命名框架。有许多工具能够帮助你完成这个任务,其中之一就是PASX。PASX是一个源代码开放的Java工具,它通过XML进行配置,通过JNDI实现命名。PASX框架用XML配置用户定义的服务、JNDI名称空间、JDBC连接池、事件树、工作队列和系统日志。
PASX利用XML进行配置,因为XML比简单的属性列表具有更丰富的描述能力。为了理解为何XML更适合完成这类任务,请再次考虑第一个例子。如果用PASX定义的标记重新描述,则结果应该如下:
<List
<Stringns.foo.com</String
<Stringns.bar.com</String
<Stringns.acme.com</String
</List
虽然代码更加冗长,但它的含义比原来要清楚得多。由于一些列表可能被排序,元素在XML文档中出现的次序决定了它们在最终数据结构中的次序。属性文件最终用来构造Properties对象,它的名字必须指示出元素的索引,因为Properties对象直接从Hashtable派生得到。
用XML描述时,第二个属性示例如下所示:
<List name="name-servers"
<Stringns.foo.com</String
<Stringns.bar.com</String
<Stringns.acme.com</String
</List
<List name="web-servers"
<Stringwww.foo.com</String
<Stringwww.bar.com</String
<Stringwww.acme.com</String
</List
还记得示例二之后提出的难题吗?让列表中的某个元素成为一个子列表,让其中一个列表成为元素名字可以包含下划线的映射结构。下面是它的答案:
<List name="name-servers"
<Stringns.foo.com<String
<List
<Stringns1.bar.com</String
<Stringns2.bar.com</String
</List
<Stringns.acme.com</String
</List
<Map name="web-servers"
<String name="most_visited"www.foo.com<String
<String name="most_bytes"www.bar.com</String
</Map
在PASX中,组件(一个类或者一组有着密切关系的类)是实现PASXService接口的Java Bean。它们由XML <Service标记定义,这个标记用来命名组件的单个实例。赋予实例“A”名称服务器列表以及赋予实例“B”Web服务器列表的XML代码如下所示:
<Service name="A" class="my.InternetHost"
<List name="hosts"
<Stringns.foo.com</String
<String<ns.bar.com</String
<String<ns.acme.com</String
</List
</Service
<Service name="B" class="my.InternetHost"
<List name="hosts"
<Stringweb.foo.com</String
<Stringweb.bar.com</String
<Stringweb.acme.com</String
</List
</Service
PASX定义了一系列的标准XML标记,用来声明List、Map、Integer、String、Boolean等类型的属性。然而,PASXService类还可以经由名称空间和XML模式使用它自己的XML标记。XML模式允许组件开发者定义自己的标记,允许XML解析器验证PASX所定义标记和组件开发者所定义标记的合法性。下面的示例模式定义了一个<Server标记,它必须有hostName和portNumber属性。<Server标记必须作为<Cluster标记的子元素至少出现一次,但可以出现多次。<Cluster标记必须作为<ServerFarm标记的子元素出现至少一次,但可以出现多次。
<?xml version="1.0"?
<schema xmlns="http://www.w3.org/2000/10/XMLSchema"
xmlns:pce="http://pasx.org/PASX/CUSTOM-EXAMPLE"
targetNamespace="http://pasx.org/PASX/CUSTOM-EXAMPLE" elementFormDefault="qualified"
<annotation
<documentation
A custom schema example to be using with PASX (PCE)
</documentation
</annotation
<element name="Server"
<complexType content="empty"
<attribute
name="hostName"
use="required"
type="string"/
<attribute
name="portNumber"
use="required"
type="positiveInteger"/
</complexType
</element
<element name="Cluster"
<complexType
<sequence
<element
ref="pce:Server" minOccurs="1" maxOccurs="unbounded"/
</sequence
<attribute
name="name"
use="required"
type="string"/
</complexType
</element
<element name="ServerFarm"
<complexType
<sequence
<element
ref="pce:Cluster" minOccurs="1" maxOccurs="unbounded"/
</sequence
<attribute
name="name"
use="required"
type="string"/
</complexType
</element
<element name="PCE"
<complexType
<sequence
<element
ref="pce:ServerFarm" minOccurs="1" maxOccurs="unbounded"/
</sequence
</complexType
</element
</schema
详细介绍XML模式文档(XSD)的构造方法已经超出了本文的范围。重要的是必须认识到,PASXService组件的开发者可以使用一组定制标记。更妙的是,开发者无需编写任何验证代码,就可以确保XML不仅格式良好而且合法(这一切由解析器完成)。下面声明的<Service标记用到了前面的模式:
<Service
class="org.pasx.examples.CustomConfigExample"
name="examples.customConfigExample"
<pce:PCE xmlns="http://pasx.org/PASX/CUSTOM-EXAMPLE"
xmlns:pce="http://pasx.org/PASX/CUSTOM-EXAMPLE"
xsi:schemaLocation="http://pasx.org/PASX/CUSTOM-EXAMPLE /org/pasx/examples/custom-example.xsd"
<ServerFarm name="farm0"
<Cluster name="cluster0"
<Server hostName="app0.foo.com" portNumber="8080" /
<Server hostName="app1.foo.com" portNumber="8080" /
</Cluster
<Cluster name="cluster1"
<Server hostName="app2.foo.c