[转]Ant全攻略

四月 21, 2005

Ant全攻略

转自:BEA论坛 powerise

1.Ant是什么?
Ant是一种基于Java和XML的build工具。

2 下载、安装Ant
安装Ant
下载.zip文件,解压缩到c:\ant1.3(后面引用为%ANT_HOME%)

2.1 在你运行Ant之前需要做一些配置工作。
・ 将bin目录加入PATH环境变量。
・ 设定ANT_HOME环境变量,指向你安装Ant的目录。在一些OS上,Ant的脚本可以猜测ANT_HOME(Unix和Windos NT/2000)-但最好不要依赖这一特性。
・ 可选地,设定JAVA_HOME环境变量(参考下面的高级小节),该变量应该指向你安装JDK的目录。
注意:不要将Ant的ant.jar文件放到JDK/JRE的lib/ext目录下。Ant是个应用程序,而lib/ext目录是为JDK扩展使用的(如JCE,JSSE扩展)。而且通过扩展装入的类会有安全方面的限制。
2.2 运行Ant

运行Ant非常简单,当你正确地安装Ant后,只要输入ant就可以了。

n 没有指定任何参数时,Ant会在当前目录下查询build.xml文件。如果找到了就用该文件作为buildfile。如果你用 -find 选项。Ant就会在上级目录中寻找buildfile,直至到达文件系统的根。要想让Ant使用其他的buildfile,可以用参数 -buildfile file,这里file指定了你想使用的buildfile。

n 可以指定执行一个或多个target。当省略target时,Ant使用标签<project>的default属性所指定的target。

命令行选项总结:
ant [options] [target [target2 [target3] …]]
Options:
-help print this message
-projecthelp print project help information
-version print the version information and exit
-quiet be extra quiet
-verbose be extra verbose
-debug print debugging information
-emacs produce logging information without adornments
-logfile file use given file for log output
-logger classname the class that is to perform logging
-listener classname add an instance of class as a project listener
-buildfile file use specified buildfile
-find file search for buildfile towards the root of the filesystem and use the first one found
-Dproperty=value set property to value
例子
ant
使用当前目录下的build.xml运行Ant,执行缺省的target。
ant -buildfile test.xml
使用当前目录下的test.xml运行Ant,执行缺省的target。
ant -buildfile test.xml dist
使用当前目录下的test.xml运行Ant,执行一个叫做dist的target。
ant -buildfile test.xml -Dbuild=build/classes dist
使用当前目录下的test.xml运行Ant,执行一个叫做dist的target,并设定build属性的值为build/classes。

3 编写build.xml

Ant的buildfile是用XML写的。每个buildfile含有一个project。

buildfile中每个task元素可以有一个id属性,可以用这个id值引用指定的任务。这个值必须是唯一的。(详情请参考下面的Task小节)

3.1 Projects

project有下面的属性:
Attribute Description Required
name 项目名称. No
default 当没有指定target时使用的缺省target Yes
basedir 用于计算所有其他路径的基路径。该属性可以被basedir property覆盖。当覆盖时,该属性被忽略。如果属性和basedir property都没有设定,就使用buildfile文件的父目录。 No
项目的描述以一个顶级的<description>元素的形式出现(参看description小节)。

一个项目可以定义一个或多个target。一个target是一系列你想要执行的。执行Ant时,你可以选择执行那个target。当没有给定target时,使用project的default属性所确定的target。

3.2 Targets

一个target可以依赖于其他的target。例如,你可能会有一个target用于编译程序,一个target用于生成可执行文件。你在生成可执行文件之前必须先编译通过,所以生成可执行文件的target依赖于编译target。Ant会处理这种依赖关系。

然而,应当注意到,Ant的depends属性只指定了target应该被执行的顺序-如果被依赖的target无法运行,这种depends对于指定了依赖关系的target就没有影响。

Ant会依照depends属性中target出现的顺序(从左到右)依次执行每个target。然而,要记住的是只要某个target依赖于一个target,后者就会被先执行。
<target name=”A”/>
<target name=”B” depends=”A”/>
<target name=”C” depends=”B”/>
<target name=”D” depends=”C,B,A”/>
假定我们要执行target D。从它的依赖属性来看,你可能认为先执行C,然后B,最后A被执行。错了,C依赖于B,B依赖于A,所以先执行A,然后B,然后C,最后D被执行。

一个target只能被执行一次,即时有多个target依赖于它(看上面的例子)。

如果(或如果不)某些属性被设定,才执行某个target。这样,允许根据系统的状态(java version, OS, 命令行属性定义等等)来更好地控制build的过程。要想让一个target这样做,你就应该在target元素中,加入if(或unless)属性,带上target因该有所判断的属性。例如:
<target name=”build-module-A” if=”module-A-present”/>
<target name=”build-own-fake-module-A” unless=”module-A-present”/>
如果没有if或unless属性,target总会被执行。

可选的description属性可用来提供关于target的一行描述,这些描述可由-projecthelp命令行选项输出。

将你的tstamp task在一个所谓的初始化target是很好的做法,其他的target依赖这个初始化target。要确保初始化target是出现在其他target依赖表中的第一个target。在本手册中大多数的初始化target的名字是”init”。

target有下面的属性:
Attribute Description Required
name target的名字 Yes
depends 用逗号分隔的target的名字列表,也就是依赖表。 No
if 执行target所需要设定的属性名。 No
unless 执行target需要清除设定的属性名。 No
description 关于target功能的简短描述。 No

3.3 Tasks

一个task是一段可执行的代码。

一个task可以有多个属性(如果你愿意的话,可以将其称之为变量)。属性只可能包含对property的引用。这些引用会在task执行前被解析。

下面是Task的一般构造形式:
<name attribute1=”value1″ attribute2=”value2″ … />
这里name是task的名字,attributeN是属性名,valueN是属性值。

有一套内置的(built-in)task,以及一些可选task,但你也可以编写自己的task。

所有的task都有一个task名字属性。Ant用属性值来产生日志信息。

可以给task赋一个id属性:
<taskname id=”taskID” … />
这里taskname是task的名字,而taskID是这个task的唯一标识符。通过这个标识符,你可以在脚本中引用相应的task。例如,在脚本中你可以这样:
<script … >
task1.setFoo(”bar”);
</script>
设定某个task实例的foo属性。在另一个task中(用java编写),你可以利用下面的语句存取相应的实例。
project.getReference(”task1″).
注意1:如果task1还没有运行,就不会被生效(例如:不设定属性),如果你在随后配置它,你所作的一切都会被覆盖。

注意2:未来的Ant版本可能不会兼容这里所提的属性,因为很有可能根本没有task实例,只有proxies。

3.4 Properties

一个project可以有很多的properties。可以在buildfile中用property task来设定,或在Ant之外设定。一个property有一个名字和一个值。property可用于task的属性值。这是通过将属性名放在”${”和”}”之间并放在属性值的位置来实现的。例如如果有一个property builddir的值是”build”,这个property就可用于属性值:${builddir}/classes。这个值就可被解析为build/classes。

内置属性

如果你使用了<property> task 定义了所有的系统属性,Ant允许你使用这些属性。例如,${os.name}对应操作系统的名字。

要想得到系统属性的列表可参考the Javadoc of System.getProperties。

除了Java的系统属性,Ant还定义了一些自己的内置属性:
basedir project基目录的绝对路径 (与<project>的basedir属性一样)。
ant.file buildfile的绝对路径。
ant.version Ant的版本。
ant.project.name 当前执行的project的名字;由<project>的name属性设定.
ant.java.version Ant检测到的JVM的版本; 目前的值有”1.1″, “1.2″, “1.3″ and “1.4″.

例子
<project name=”MyProject” default=”dist” basedir=”.”>

<!– set global properties for this build –>
<property name=”src” value=”http://blogger.org.cn/blog/.”/>
<property name=”build” value=”build”/>
<property name=”dist” value=”dist”/>

<target name=”init”>
<!– Create the time stamp –>
<tstamp/>
<!– Create the build directory structure used by compile –>
<mkdir dir=”${build}”/>
</target>

<target name=”compile” depends=”init”>
<!– Compile the java code from ${src} into ${build} –>
<javac srcdir=”http://blogger.org.cn/blog/${src}” destdir=”http://blogger.org.cn/blog/${build}”/>
</target>

<target name=”dist” depends=”compile”>
<!– Create the distribution directory –>
<mkdir dir=”${dist}/lib”/>
<!– Put everything in ${build} into the MyProject-${DSTAMP}.jar file –>
<jar jarfile=”${dist}/lib/MyProject-${DSTAMP}.jar” basedir=”${build}”/>
</target>

<target name=”clean”>
<!– Delete the ${build} and ${dist} directory trees –>
<delete dir=”${build}”/>
<delete dir=”${dist}”/>
</target>

</project>

3.5 Path-like Structures

你可以用”:”和”;”作为分隔符,指定类似PATH和CLASSPATH的引用。Ant会把分隔符转换为当前系统所用的分隔符。

当需要指定类似路径的值时,可以使用嵌套元素。一般的形式是
<classpath>
<pathelement path=”${classpath}”/>
<pathelement location=”lib/helper.jar”/>
</classpath>
location属性指定了相对于project基目录的一个文件和目录,而path属性接受逗号或分号分隔的一个位置列表。path属性一般用作预定义的路径--其他情况下,应该用多个location属性。

为简洁起见,classpath标签支持自己的path和location属性。所以:
<classpath>
<pathelement path=”${classpath}”/>
</classpath>
可以被简写作:
<classpath path=”${classpath}”/>
也可通过<fileset>元素指定路径。构成一个fileset的多个文件加入path-like structure的顺序是未定的。
<classpath>
<pathelement path=”${classpath}”/>
<fileset dir=”lib”>
<include name=”**/*.jar”/>
</fileset>
<pathelement location=”classes”/>
</classpath>
上面的例子构造了一个路径值包括:${classpath}的路径,跟着lib目录下的所有jar文件,接着是classes目录。

如果你想在多个task中使用相同的path-like structure,你可以用<path>元素定义他们(与target同级),然后通过id属性引用--参考Referencs例子。

path-like structure可能包括对另一个path-like structurede的引用(通过嵌套<path>元素):
<path id=”base.path”>
<pathelement path=”${classpath}”/>
<fileset dir=”lib”>
<include name=”**/*.jar”/>
</fileset>
<pathelement location=”classes”/>
</path>
<path id=”tests.path”>
<path refid=”base.path”/>
<pathelement location=”testclasses”/>
</path>
前面所提的关于<classpath>的简洁写法对于<path>也是有效的,如:
<path id=”tests.path”>
<path refid=”base.path”/>
<pathelement location=”testclasses”/>
</path>
可写成:
<path id=”base.path” path=”${classpath}”/>
命令行变量

有些task可接受参数,并将其传递给另一个进程。为了能在变量中包含空格字符,可使用嵌套的arg元素。
Attribute Description Required
value 一个命令行变量;可包含空格字符。 只能用一个
line 空格分隔的命令行变量列表。
file 作为命令行变量的文件名;会被文件的绝对名替代。
path 一个作为单个命令行变量的path-like的字符串;或作为分隔符,Ant会将其转变为特定平台的分隔符。

例子
<arg value=”-l -a”/>
是一个含有空格的单个的命令行变量。
<arg line=”-l -a”/>
是两个空格分隔的命令行变量。
<arg path=”/dir;/dir2:\dir3″/>
是一个命令行变量,其值在DOS系统上为\dir;\dir2;\dir3;在Unix系统上为/dir:/dir2:/dir3 。

3.6 References

buildfile元素的id属性可用来引用这些元素。如果你需要一遍遍的复制相同的XML代码块,这一属性就很有用--如多次使用<classpath>结构。

下面的例子:
<project … >
<target … >
<rmic …>
<classpath>
<pathelement location=”lib/”/>
<pathelement path=”${java.class.path}/”/>
<pathelement path=”${additional.path}”/>
</classpath>
</rmic>
</target>
<target … >
<javac …>
<classpath>
<pathelement location=”lib/”/>
<pathelement path=”${java.class.path}/”/>
<pathelement path=”${additional.path}”/>
</classpath>
</javac>
</target>
</project>
可以写成如下形式:
<project … >
<path id=”project.class.path”>
<pathelement location=”lib/”/>
<pathelement path=”${java.class.path}/”/>
<pathelement path=”${additional.path}”/>
</path>
<target … >
<rmic …>
<classpath refid=”project.class.path”/>
</rmic>
</target>
<target … >
<javac …>
<classpath refid=”project.class.path”/>
</javac>
</target>
</project>
所有使用PatternSets, FileSets 或 path-like structures嵌套元素的task也接受这种类型的引用。
================================================================
Ant全攻略(续)---Ant中的内置任务

4.1 File(Directory)类
4.1.1 Mkdir
n 创建一个目录,如果他的父目录不存在,也会被同时创建。
n 例子:
<mkdir dir=”build/classes”/>
n 说明: 如果build不存在,也会被同时创建
4.1.2 Copy
n 拷贝一个(组)文件、目录
n 例子:
1. 拷贝单个的文件:
<copy file=”myfile.txt” tofile=”mycopy.txt”/>
2. 拷贝单个的文件到指定目录下
<copy file=”myfile.txt” todir=”../some/other/dir”/>
3. 拷贝一个目录到另外一个目录下
<copy todir=”../new/dir”>
<fileset dir=”src_dir”/>
</copy>
4. 拷贝一批文件到指定目录下
<copy todir=”http://blogger.org.cn/blog/../dest/dir”>
<fileset dir=”src_dir”>
<exclude name=”http://blogger.org.cn/blog/**/*.java”/>
</fileset>
</copy>

<copy todir=”../dest/dir”>
<fileset dir=”src_dir” excludes=”http://blogger.org.cn/blog/**/*.java”/>
</copy>
5. 拷贝一批文件到指定目录下,将文件名后增加。Bak后缀
<copy todir=”../backup/dir”>
<fileset dir=”src_dir”/>
<mapper type=”http://blogger.org.cn/blog/glob” from=”*” to=”*.bak”/>
</copy>
6. 拷贝一组文件到指定目录下,替换其中的@标签@内容
<copy todir=”../backup/dir”>
<fileset dir=”src_dir”/>
<filterset>
<filter token=”http://blogger.org.cn/blog/TITLE” value=”Foo Bar”/>
</filterset>
</copy>
4.1.3 Delete
n 删除一个(组)文件或者目录
n 例子
1. 删除一个文件
<delete file=”/lib/ant.jar”/>
2. 删除指定目录及其子目录
<delete dir=”lib”/>
3. 删除指定的一组文件
<delete>
<fileset dir=”.” includes=”**/*.bak”/>
</delete>
4. 删除指定目录及其子目录,包括他自己
<delete includeEmptyDirs=”true”>
<fileset dir=”build”/>
</delete>
4.1.4 Move
n 移动或重命名一个(组)文件、目录
n 例子:
1. 移动或重命名一个文件
<move file=”file.orig” tofile=”file.moved”/>
2. 移动或重命名一个文件到另一个文件夹下面
<move file=”file.orig” todir=”dir/to/move/to”/>
3. 将一个目录移到另外一个目录下
<move todir=”new/dir/to/move/to”>
<fileset dir=”src/dir”/>
</move>
4. 将一组文件移动到另外的目录下
<move todir=”http://blogger.org.cn/blog/some/new/dir”>
<fileset dir=”my/src/dir”>
<include name=”http://blogger.org.cn/blog/**/*.jar”/>
<exclude name=”**/ant.jar”/>
</fileset>
</move>
5. 移动文件过程中增加。Bak后缀
<move todir=”my/src/dir”>
<fileset dir=”http://blogger.org.cn/blog/my/src/dir”>
<exclude name=”http://blogger.org.cn/blog/**/*.bak”/>
</fileset>
<mapper type=”glob” from=”*” to=”*.bak”/>
</move>

================================================================
Ant全攻略 — Java相关任务
4.2.1 Javac
n 编译java原代码
n 例子
1. <javac srcdir=”http://blogger.org.cn/blog/${src}”
destdir=”http://blogger.org.cn/blog/${build}”
classpath=”xyz.jar”
debug=”on”
/>
编译${src}目录及其子目录下的所有。Java文件,。Class文件将放在${build}指定的目录下,classpath表示需要用到的类文件或者目录,debug设置为on表示输出debug信息
2. <javac srcdir=”http://blogger.org.cn/blog/${src}:${src2}”
destdir=”http://blogger.org.cn/blog/${build}”
includes=”mypackage/p1/**,mypackage/p2/**”
excludes=”mypackage/p1/testpackage/**”
classpath=”xyz.jar”
debug=”on”
/>
编译${src}和${src2}目录及其子目录下的所有。Java文件,但是package/p1/**,mypackage/p2/**将被编译,而mypackage/p1/testpackage/**将不会被编译。Class文件将放在${build}指定的目录下,classpath表示需要用到的类文件或者目录,debug设置为on表示输出debug信息
3. <property name=”http://blogger.org.cn/blog/classpath” value=”.;./xml-apis.jar;../lib/xbean.jar;./easypo.jar”/>

<javac srcdir=”http://blogger.org.cn/blog/${src}”
destdir=”http://blogger.org.cn/blog/${src}”
classpath=”http://blogger.org.cn/blog/${classpath}”
debug=”on”
/>
路径是在property中定义的
4.2.2 java
n 执行指定的java类
n 例子:
1. <java classname=”test.Main”>
<classpath>
<pathelement location=”dist/test.jar”/>
<pathelement path=”${java.class.path}”/>
</classpath>
</java>
classname中指定要执行的类,classpath设定要使用的环境变量
2. <path id=”project.class.path”>
<pathelement location=”lib/”/>
<pathelement path=”${java.class.path}/”/>
<pathelement path=”${additional.path}”/>
</path>

<target … >
<rmic …>
<classpath refid=”project.class.path”/>
</rmic>
</target>

================================================================

Ant全攻略 — 通过JDBC执行SQL语句

4.5 执行SQL语句

n 通过jdbc执行SQL语句
n 例子:
1. <sql
driver=”org.gjt.mm.mysql.Driver”
url=”jdbc:mysql://localhost:3306/mydb”
userid=”root”
password=”root”
xsrc=”http://blogger.org.cn/blog/data.sql” mce_src=”http://blogger.org.cn/blog/data.sql”
/>
2. <sql
driver=”org.database.jdbcDriver”
url=”jdbc:database-url”
userid=”sa”
password=”pass”
xsrc=”http://blogger.org.cn/blog/data.sql” mce_src=”http://blogger.org.cn/blog/data.sql”
rdbms=”oracle”
version=”8.1.”
>
</sql>
只有在oracle、版本是8.1的时候才执行

================================================================
Ant 全攻略之 --- 发送邮件

4.6 发送邮件
n 使用SMTP服务器发送邮件
n 例子:
<mail mailhost=”smtp.myisp.com” mailport=”1025″ subject=”Test build”>
<from address=”me@myisp.com”/>
<to address=”http://blogger.org.cn/blog/all@xyz.com”/>
<message>The ${buildname} nightly build has completed</message>
<fileset dir=”http://blogger.org.cn/blog/dist”>
<includes name=”**/*.zip”/>
</fileset>
</mail>
l mailhost: SMTP服务器地址
l mailport: 服务器端口
l subject: 主题
l from: 发送人地址
l to: 接受人地址
l message: 发送的消息
l fileset: 设置附件

软件维护:Java文件格式之变化

一、概述

二、Java串行化

三、引入版本编号

四、结束语

一、概述

一个程序正式发行出去之后,如果要增加一些新的功能,往往意味着同时要修改用户保存数据的方式,也就是必须更改程序保存文件的格式――通常是增加保存到文件的数据。有些时候,文件格式必须作彻底的改动,以配合实现程序的新功能。从这个意义上看,文件格式的发展/变化总是和程序的功能改进相呼应。

但是,大多数情况下,把原有的数据格式一丢了事是行不通的。动物王国中,不能适应环境意味着死亡;软件领域也相似,新软件是否支持原有的数据格式很大程度上决定了用户是否升级。

不管软件新增/改进了多少功能,不管新的文件格式是多么完美,如果新软件不能利用原来的文件格式,用户一般不太会认可新软件。解决该问题的办法包括:

●保留老代码来读取老文件。采用这种方案一般需要额外编写一些代码,把老文件转换成新的格式(一般地,最简单的办法是先把老文件的数据转换成新的内部对象,然后利用现有的写入新版文件格式的对象)。这种办法的好处是既保留了原有的代码,又使它与新的文件格式兼容。但是,这种办法有时可能导致丢失部分数据,不过总要比丢失全部数据好。

●使新版软件能够读/写老文件格式。这种办法工作量较大,因为程序的新版本一般会增加一些原来没有的功能,老的数据格式中通常缺乏新功能必需的某些数据。

当新版软件对原来执行任务的方式作了根本性的变动时,丢失数据决非难得一见的偶然事件。如果新版软件采用和原来不同的方式达到同样的效果,原来的功能可能不再有保留的必要。例如,如果一个程序原来用Swing做用户界面,现在把它改成了Web(浏览器)用户界面,原来的许多用户界面设置就不再有效。

又如,如果有一个邮件程序,原来用的是以文件夹为基础的索引,现在把它改成了以单词为基础的索引系统,在升级索引文件格式的过程中就有可能丢失许多信息;如果原来的索引文件保存了许多用户配置选项和优化措施,在新的索引系统中这些数据可能无法利用。

这类问题没有绝对完美的解决办法,但是我们可以采取一些措施,使得升级文件格式带来的负面影响尽可能小。Java串行化(Serialization)有着简单易用的特点,日益成为一种保存文件的重要手段,有鉴于此,下面我们就来看看在软件版本变更过程中,通过Java串行化保存的文件如何保持兼容性。

二、Java串行化

Java串行化有许多优点:

●容易使用。

●如果一个对象连接到其他对象,串行化机制会保存所有相关的对象。

●如果某个对象出现多次,串行化机制只保存一次。这一点极为重要,它不仅减小了文件空间,而且即使代码写得不是很老练,也不必担心会出现无限循环(一个不老练的例子是,用递归的方式保存各个对象,却又未能有效审计哪些对象已经保存,这时就有可能陷入永无终止的循环)。

遗憾的是,Java串行化机制定义的文件格式似乎很脆弱,只要稍微改动一下类的定义,原来保存的对象就可能无法读取。例如,下面是一个简单的类定义:

public class Save implements Serializable
{
String name;

public void save() throws IOException
{
FileOutputStream f = new FileOutputStream("foo");
ObjectOutputStream oos = new ObjectOutputStream(f);
oos.writeObject(this);
oos.close();
}
}

如果在这个类定义中增加一个域,例如final int val = 7;,再来读取原来保存的对象,就会出现下面的异常:

java.io.InvalidClassException:
Save; local class incompatible:
stream classdesc serialVersionUID = -2805284943658356093,
local class serialVersionUID = 3419534311899376629

上例异常信息中的数字串表示类定义里各种属性的编码值:

●类的名字(Save)。

●域的名字(name)。

●方法的名字(Save)。

●已实现的接口(Serializable)。

改动上述任意一项内容(无论是增加或删除),都会引起编码值变化,从而引起类似的异常警报。这个数字序列称为“串行化版本统一标识符”(serial version universal identifier),简称UID。解决这个问题的办法是在类里面新增一个域serialVersionUID,强制类仍旧使用原来的UID。新增的域必须是:

●static:该域定义的属性作用于整个类,而非特定的对象。

●final:保证代码运行期间该域不会被修改。

●long:它是一个64位的数值。

也就是说,新增的serialVersionUID必须定义成下面这种形式:static final long serialVersionUID=-2805284943658356093L;。其中数字后面加上的L表示这是一个long值。

当然,改动之后的类不一定能够和原来的对象兼容。例如,如果把一个域的定义从String改成了int,执行逆-串行化操作时系统就不知道如何处理该值,显示出错误信息:java.io.InvalidClassException: Save; incompatible types for field name。

Java串行化规范(http://java.sun.com/j2se/1.4.1/docs/guide/

serialization/spec/serialTOC.doc.html)提供了有关兼容的改动(http://java.sun.com/j2se/1.4.1/docs/

guide/serialization/spec/version.doc7.html)和不兼容改动(http://java.sun.com/j2se/1.4.1/docs/guide/

serialization/spec/version.doc8.html)的清单,这些清单指出了对类作了哪些改动之后仍可能读取原来串行化的数据。具体细节比较复杂,但了解其主要机制还是很容易的:

简而言之,如果文件中确实保存了所有必需的数据,那么仍有可能读取该文件,当然前提是必须处理好串行化的UID。

三、引入版本编号

许多程序都在无意之中作出了这样的假设:这种文件格式是我要用到的最后一种格式,以后不再需要制定新的格式,现在要做的是处理好在此之前的各种格式。这种程序会试图读取格式版本更高的文件,操作进行到一半才发现某些不能识别的数据,然后就是突然崩溃。如果文件包含了大量的元数据(描述文件本身的数据),处理起来就要容易得多。

在Java中,每一个域都由其名称显式标明,只要文件的改动不是很大(只添加了域,没有被删除或作重大更改的域),可以想象,用老软件来读取新文件格式不是什么难事,虽然有可能丢失一些信息,但可以搞清楚文件的基本情况。

文件格式随着程序功能的改变而改变。理想情况下,程序应当做到既向后兼容(新的版本能够按照老版本的格式读取,甚至可能允许更新),同时做到向前兼容(较老的软件能够识别和处理新版的文件格式)。

通常,文件的版本无法从表面上一眼看出。大多数程序不会因为文件的版本不同而更改文件扩展名,而且目前尚无统一的标记文件版本的办法。因此,有关文件格式的版本声明只能在文件本身之内进行。如果你现在使用的文件格式还不包含版本声明,最好在下次把文件升级成一个不兼容的版本时马上加入版本标记,或者寻求一种在当前文件格式中加入版本标记但不会带来负面影响的办法。

版本信息一般在文件的开头声明,这是因为程序必须在处理文件之前首先检查文件的版本,除非确定了文件的版本,否则不必读取文件的其余部分。

按照惯例,文件版本编号包含两个部分:主版本编号和次版本编号。一个特定版本的程序应当有最适合它处理的主-次版本号;主版本号变化意味着文件格式的重大变化,要继续使用已经非常困难,必须作出重大修改才能升级到新的版本。

文件的主次版本号之前往往还可以加入另一项内容,称为“魔术数字”,它的作用就是保证程序处理的文件类型不会有误(因为文件扩展名有可能不能唯一地标明文件类型)。例如,Java的类文件总是以下列字节内容开头(十六进制):CA FE BA BE。目前还没有这类数字的统一注册机构,不过UNIX在/etc/magic下提供了一个清单(但并不完整)。魔术数字一般有四个字节,取值范围很大,所以一般不必担心会出现取值冲突的情形。

在编写和维护必须读/写文件的代码时,注意代码的向前/向后兼容性是非常必要的。在处理文件的代码中首先读取文件版本,然后根据版本号将文件剩余内容传递给适当的处理方法;如果文件的版本太老,已不再支持,程序应当给出明确的提示。

四、结束语

文件格式设计是一个极其重要的话题,但本文还有许多细节问题尚未涉及。例如,对于大型文件,我们需要随机访问,而不是从前向后依次读取文件内容的顺序访问,这样就不必为了访问文件最后几个字节而读取整个文件。无论是XML还是Java串行化对这类随机访问的支持都不是很理想,而且这类文件格式的发展变化比普通文件更难管理,因为他们依赖于字节级的访问,稍微改动一下文件格式就可能导致不兼容。

如果要让文件具有ACID特性――Atomicity、Consistency、Isolation和Durability,即原子性、一致性、隔离性、持久性,问题更加复杂。ACID与事务的概念密切相关,支持多用户同时访问一个文件。对于这类文件,可以考虑采用某种小型的数据库系统,例如Birdstep或Sleepycat。不过这已经进入了文件格式管理的另一个领域,既涉及到数据库管理软件的版本,也涉及到数据模式设计的版本。

撇开这些复杂的问题不谈,在实践中,很多时候我们只需简单的文件来保存数据,而且不会出现多用户并发访问,可以一次性地处理整个文件(或者至少适合使用顺序访问方式)。对于这些情形,最好在设计文件格式时就考虑版本问题,在日后的运行、维护中一定会带来不少方便。

【阅读原文】《
71EA82}/session_id~{A3FE3724-09D8-4AC2-B35E-21558DB8D582}/content/index.

asp”>Software Maintenance File Format Evolution in Java》

和银狐999聊工作流

三月 27, 2005

多谢胡长城的帮助,解答了我的很多问题,在此再次表示谢意.

gray:我 maroon:胡

我刚才接触工作流没多久,问的可能比较初级一点,请别见怪^_^.我看到您写的工作流星光一文,在里面比较了hardcode和使用工作流之间的不同,我想请问一下,如果是使用工作流的话,开发者主要完成的任务有哪几部分?您比较时使用的那张图里面讲到了数据流程DB,我想请问一下这里的数据流程DB中主要包括了哪些信息

这怎么说,使用工作流以后;将具体某个处理环节的”"业务逻辑 和 运转逻辑 分开.

这样,在开发的时候,可以更多的考虑 业务处理逻辑,而可以不关心这个活动的任务处理之后,其工作流程问题.

业务逻辑和运转逻辑分开,嗯,我想请问一下,这个是不是和java的web framework的功能类似呢,比如Struts的action control这种,只是工作流将这部分从代码中抽取出来进行管理了,可以这样理解吗?

恩,可以这么形象的理解吧._ 也许这样简单些.

嗯,那我还想问一下,就是开始问的第二个问题,其实我只是想知道这里的数据流程DB里面除了一些流程定义的数据以外,是不是还包括了用户管理的这些数据呢?

用户管理,这个不属于工作流的标准的数据.一个完成得工作流管理系统是包含用户(或者说组织模型),权限等功能.―― 但是,从狭义的角度上说,工作流关系的数据,并不包含用户数据。

嗯,不过我看了一些工作流引擎,似乎有将用户的组织模型嵌入在里面的样子,这个是和特定的工作流引擎相关的吗?如果没有的话,那要是涉及到权限控制来决定流程的时候怎么办,是工作流引擎通过和应用交互取得权限信息以后再来控制流程转向吗?

怎么说了,工作流引擎必然会牵涉到组织模型(但是,这部代表一个工作流引擎系统就必须包含完整组织模型模块,其可以通过一些适配的方式处理)。

至于权限。这个就是各个系统处理机制不一样了,会有所不同。

哦,好的.还想请问一下,关于工作流引擎和应用交互的接口3,这个方面有什么标准的吗,还是根据各个工作流引擎不同有各自的接口的?

这个wfmc给了一些标准规范。不过基本上各个厂家,都是自己的实现机制。

哦,知道了.我现在是和另外几个同学一起创业,我们想做一点EAI方面的产品,考虑到了一些集成方面的问题,所以想使用工作流,您觉得如果是EAI的话使用工作流可能要考虑哪些方面的问题呢,还有工作流引擎选型的时候要注意些什么呢?

创业?EAI?—- 这个方向可有些可怕哦,呵呵。

嗯?为什么说可怕?

因为目前国内eai市场好像很狭小。

但工作流呢,又太泛滥。

嗯,我们也不完全是定位在这个方面吧,更多的考虑可能是知识管理这一块,当然我们也没什么经验,只是在摸索着走

嗯,工作流好像是很泛滥的样子,就是因为到处都看到,所以我们也想尝试一下

^_^,如果是EAI中应用工作流引擎的话,选型您有没有什么推荐?

选型?你的意思是你要使用open source的?

呃,呵呵,我们从来没想过用商用的,因为资金有限:P

呵呵。

看来你要很失望了,从选型这个角度来讲,估计开源哪一个都不适合。――

但是,那几个你可以参考参考倒是可以

jbpm可能更符合你们的定位。

jbpm,嗯,好像要上ejb啊那个,有没有轻量级点的?

呵呵,不用ejb啊。

不用的吗,呵呵,明白了.嗯,还想到一个问题,就是如果按照刚刚那种说法的话,使用工作流的同时再使用Struts或者Webwork这些mvc框架是不是有些重复了呢?如果不是的话,那么这里mvc还要控制什么呢?

呵呵,workflow和web framework这个是两个不同的层面问题―― 没有任何可比性。

唔,这样的吗,那是说workflow处在更高的层次吗?

不是的。这两个是两个不同的视角划分;

不同的视角划分,嗯,不是很明白,嗯,先这样吧.还有最后一个问题,就是我看到一些工作流管理系统中涉及到了表单的设计,这个部分算是业务数据了吧,是不是一般的工作流系统只要求流程数据的处理,像这种表单设计什么的不要也可以的呢?

呵呵,怎么说呢。表单设计跟workflow也没有任何关系;当然很多系统有,只是为了让系统功能更加丰富而以。

1 定义头和根元素部署描述符文件就像所有XML文件一样,必须以一个XML头开始。这个头声明可以使用的XML版本并给出文件的字符编码。
DOCYTPE声明必须立即出现在此头之后。这个声明告诉服务器适用的servlet规范的版本(如2.2或2.3)并指定管理此文件其余部分内容的语法的DTD(Document Type Definition,文档类型定义)。
所有部署描述符文件的顶层(根)元素为web-app。请注意,XML元素不像HTML,他们是大小写敏感的。因此,web-App和WEB-APP都是不合法的,web-app必须用小写。

2 部署描述符文件内的元素次序

XML元素不仅是大小写敏感的,而且它们还对出现在其他元素中的次序敏感。例如,XML头必须是文件中的第一项,DOCTYPE声明必须是第二项,而web-app元素必须是第三项。在web-app元素内,元素的次序也很重要。服务器不一定强制要求这种次序,但它们允许(实际上有些服务器就是这样做的)完全拒绝执行含有次序不正确的元素的Web应用。这表示使用非标准元素次序的web.xml文件是不可移植的。
下面的列表给出了所有可直接出现在web-app元素内的合法元素所必需的次序。例如,此列表说明servlet元素必须出现在所有servlet-mapping元素之前。请注意,所有这些元素都是可选的。因此,可以省略掉某一元素,但不能把它放于不正确的位置。
l icon icon元素指出IDE和GUI工具用来表示Web应用的一个和两个图像文件的位置。
l display-name display-name元素提供GUI工具可能会用来标记这个特定的Web应用的一个名称。
l description description元素给出与此有关的说明性文本。
l context-param context-param元素声明应用范围内的初始化参数。
l filter 过滤器元素将一个名字与一个实现javax.servlet.Filter接口的类相关联。
l filter-mapping 一旦命名了一个过滤器,就要利用filter-mapping元素把它与一个或多个servlet或JSP页面相关联。
l listener servlet API的版本2.3增加了对事件监听程序的支持,事件监听程序在建立、修改和删除会话或servlet环境时得到通知。Listener元素指出事件监听程序类。
l servlet 在向servlet或JSP页面制定初始化参数或定制URL时,必须首先命名servlet或JSP页面。Servlet元素就是用来完成此项任务的。
l servlet-mapping 服务器一般为servlet提供一个缺省的URL:http://host/webAppPrefix/servlet/ServletName。但是,常常会更改这个URL,以便servlet可以访问初始化参数或更容易地处理相对URL。在更改缺省URL时,使用servlet-mapping元素。
l session-config 如果某个会话在一定时间内未被访问,服务器可以抛弃它以节省内存。可通过使用HttpSession的setMaxInactiveInterval方法明确设置单个会话对象的超时值,或者可利用session-config元素制定缺省超时值。
l mime-mapping 如果Web应用具有想到特殊的文件,希望能保证给他们分配特定的MIME类型,则mime-mapping元素提供这种保证。
l welcom-file-list welcome-file-list元素指示服务器在收到引用一个目录名而不是文件名的URL时,使用哪个文件。
l error-page error-page元素使得在返回特定HTTP状态代码时,或者特定类型的异常被抛出时,能够制定将要显示的页面。
l taglib taglib元素对标记库描述符文件(Tag Libraryu Descriptor file)指定别名。此功能使你能够更改TLD文件的位置,而不用编辑使用这些文件的JSP页面。
l resource-env-ref resource-env-ref元素声明与资源相关的一个管理对象。
l resource-ref resource-ref元素声明一个资源工厂使用的外部资源。
l security-constraint security-constraint元素制定应该保护的URL。它与login-config元素联合使用
l login-config 用login-config元素来指定服务器应该怎样给试图访问受保护页面的用户授权。它与sercurity-constraint元素联合使用。
l security-role security-role元素给出安全角色的一个列表,这些角色将出现在servlet元素内的security-role-ref元素的role-name子元素中。分别地声明角色可使高级IDE处理安全信息更为容易。
l env-entry env-entry元素声明Web应用的环境项。
l ejb-ref ejb-ref元素声明一个EJB的主目录的引用。
l ejb-local-ref ejb-local-ref元素声明一个EJB的本地主目录的应用。

3 分配名称和定制的UL

在web.xml中完成的一个最常见的任务是对servlet或JSP页面给出名称和定制的URL。用servlet元素分配名称,使用servlet-mapping元素将定制的URL与刚分配的名称相关联。
3.1 分配名称
为了提供初始化参数,对servlet或JSP页面定义一个定制URL或分配一个安全角色,必须首先给servlet或JSP页面一个名称。可通过servlet元素分配一个名称。最常见的格式包括servlet-name和servlet-class子元素(在web-app元素内),如下所示:
<servlet>
<servlet-name>Test</servlet-name>
<servlet-class>moreservlets.TestServlet</servlet-class>
</servlet>

这表示位于WEB-INF/classes/moreservlets/TestServlet的servlet已经得到了注册名Test。给servlet一个名称具有两个主要的含义。首先,初始化参数、定制的URL模式以及其他定制通过此注册名而不是类名引用此servlet。其次,可在URL而不是类名中使用此名称。因此,利用刚才给出的定义,URL http://host/webAppPrefix/servlet/Test 可用于 http://host/webAppPrefix/servlet/moreservlets.TestServlet 的场所。
请记住:XML元素不仅是大小写敏感的,而且定义它们的次序也很重要。例如,web-app元素内所有servlet元素必须位于所有servlet-mapping元素(下一小节介绍)之前,而且还要位于5.6节和5.11节讨论的与过滤器或文档相关的元素(如果有的话)之前。类似地,servlet的servlet-name子元素也必须出现在servlet-class之前。5.2节”部署描述符文件内的元素次序”将详细介绍这种必需的次序。
例如,程序清单5-1给出了一个名为TestServlet的简单servlet,它驻留在moreservlets程序包中。因为此servlet是扎根在一个名为deployDemo的目录中的Web应用的组成部分,所以TestServlet.class放在deployDemo/WEB-INF/classes/moreservlets中。程序清单5-2给出将放置在deployDemo/WEB-INF/内的web.xml文件的一部分。此web.xml文件使用servlet-name和servlet-class元素将名称Test与TestServlet.class相关联。图5-1和图5-2分别显示利用缺省URL和注册名调用TestServlet时的结果。

程序清单5-1 TestServlet.java
package moreservlets;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

/** Simple servlet used to illustrate servlet naming
* and custom URLs.
* <P>
* Taken from More Servlets and JavaServer Pages
* from Prentice Hall and Sun Microsystems Press,
* http://www.moreservlets.com/.
* © 2002 Marty Hall; may be freely used or adapted.
*/

public class TestServlet extends HttpServlet {
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
response.setContentType(”text/html”);
PrintWriter out = response.getWriter();
String uri = request.getRequestURI();
out.println(ServletUtilities.headWithTitle(”Test Servlet”) +
“<BODY BGCOLOR=\”#FDF5E6\”>\n” +
“<H2>URI: ” + uri + “</H2>\n” +
“</BODY></HTML>”);
}
}

程序清单5-2 web.xml(说明servlet名称的摘录)
<?xml version=”1.0″ encoding=”ISO-8859-1″?>
<!DOCTYPE web-app
PUBLIC “-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN”
http://java.sun.com/dtd/web-app_2_3.dtd“>

<web-app>
<!– … –>
<servlet>
<servlet-name>Test</servlet-name>
<servlet-class>moreservlets.TestServlet</servlet-class>
</servlet>
<!– … –>
</web-app>

3.2 定义定制的URL
大多数服务器具有一个缺省的serlvet URL:
http://host/webAppPrefix/servlet/packageName.ServletName。虽然在开发中使用这个URL很方便,但是我们常常会希望另一个URL用于部署。例如,可能会需要一个出现在Web应用顶层的URL(如,http://host/webAppPrefix/Anyname),并且在此URL中没有servlet项。位于顶层的URL简化了相对URL的使用。此外,对许多开发人员来说,顶层URL看上去比更长更麻烦的缺省URL更简短。
事实上,有时需要使用定制的URL。比如,你可能想关闭缺省URL映射,以便更好地强制实施安全限制或防止用户意外地访问无初始化参数的servlet。如果你禁止了缺省的URL,那么你怎样访问servlet呢?这时只有使用定制的URL了。
为了分配一个定制的URL,可使用servlet-mapping元素及其servlet-name和url-pattern子元素。Servlet-name元素提供了一个任意名称,可利用此名称引用相应的servlet;url-pattern描述了相对于Web应用的根目录的URL。url-pattern元素的值必须以斜杠(/)起始。
下面给出一个简单的web.xml摘录,它允许使用URL http://host/webAppPrefix/UrlTest而不是http://host/webAppPrefix/servlet/Test
http://host/webAppPrefix/servlet/moreservlets.TestServlet。请注意,仍然需要XML头、DOCTYPE声明以及web-app封闭元素。此外,可回忆一下,XML元素出现地次序不是随意的。特别是,需要把所有servlet元素放在所有servlet-mapping元素之前。
<servlet>
<servlet-name>Test</servlet-name>
<servlet-class>moreservlets.TestServlet</servlet-class>
</servlet>
<!– … –>
<servlet-mapping>
<servlet-name>Test</servlet-name>
<url-pattern>/UrlTest</url-pattern>
</servlet-mapping>
URL模式还可以包含通配符。例如,下面的小程序指示服务器发送所有以Web应用的URL前缀开始,以..asp结束的请求到名为BashMS的servlet。
<servlet>
<servlet-name>BashMS</servlet-name>
<servlet-class>msUtils.ASPTranslator</servlet-class>
</servlet>
<!– … –>
<servlet-mapping>
<servlet-name>BashMS</servlet-name>
<url-pattern>/*.asp</url-pattern>
</servlet-mapping>
3.3 命名JSP页面
因为JSP页面要转换成sevlet,自然希望就像命名servlet一样命名JSP页面。毕竟,JSP页面可能会从初始化参数、安全设置或定制的URL中受益,正如普通的serlvet那样。虽然JSP页面的后台实际上是servlet这句话是正确的,但存在一个关键的猜疑:即,你不知道JSP页面的实际类名(因为系统自己挑选这个名字)。因此,为了命名JSP页面,可将jsp-file元素替换为servlet-calss元素,如下所示:
<servlet>
<servlet-name>Test</servlet-name>
<jsp-file>/TestPage.jsp</jsp-file>
</servlet>
命名JSP页面的原因与命名servlet的原因完全相同:即为了提供一个与定制设置(如,初始化参数和安全设置)一起使用的名称,并且,以便能更改激活JSP页面的URL(比方说,以便多个URL通过相同页面得以处理,或者从URL中去掉.jsp扩展名)。但是,在设置初始化参数时,应该注意,JSP页面是利用jspInit方法,而不是init方法读取初始化参数的。
例如,程序清单5-3给出一个名为TestPage.jsp的简单JSP页面,它的工作只是打印出用来激活它的URL的本地部分。TestPage.jsp放置在deployDemo应用的顶层。程序清单5-4给出了用来分配一个注册名PageName,然后将此注册名与http://host/webAppPrefix/UrlTest2/anything 形式的URL相关联的web.xml文件(即,deployDemo/WEB-INF/web.xml)的一部分。

程序清单5-3 TestPage.jsp
<!DOCTYPE HTML PUBLIC “-//W3C//DTD HTML 4.0 Transitional//EN”>
<HTML>
<HEAD>
<TITLE>
JSP Test Page
</TITLE>
</HEAD>
<BODY BGCOLOR=”#FDF5E6″>
<H2>URI: <%= request.getRequestURI() %></H2>
</BODY>
</HTML>

程序清单5-4 web.xml(说明JSP页命名的摘录)
<?xml version=”1.0″ encoding=”ISO-8859-1″?>
<!DOCTYPE web-app
PUBLIC “-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN”
http://java.sun.com/dtd/web-app_2_3.dtd“>

<web-app>
<!– … –>
<servlet>
<servlet-name>PageName</servlet-name>
<jsp-file>/TestPage.jsp</jsp-file>
</servlet>
<!– … –>
<servlet-mapping>
<servlet-name> PageName </servlet-name>
<url-pattern>/UrlTest2/*</url-pattern>
</servlet-mapping>
<!– … –>
</web-app>

4 禁止激活器servlet

对servlet或JSP页面建立定制URL的一个原因是,这样做可以注册从init(servlet)或jspInit(JSP页面)方法中读取得初始化参数。但是,初始化参数只在是利用定制URL模式或注册名访问servlet或JSP页面时可以使用,用缺省URL http://host/webAppPrefix/servlet/ServletName 访问时不能使用。因此,你可能会希望关闭缺省URL,这样就不会有人意外地调用初始化servlet了。这个过程有时称为禁止激活器servlet,因为多数服务器具有一个用缺省的servlet URL注册的标准servlet,并激活缺省的URL应用的实际servlet。
有两种禁止此缺省URL的主要方法:
l 在每个Web应用中重新映射/servlet/模式。
l 全局关闭激活器servlet。
重要的是应该注意到,虽然重新映射每个Web应用中的/servlet/模式比彻底禁止激活servlet所做的工作更多,但重新映射可以用一种完全可移植的方式来完成。相反,全局禁止激活器servlet完全是针对具体机器的,事实上有的服务器(如ServletExec)没有这样的选择。下面的讨论对每个Web应用重新映射/servlet/ URL模式的策略。后面提供在Tomcat中全局禁止激活器servlet的详细内容。
4.1 重新映射/servlet/URL模式
在一个特定的Web应用中禁止以http://host/webAppPrefix/servlet/ 开始的URL的处理非常简单。所需做的事情就是建立一个错误消息servlet,并使用前一节讨论的url-pattern元素将所有匹配请求转向该servlet。只要简单地使用:
<url-pattern>/servlet/*</url-pattern>
作为servlet-mapping元素中的模式即可。
例如,程序清单5-5给出了将SorryServlet servlet(程序清单5-6)与所有以http://host/webAppPrefix/servlet/ 开头的URL相关联的部署描述符文件的一部分。

程序清单5-5 web.xml(说明JSP页命名的摘录)
<?xml version=”1.0″ encoding=”ISO-8859-1″?>
<!DOCTYPE web-app
PUBLIC “-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN”
http://java.sun.com/dtd/web-app_2_3.dtd“>

<web-app>
<!– … –>
<servlet>
<servlet-name>Sorry</servlet-name>
<servlet-class>moreservlets.SorryServlet</servlet-class>
</servlet>
<!– … –>
<servlet-mapping>
<servlet-name> Sorry </servlet-name>
<url-pattern>/servlet/*</url-pattern>
</servlet-mapping>
<!– … –>
</web-app>

程序清单5-6 SorryServlet.java
package moreservlets;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

/** Simple servlet used to give error messages to
* users who try to access default servlet URLs
* (i.e., http://host/webAppPrefix/servlet/ServletName)
* in Web applications that have disabled this
* behavior.
* <P>
* Taken from More Servlets and JavaServer Pages
* from Prentice Hall and Sun Microsystems Press,
* http://www.moreservlets.com/.
* © 2002 Marty Hall; may be freely used or adapted.
*/

public class SorryServlet extends HttpServlet {
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
response.setContentType(”text/html”);
PrintWriter out = response.getWriter();
String title = “Invoker Servlet Disabled.”;
out.println(ServletUtilities.headWithTitle(title) +
“<BODY BGCOLOR=\”#FDF5E6\”>\n” +
“<H2>” + title + “</H2>\n” +
“Sorry, access to servlets by means of\n” +
“URLs that begin with\n” +
http://host/webAppPrefix/servlet/\n” +
“has been disabled.\n” +
“</BODY></HTML>”);
}

public void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}

4.2 全局禁止激活器:Tomcat
Tomcat 4中用来关闭缺省URL的方法与Tomcat 3中所用的很不相同。下面介绍这两种方法:
1.禁止激活器: Tomcat 4
Tomcat 4用与前面相同的方法关闭激活器servlet,即利用web.xml中的url-mapping元素进行关闭。不同之处在于Tomcat使用了放在install_dir/conf中的一个服务器专用的全局web.xml文件,而前面使用的是存放在每个Web应用的WEB-INF目录中的标准web.xml文件。
因此,为了在Tomcat 4中关闭激活器servlet,只需在install_dir/conf/web.xml中简单地注释出/servlet/* URL映射项即可,如下所示:
<!–
<servlet-mapping>
<servlet-name>invoker</servlet-name>
<url-pattern>/servlet/*</url-pattern>
</servlet-mapping>
–>
再次提醒,应该注意这个项是位于存放在install_dir/conf的Tomcat专用的web.xml文件中的,此文件不是存放在每个Web应用的WEB-INF目录中的标准web.xml。
2.禁止激活器:Tomcat3
在Apache Tomcat的版本3中,通过在install_dir/conf/server.xml中注释出InvokerInterceptor项全局禁止缺省servlet URL。例如,下面是禁止使用缺省servlet URL的server.xml文件的一部分。
<!–
<RequsetInterceptor
className=”org.apache.tomcat.request.InvokerInterceptor”
debug=”0″ prefix=”/servlet/” />
–>

5 初始化和预装载servlet与JSP页面

这里讨论控制servlet和JSP页面的启动行为的方法。特别是,说明了怎样分配初始化参数以及怎样更改服务器生存期中装载servlet和JSP页面的时刻。
5.1 分配servlet初始化参数
利用init-param元素向servlet提供初始化参数,init-param元素具有param-name和param-value子元素。例如,在下面的例子中,如果initServlet servlet是利用它的注册名(InitTest)访问的,它将能够从其方法中调用getServletConfig().getInitParameter(”param1″)获得”Value 1″,调用getServletConfig().getInitParameter(”param2″)获得”2″。
<servlet>
<servlet-name>InitTest</servlet-name>
<servlet-class>moreservlets.InitServlet</servlet-class>
<init-param>
<param-name>param1</param-name>
<param-value>value1</param-value>
</init-param>
<init-param>
<param-name>param2</param-name>
<param-value>2</param-value>
</init-param>
</servlet>
在涉及初始化参数时,有几点需要注意:
l 返回值。GetInitParameter的返回值总是一个String。因此,在前一个例子中,可对param2使用Integer.parseInt获得一个int。
l JSP中的初始化。JSP页面使用jspInit而不是init。JSP页面还需要使用jsp-file元素代替servlet-class。
l 缺省URL。初始化参数只在通过它们的注册名或与它们注册名相关的定制URL模式访问Servlet时可以使用。因此,在这个例子中,param1和param2初始化参数将能够在使用URL http://host/webAppPrefix/servlet/InitTest 时可用,但在使用URL http://host/webAppPrefix/servlet/myPackage.InitServlet 时不能使用。
例如,程序清单5-7给出一个名为InitServlet的简单servlet,它使用init方法设置firstName和emailAddress字段。程序清单5-8给出分配名称InitTest给servlet的web.xml文件。
程序清单5-7 InitServlet.java
package moreservlets;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

/** Simple servlet used to illustrate servlet
* initialization parameters.
* <P>
* Taken from More Servlets and JavaServer Pages
* from Prentice Hall and Sun Microsystems Press,
* http://www.moreservlets.com/.
* © 2002 Marty Hall; may be freely used or adapted.
*/

public class InitServlet extends HttpServlet {
private String firstName, emailAddress;

public void init() {
ServletConfig config = getServletConfig();
firstName = config.getInitParameter(”firstName”);
emailAddress = config.getInitParameter(”emailAddress”);
}

public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
response.setContentType(”text/html”);
PrintWriter out = response.getWriter();
String uri = request.getRequestURI();
out.println(ServletUtilities.headWithTitle(”Init Servlet”) +
“<BODY BGCOLOR=\”#FDF5E6\”>\n” +
“<H2>Init Parameters:</H2>\n” +
“<UL>\n” +
“<LI>First name: ” + firstName + “\n” +
“<LI>Email address: ” + emailAddress + “\n” +
“</UL>\n” +
“</BODY></HTML>”);
}
}

程序清单5-8 web.xml(说明初始化参数的摘录)
<?xml version=”1.0″ encoding=”ISO-8859-1″?>
<!DOCTYPE web-app
PUBLIC “-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN”
http://java.sun.com/dtd/web-app_2_3.dtd“>

<web-app>
<!– … –>
<servlet>
<servlet-name>InitTest</servlet-name>
<servlet-class>moreservlets.InitServlet</servlet-class>
<init-param>
<param-name>firstName</param-name>
<param-value>Larry</param-value>
</init-param>
<init-param>
<param-name>emailAddress</param-name>
<param-value>Ellison@Microsoft.com</param-value>
</init-param>
</servlet>
<!– … –>
</web-app>

5.2 分配JSP初始化参数
给JSP页面提供初始化参数在三个方面不同于给servlet提供初始化参数。
1)使用jsp-file而不是servlet-class。因此,WEB-INF/web.xml文件的servlet元素如下所示:
<servlet>
<servlet-name>PageName</servlet-name>
<jsp-file>/RealPage.jsp</jsp-file>
<init-param>
<param-name>…</param-name>
<param-value>…</param-value>
</init-param>

</servlet>
2)几乎总是分配一个明确的URL模式。对servlet,一般相应地使用以http://host/webAppPrefix/servlet/ 开始的缺省URL。只需记住,使用注册名而不是原名称即可。这对于JSP页面在技术上也是合法的。例如,在上面给出的例子中,可用URL http://host/webAppPrefix/servlet/PageName 访问RealPage.jsp的对初始化参数具有访问权的版本。但在用于JSP页面时,许多用户似乎不喜欢应用常规的servlet的URL。此外,如果JSP页面位于服务器为其提供了目录清单的目录中(如,一个既没有index.html也没有index.jsp文件的目录),则用户可能会连接到此JSP页面,单击它,从而意外地激活未初始化的页面。因此,好的办法是使用url-pattern(5.3节)将JSP页面的原URL与注册的servlet名相关联。这样,客户机可使用JSP页面的普通名称,但仍然激活定制的版本。例如,给定来自项目1的servlet定义,可使用下面的servlet-mapping定义:
<servlet-mapping>
<servlet-name>PageName</servlet-name>
<url-pattern>/RealPage.jsp</url-pattern>
</servlet-mapping>
3)JSP页使用jspInit而不是init。自动从JSP页面建立的servlet或许已经使用了inti方法。因此,使用JSP声明提供一个init方法是不合法的,必须制定jspInit方法。
为了说明初始化JSP页面的过程,程序清单5-9给出了一个名为InitPage.jsp的JSP页面,它包含一个jspInit方法且放置于deployDemo Web应用层次结构的顶层。一般,http://host/deployDemo/InitPage.jsp 形式的URL将激活此页面的不具有初始化参数访问权的版本,从而将对firstName和emailAddress变量显示null。但是,web.xml文件(程序清单5-10)分配了一个注册名,然后将该注册名与URL模式/InitPage.jsp相关联。

程序清单5-9 InitPage.jsp
<!DOCTYPE HTML PUBLIC “-//W3C//DTD HTML 4.0 Transitional//EN”>
<HTML>
<HEAD><TITLE>JSP Init Test</TITLE></HEAD>
<BODY BGCOLOR=”#FDF5E6″>
<H2>Init Parameters:</H2>
<UL>
<LI>First name: <%= firstName %>
<LI>Email address: <%= emailAddress %>
</UL>
</BODY></HTML>
<%!
private String firstName, emailAddress;

public void jspInit() {
ServletConfig config = getServletConfig();
firstName = config.getInitParameter(”firstName”);
emailAddress = config.getInitParameter(”emailAddress”);
}
%>

程序清单5-10 web.xml(说明JSP页面的init参数的摘录)
<?xml version=”1.0″ encoding=”ISO-8859-1″?>
<!DOCTYPE web-app
PUBLIC “-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN”
http://java.sun.com/dtd/web-app_2_3.dtd“>

<web-app>
<!– … –>
<servlet>
<servlet-name>InitPage</servlet-name>
<jsp-file>/InitPage.jsp</jsp-file>
<init-param>
<param-name>firstName</param-name>
<param-value>Bill</param-value>
</init-param>
<init-param>
<param-name>emailAddress</param-name>
<param-value>gates@oracle.com</param-value>
</init-param>
</servlet>
<!– … –>
<servlet-mapping>
<servlet-name> InitPage</servlet-name>
<url-pattern>/InitPage.jsp</url-pattern>
</servlet-mapping>
<!– … –>
</web-app>

5.3 提供应用范围内的初始化参数
一般,对单个地servlet或JSP页面分配初始化参数。指定的servlet或JSP页面利用ServletConfig的getInitParameter方法读取这些参数。但是,在某些情形下,希望提供可由任意servlet或JSP页面借助ServletContext的getInitParameter方法读取的系统范围内的初始化参数。
可利用context-param元素声明这些系统范围内的初始化值。context-param元素应该包含param-name、param-value以及可选的description子元素,如下所示:
<context-param>
<param-name>support-email</param-name>
<param-value>blackhole@mycompany.com</param-value>
</context-param>
可回忆一下,为了保证可移植性,web.xml内的元素必须以正确的次序声明。但这里应该注意,context-param元素必须出现任意与文档有关的元素(icon、display-name或description)之后及filter、filter-mapping、listener或servlet元素之前。
5.4 在服务器启动时装载servlet
假如servlet或JSP页面有一个要花很长时间执行的init(servlet)或jspInit(JSP)方法。例如,假如init或jspInit方法从某个数据库或ResourceBundle查找产量。这种情况下,在第一个客户机请求时装载servlet的缺省行为将对第一个客户机产生较长时间的延迟。因此,可利用servlet的load-on-startup元素规定服务器在第一次启动时装载servlet。下面是一个例子。
<servlet>
<servlet-name> … </servlet-name>
<servlet-class> … </servlet-class> <!– Or jsp-file –>
<load-on-startup/>
</servlet>
可以为此元素体提供一个整数而不是使用一个空的load-on-startup。想法是服务器应该在装载较大数目的servlet或JSP页面之前装载较少数目的servlet或JSP页面。例如,下面的servlet项(放置在Web应用的WEB-INF目录下的web.xml文件中的web-app元素内)将指示服务器首先装载和初始化SearchServlet,然后装载和初始化由位于Web应用的result目录中的index.jsp文件产生的servlet。
<servlet>
<servlet-name>Search</servlet-name>
<servlet-class>myPackage.SearchServlet</servlet-class> <!– Or jsp-file –>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet>
<servlet-name>Results</servlet-name>
<servlet-class>/results/index.jsp</servlet-class> <!– Or jsp-file –>
<load-on-startup>2</load-on-startup>
</servlet>

6 声明过滤器

servlet版本2.3引入了过滤器的概念。虽然所有支持servlet API版本2.3的服务器都支持过滤器,但为了使用与过滤器有关的元素,必须在web.xml中使用版本2.3的DTD。
过滤器可截取和修改进入一个servlet或JSP页面的请求或从一个servlet或JSP页面发出的相应。在执行一个servlet或JSP页面之前,必须执行第一个相关的过滤器的doFilter方法。在该过滤器对其FilterChain对象调用doFilter时,执行链中的下一个过滤器。如果没有其他过滤器,servlet或JSP页面被执行。过滤器具有对到来的ServletRequest对象的全部访问权,因此,它们可以查看客户机名、查找到来的cookie等。为了访问servlet或JSP页面的输出,过滤器可将响应对象包裹在一个替身对象(stand-in object)中,比方说把输出累加到一个缓冲区。在调用FilterChain对象的doFilter方法之后,过滤器可检查缓冲区,如有必要,就对它进行修改,然后传送到客户机。
例如,程序清单5-11帝国难以了一个简单的过滤器,只要访问相关的servlet或JSP页面,它就截取请求并在标准输出上打印一个报告(开发过程中在桌面系统上运行时,大多数服务器都可以使用这个过滤器)。

程序清单5-11 ReportFilter.java
package moreservlets;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.util.*;

/** Simple filter that prints a report on the standard output
* whenever the associated servlet or JSP page is accessed.
* <P>
* Taken from More Servlets and JavaServer Pages
* from Prentice Hall and Sun Microsystems Press,
* http://www.moreservlets.com/.
* © 2002 Marty Hall; may be freely used or adapted.
*/

public class ReportFilter implements Filter {
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain)
throws ServletException, IOException {
HttpServletRequest req = (HttpServletRequest)request;
System.out.println(req.getRemoteHost() +
” tried to access ” +
req.getRequestURL() +
” on ” + new Date() + “.”);
chain.doFilter(request,response);
}

public void init(FilterConfig config)
throws ServletException {
}

public void destroy() {}
}

一旦建立了一个过滤器,可以在web.xml中利用filter元素以及filter-name(任意名称)、file-class(完全限定的类名)和(可选的)init-params子元素声明它。请注意,元素在web.xml的web-app元素中出现的次序不是任意的;允许服务器(但不是必需的)强制所需的次序,并且实际中有些服务器也是这样做的。但这里要注意,所有filter元素必须出现在任意filter-mapping元素之前,filter-mapping元素又必须出现在所有servlet或servlet-mapping元素之前。
例如,给定上述的ReportFilter类,可在web.xml中作出下面的filter声明。它把名称Reporter与实际的类ReportFilter(位于moreservlets程序包中)相关联。
<filter>
<filter-name>Reporter</filter-name>
<filter-class>moresevlets.ReportFilter</filter-class>
</filter>
一旦命名了一个过滤器,可利用filter-mapping元素把它与一个或多个servlet或JSP页面相关联。关于此项工作有两种选择。
首先,可使用filter-name和servlet-name子元素把此过滤器与一个特定的servlet名(此servlet名必须稍后在相同的web.xml文件中使用servlet元素声明)关联。例如,下面的程序片断指示系统只要利用一个定制的URL访问名为SomeServletName的servlet或JSP页面,就运行名为Reporter的过滤器。
<filter-mapping>
<filter-name>Reporter</filter-name>
<servlet-name>SomeServletName</servlet-name>
</filter-mapping>
其次,可利用filter-name和url-pattern子元素将过滤器与一组servlet、JSP页面或静态内容相关联。例如,相面的程序片段指示系统只要访问Web应用中的任意URL,就运行名为Reporter的过滤器。
<filter-mapping>
<filter-name>Reporter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
例如,程序清单5-12给出了将ReportFilter过滤器与名为PageName的servlet相关联的web.xml文件的一部分。名字PageName依次又与一个名为TestPage.jsp的JSP页面以及以模式http://host/webAppPrefix/UrlTest2/ 开头的URL相关联。TestPage.jsp的源代码已经JSP页面命名的谈论在前面的3节”分配名称和定制的URL”中给出。事实上,程序清单5-12中的servlet和servlet-name项从该节原封不动地拿过来的。给定这些web.xml项,可看到下面的标准输出形式的调试报告(换行是为了容易阅读)。
audit.irs.gov tried to access
http://mycompany.com/deployDemo/UrlTest2/business/tax-plan.html
on Tue Dec 25 13:12:29 EDT 2001.

程序清单5-12 Web.xml(说明filter用法的摘录)
<?xml version=”1.0″ encoding=”ISO-8859-1″?>
<!DOCTYPE web-app
PUBLIC “-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN”
http://java.sun.com/dtd/web-app_2_3.dtd“>

<web-app>
<filter>
<filter-name>Reporter</filter-name>
<filter-class>moresevlets.ReportFilter</filter-class>
</filter>
<!– … –>
<filter-mapping>
<filter-name>Reporter</filter-name>
<servlet-name>PageName</servlet-name>
</filter-mapping>
<!– … –>
<servlet>
<servlet-name>PageName</servlet-name>
<jsp-file>/RealPage.jsp</jsp-file>
</servlet>
<!– … –>
<servlet-mapping>
<servlet-name> PageName </servlet-name>
<url-pattern>/UrlTest2/*</url-pattern>
</servlet-mapping>
<!– … –>
</web-app>

7 指定欢迎页

假如用户提供了一个像http://host/webAppPrefix/directoryName/ 这样的包含一个目录名但没有包含文件名的URL,会发生什么事情呢?用户能得到一个目录表?一个错误?还是标准文件的内容?如果得到标准文件内容,是index.html、index.jsp、default.html、default.htm或别的什么东西呢?
Welcome-file-list元素及其辅助的welcome-file元素解决了这个模糊的问题。例如,下面的web.xml项指出,如果一个URL给出一个目录名但未给出文件名,服务器应该首先试用index.jsp,然后再试用index.html。如果两者都没有找到,则结果有赖于所用的服务器(如一个目录列表)。
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
虽然许多服务器缺省遵循这种行为,但不一定必须这样。因此,明确地使用welcom-file-list保证可移植性是一种良好的习惯。

8 指定处理错误的页面

现在我了解到,你在开发servlet和JSP页面时从不会犯错误,而且你的所有页面是那样的清晰,一般的程序员都不会被它们的搞糊涂。但是,是人总会犯错误的,用户可能会提供不合规定的参数,使用不正确的URL或者不能提供必需的表单字段值。除此之外,其它开发人员可能不那么细心,他们应该有些工具来克服自己的不足。
error-page元素就是用来克服这些问题的。它有两个可能的子元素,分别是:error-code和exception-type。第一个子元素error-code指出在给定的HTTP错误代码出现时使用的URL。第二个子元素excpetion-type指出在出现某个给定的Java异常但未捕捉到时使用的URL。error-code和exception-type都利用location元素指出相应的URL。此URL必须以/开始。location所指出的位置处的页面可通过查找HttpServletRequest对象的两个专门的属性来访问关于错误的信息,这两个属性分别是:javax.servlet.error.status_code和javax.servlet.error.message。
可回忆一下,在web.xml内以正确的次序声明web-app的子元素很重要。这里只要记住,error-page出现在web.xml文件的末尾附近,servlet、servlet-name和welcome-file-list之后即可。

8.1 error-code元素
为了更好地了解error-code元素的值,可考虑一下如果不正确地输入文件名,大多数站点会作出什么反映。这样做一般会出现一个404错误信息,它表示不能找到该文件,但几乎没提供更多有用的信息。另一方面,可以试一下在www.microsoft.comwww.ibm.com 处或者特别是在www.bea.com 处输出未知的文件名。这是会得出有用的消息,这些消息提供可选择的位置,以便查找感兴趣的页面。提供这样有用的错误页面对于Web应用来说是很有价值得。事实上,http://www.plinko.net/404/ 就是把整个站点专门用于404错误页面这个内容。这个站点包含来自全世界最好、最糟和最搞笑的404页面。
程序清单5-13给出一个JSP页面,此页面可返回给提供位置程序名的客户机。程序清单5-14给出指定程序清单5-13作为返回404错误代码时显示的页面的web.xml。请注意,浏览器中显示的URL仍然是客户机所提供的。错误页面是一种后台实现技术。
最后一点,请记住IE5的缺省配置显然不符合HTTP规范,它忽略了服务器生成的错误消息,而是显示自己的标准出错信息。可转到其Tools菜单,选择Internet Options,单击Advanced,取消Show Friendly HTTP Error Message来解决此问题。

程序清单5-13 NotFound.jsp
<!DOCTYPE HTML PUBLIC “-//W3C//DTD HTML 4.0 Transitional//EN”>
<HTML>
<HEAD><TITLE>404: Not Found</TITLE></HEAD>
<BODY BGCOLOR=”#FDF5E6″>
<H2>Error!</H2>
I’m sorry, but I cannot find a page that matches
<%= request.getRequestURI() %> on the system. Maybe you should
try one of the following:
<UL>
<LI>Go to the server’s <A xhref=”http://blogger.org.cn/blog//” mce_href=”http://blogger.org.cn/blog//”>home page</A>.
<LI>Search for relevant pages.<BR>
<FORM ACTION=”http://www.google.com/search“>
<CENTER>
Keywords: <INPUT TYPE=”http://blogger.org.cn/blog/TEXT” NAME=”q”><BR>
<INPUT TYPE=”SUBMIT” VALUE=”Search”>
</CENTER>
</FORM>
<LI>Admire a random multiple of 404:
<%= 404*((int)(1000*Math.random())) %>.
<LI>Try a <A xhref=”http://blogger.org.cn/blog/http://www.plinko.net/404/rndindex.asp
TARGET=”http://blogger.org.cn/blog/_blank”>
random 404 error message</A>. From the amazing and
amusing plinko.net <A xhref=”http://blogger.org.cn/blog/http://www.plinko.net/404/“>
404 archive</A>.
</UL>
</BODY></HTML>

程序清单5-14 web.xml(指出HTTP错误代码的错误页面的摘录)
<?xml version=”1.0″ encoding=”ISO-8859-1″?>
<!DOCTYPE web-app
PUBLIC “-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN”
http://java.sun.com/dtd/web-app_2_3.dtd“>

<web-app>
<error-page>
<error-code>404</error-code>
<location>/NotFound.jsp</location>
</error-page>
<!– … –>
</web-app>

8.2 exception-type元素
error-code元素处理某个请求产生一个特定的HTTP状态代码时的情况。然而,对于servlet或JSP页面返回200但产生运行时异常这种同样是常见的情况怎么办呢?这正是exception-type元素要处理的情况。只需提供两样东西即可:即提供如下的一个完全限定的异常类和一个位置:
<error-page>
<exception-type>packageName.className</exception-type>
<location>/SomeURL</location>
</error-page>
这样,如果Web应用中的任何servlet或JSP页面产生一个特定类型的未捕捉到的异常,则使用指定的URL。此异常类型可以是一个标准类型,如javax.ServletException或java.lang.OutOfMemoryError,或者是一个专门针对你的应用的异常。
例如,程序清单5-15给出了一个名为DumbDeveloperException的异常类,可用它来特别标记经验较少的程序员(不是说你的开发组中一定有这种人)所犯的错误。这个类还包含一个名为dangerousComputation的静态方法,它时不时地生成这种类型的异常。程序清单5-16给出对随机整数值调用dangerousCompution的一个JSP页面。在抛出此异常时,如程序清单5-18的web.xml版本中所给出的exception-type所指出的那样,对客户机显示DDE.jsp(程序清单5-17)。图5-16和图5-17分别给出幸运和不幸的结果。

程序清单5-15 DumbDeveloperException.java
package moreservlets;

/** Exception used to flag particularly onerous
programmer blunders. Used to illustrate the
exception-type web.xml element.
* <P>
* Taken from More Servlets and JavaServer Pages
* from Prentice Hall and Sun Microsystems Press,
* http://www.moreservlets.com/.
* © 2002 Marty Hall; may be freely used or adapted.
*/

public class DumbDeveloperException extends Exception {
public DumbDeveloperException() {
super(”Duh. What was I *thinking*?”);
}

public static int dangerousComputation(int n)
throws DumbDeveloperException {
if (n < 5) {
return(n + 10);
} else {
throw(new DumbDeveloperException());
}
}
}

程序清单5-16 RiskyPage.jsp
<!DOCTYPE HTML PUBLIC “-//W3C//DTD HTML 4.0 Transitional//EN”>
<HTML>
<HEAD><TITLE>Risky JSP Page</TITLE></HEAD>
<BODY BGCOLOR=”#FDF5E6″>
<H2>Risky Calculations</H2>
<%@ page import=”moreservlets.*” %>
<% int n = ((int)(10 * Math.random())); %>
<UL>
<LI>n: <%= n %>
<LI>dangerousComputation(n):
<%= DumbDeveloperException.dangerousComputation(n) %>
</UL>
</BODY></HTML>

程序清单5-17 DDE.jsp
<!DOCTYPE HTML PUBLIC “-//W3C//DTD HTML 4.0 Transitional//EN”>
<HTML>
<HEAD><TITLE>Dumb</TITLE></HEAD>
<BODY BGCOLOR=”#FDF5E6″>
<H2>Dumb Developer</H2>
We’re brain dead. Consider using our competitors.
</BODY></HTML>

程序清单5-18 web.xml(为异常指定错误页面的摘录)
<?xml version=”1.0″ encoding=”ISO-8859-1″?>
<!DOCTYPE web-app
PUBLIC “-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN”
http://java.sun.com/dtd/web-app_2_3.dtd“>

<web-app>
<!– … –>
<servlet> … </servlet>
<!– … –>
<error-page>
<exception-type>
moreservlets.DumbDeveloperException
</exception-type>
<location>/DDE.jsp</location>
</error-page>
<!– … –>
</web-app>

9 提供安全性

利用web.xml中的相关元素为服务器的内建功能提供安全性。
9.1 指定验证的方法
使用login-confgi元素规定服务器应该怎样验证试图访问受保护页面的用户。它包含三个可能的子元素,分别是:auth-method、realm-name和form-login-config。login-config元素应该出现在web.xml部署描述符文件的结尾附近,紧跟在security-constraint元素之后。
l auth-method
login-config的这个子元素列出服务器将要使用的特定验证机制。有效值为BASIC、DIGEST、FORM和CLIENT-CERT。服务器只需要支持BASIC和FORM。
BASIC指出应该使用标准的HTTP验证,在此验证中服务器检查Authorization头。如果缺少这个头则返回一个401状态代码和一个WWW-Authenticate头。这导致客户机弹出一个用来填写Authorization头的对话框。此机制很少或不提供对攻击者的防范,这些攻击者在Internet连接上进行窥探(如通过在客户机的子网上执行一个信息包探测装置),因为用户名和口令是用简单的可逆base64编码发送的,他们很容易得手。所有兼容的服务器都需要支持BASIC验证。
DIGEST指出客户机应该利用加密Digest Authentication形式传输用户名和口令。这提供了比BASIC验证更高的防范网络截取得的安全性,但这种加密比SSL(HTTPS)所用的方法更容易破解。不过,此结论有时没有意义,因为当前很少有浏览器支持Digest Authentication,所以servlet容器不需要支持它。
FORM指出服务器应该检查保留的会话cookie并且把不具有它的用户重定向到一个指定的登陆页。此登陆页应该包含一个收集用户名和口令的常规HTML表单。在登陆之后,利用保留会话级的cookie跟踪用户。虽然很复杂,但FORM验证防范网络窥探并不比BASIC验证更安全,如果有必要可以在顶层安排诸如SSL或网络层安全(如IPSEC或VPN)等额外的保护。所有兼容的服务器都需要支持FORM验证。
CLIENT-CERT规定服务器必须使用HTTPS(SSL之上的HTTP)并利用用户的公开密钥证书(Pulic Key Certificat)对用户进行验证。这提供了防范网络截取的很强的安全性,但只有兼容J2EE的服务器需要支持它。
l realm-name
此元素只在auth-method为BASIC时使用。它指出浏览器在相应对话框标题使用的、并作为Authorization头组成部分的安全域的名称。
l form-login-config
此元素只在auth-method为FORM时适用。它指定两个页面,分别是:包含收集用户名及口令的HTML表单的页面(利用form-login-page子元素),用来指示验证失败的页面(利用form-error-page子元素)。由form-login-page给出的HTML表单必须具有一个j_security_check的ACTION属性、一个名为j_username的用户名文本字段以及一个名为j_password的口令字段。
例如,程序清单5-19指示服务器使用基于表单的验证。Web应用的顶层目录中的一个名为login.jsp的页面将收集用户名和口令,并且失败的登陆将由相同目录中名为login-error.jsp的页面报告。

程序清单5-19 web.xml(说明login-config的摘录)
<?xml version=”1.0″ encoding=”ISO-8859-1″?>
<!DOCTYPE web-app
PUBLIC “-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN”
http://java.sun.com/dtd/web-app_2_3.dtd“>

<web-app>
<!– … –>
<security-constraint> … </security-constraint>
<login-config>
<auth-method> FORM </auth-method>
<form-login-config>
<form-login-page>/login.jsp</form-login-page>
<form-error-page>/login-error.jsp</form-error-page>
</form-login-config>
</login-config>
<!– … –>
</web-app>

9.2 限制对Web资源的访问
现在,可以指示服务器使用何种验证方法了。”了不起,”你说道,”除非我能指定一个来收到保护的URL,否则没有多大用处。”没错。指出这些URL并说明他们应该得到何种保护正是security-constriaint元素的用途。此元素在web.xml中应该出现在login-config的紧前面。它包含是个可能的子元素,分别是:web-resource-collection、auth-constraint、user-data-constraint和display-name。下面各小节对它们进行介绍。
l web-resource-collection
此元素确定应该保护的资源。所有security-constraint元素都必须包含至少一个web-resource-collection项。此元素由一个给出任意标识名称的web-resource-name元素、一个确定应该保护的URL的url-pattern元素、一个指出此保护所适用的HTTP命令(GET、POST等,缺省为所有方法)的http-method元素和一个提供资料的可选description元素组成。例如,下面的Web-resource-collection项(在security-constratint元素内)指出Web应用的proprietary目录中所有文档应该受到保护。
<security-constraint>
<web-resource-coolection>
<web-resource-name>Proprietary</web-resource-name>
<url-pattern>/propritary/*</url-pattern>
</web-resource-coolection>
<!– … –>
</security-constraint>
重要的是应该注意到,url-pattern仅适用于直接访问这些资源的客户机。特别是,它不适合于通过MVC体系结构利用RequestDispatcher来访问的页面,或者不适合于利用类似jsp:forward的手段来访问的页面。这种不匀称如果利用得当的话很有好处。例如,servlet可利用MVC体系结构查找数据,把它放到bean中,发送请求到从bean中提取数据的JSP页面并显示它。我们希望保证决不直接访问受保护的JSP页面,而只是通过建立该页面将使用的bean的servlet来访问它。url-pattern和auth-contraint元素可通过声明不允许任何用户直接访问JSP页面来提供这种保证。但是,这种不匀称的行为可能让开发人员放松警惕,使他们偶然对应受保护的资源提供不受限制的访问。
l auth-constraint
尽管web-resource-collention元素质出了哪些URL应该受到保护,但是auth-constraint元素却指出哪些用户应该具有受保护资源的访问权。此元素应该包含一个或多个标识具有访问权限的用户类别role-name元素,以及包含(可选)一个描述角色的description元素。例如,下面web.xml中的security-constraint元素部门规定只有指定为Administrator或Big Kahuna(或两者)的用户具有指定资源的访问权。
<security-constraint>
<web-resource-coolection> … </web-resource-coolection>
<auth-constraint>
<role-name>administrator</role-name>
<role-name>kahuna</role-name>
</auth-constraint>
</security-constraint>
重要的是认识到,到此为止,这个过程的可移植部分结束了。服务器怎样确定哪些用户处于任何角色以及它怎样存放用户的口令,完全有赖于具体的系统。
例如,Tomcat使用install_dir/conf/tomcat-users.xml将用户名与角色名和口令相关联,正如下面例子中所示,它指出用户joe(口令bigshot)和jane(口令enaj)属于administrator和kahuna角色。
<tomcat-users>
<user name=”joe” password=”bigshot” roles=”administrator,kahuna” />
<user name=”jane” password=”enaj” roles=”kahuna” />
</tomcat-users>
l user-data-constraint
这个可选的元素指出在访问相关资源时使用任何传输层保护。它必须包含一个transport-guarantee子元素(合法值为NONE、INTEGRAL或CONFIDENTIAL),并且可选地包含一个description元素。transport-guarantee为NONE值将对所用的通讯协议不加限制。INTEGRAL值表示数据必须以一种防止截取它的人阅读它的方式传送。虽然原理上(并且在未来的HTTP版本中),在INTEGRAL和CONFIDENTIAL之间可能会有差别,但在当前实践中,他们都只是简单地要求用SSL。例如,下面指示服务器只允许对相关资源做HTTPS连接:
<security-constraint>
<!– … –>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
l display-name
security-constraint的这个很少使用的子元素给予可能由GUI工具使用的安全约束项一个名称。
9.3 分配角色名
迄今为止,讨论已经集中到完全由容器(服务器)处理的安全问题之上了。但servlet以及JSP页面也能够处理它们自己的安全问题。
例如,容器可能允许用户从bigwig或bigcheese角色访问一个显示主管人员额外紧贴的页面,但只允许bigwig用户修改此页面的参数。完成这种更细致的控制的一种常见方法是调用HttpServletRequset的isUserInRole方法,并据此修改访问。
Servlet的security-role-ref子元素提供出现在服务器专用口令文件中的安全角色名的一个别名。例如,假如编写了一个调用request.isUserInRole(”boss”)的servlet,但后来该servlet被用在了一个其口令文件调用角色manager而不是boss的服务器中。下面的程序段使该servlet能够使用这两个名称中的任何一个。
<servlet>
<!– … –>
<security-role-ref>
<role-name>boss</role-name> <!– New alias –>
<role-link>manager</role-link> <!– Real name –>
</security-role-ref>
</servlet>
也可以在web-app内利用security-role元素提供将出现在role-name元素中的所有安全角色的一个全局列表。分别地生命角色使高级IDE容易处理安全信息。

10 控制会话超时

如果某个会话在一定的时间内未被访问,服务器可把它扔掉以节约内存。可利用HttpSession的setMaxInactiveInterval方法直接设置个别会话对象的超时值。如果不采用这种方法,则缺省的超时值由具体的服务器决定。但可利用session-config和session-timeout元素来给出一个适用于所有服务器的明确的超时值。超时值的单位为分钟,因此,下面的例子设置缺省会话超时值为三个小时(180分钟)。
<session-config>
<session-timeout>180</session-timeout>
</session-config>

11 Web应用的文档化

越来越多的开发环境开始提供servlet和JSP的直接支持。例子有Borland Jbuilder Enterprise Edition、Macromedia UltraDev、Allaire JRun Studio(写此文时,已被Macromedia收购)以及IBM VisuaAge for Java等。
大量的web.xml元素不仅是为服务器设计的,而且还是为可视开发环境设计的。它们包括icon、display-name和discription等。
可回忆一下,在web.xml内以适当地次序声明web-app子元素很重要。不过,这里只要记住icon、display-name和description是web.xml的web-app元素内的前三个合法元素即可。
l icon
icon元素指出GUI工具可用来代表Web应用的一个和两个图像文件。可利用small-icon元素指定一幅16 x 16的GIF或JPEG图像,用large-icon元素指定一幅32 x 32的图像。下面举一个例子:
<icon>
<small-icon>/images/small-book.gif</small-icon>
<large-icon>/images/tome.jpg</large-icon>
</icon>
l display-name
display-name元素提供GUI工具可能会用来标记此Web应用的一个名称。下面是个例子。
<display-name>Rare Books</display-name>
l description
description元素提供解释性文本,如下所示:
<description>
This Web application represents the store developed for
rare-books.com, an online bookstore specializing in rare
and limited-edition books.
</description>

12 关联文件与MIME类型

服务器一般都具有一种让Web站点管理员将文件扩展名与媒体相关联的方法。例如,将会自动给予名为mom.jpg的文件一个image/jpeg的MIME类型。但是,假如你的Web应用具有几个不寻常的文件,你希望保证它们在发送到客户机时分配为某种MIME类型。mime-mapping元素(具有extension和mime-type子元素)可提供这种保证。例如,下面的代码指示服务器将application/x-fubar的MIME类型分配给所有以.foo结尾的文件。
<mime-mapping>
<extension>foo</extension>
<mime-type>application/x-fubar</mime-type>
</mime-mapping>
或许,你的Web应用希望重载(override)标准的映射。例如,下面的代码将告诉服务器在发送到客户机时指定.ps文件作为纯文本(text/plain)而不是作为PostScript(application/postscript)。
<mime-mapping>
<extension>ps</extension>
<mime-type>application/postscript</mime-type>
</mime-mapping>

13 定位TLD

JSP taglib元素具有一个必要的uri属性,它给出一个TLD(Tag Library Descriptor)文件相对于Web应用的根的位置。TLD文件的实际名称在发布新的标签库版本时可能会改变,但我们希望避免更改所有现有JSP页面。此外,可能还希望使用保持taglib元素的简练性的一个简短的uri。这就是部署描述符文件的taglib元素派用场的所在了。Taglib包含两个子元素:taglib-uri和taglib-location。taglib-uri元素应该与用于JSP taglib元素的uri属性的东西相匹配。Taglib-location元素给出TLD文件的实际位置。例如,假如你将文件chart-tags-1.3beta.tld放在WebApp/WEB-INF/tlds中。现在,假如web.xml在web-app元素内包含下列内容。
<taglib>
<taglib-uri>/charts.tld</taglib-uri>
<taglib-location>
/WEB-INF/tlds/chart-tags-1.3beta.tld
</taglib-location>
</taglib>
给出这个说明后,JSP页面可通过下面的简化形式使用标签库。
<%@ taglib uri=”/charts.tld” prefix=”somePrefix” %>

14 指定应用事件监听程序

应用事件监听器程序是建立或修改servlet环境或会话对象时通知的类。它们是servlet规范的版本2.3中的新内容。这里只简单地说明用来向Web应用注册一个监听程序的web.xml的用法。
注册一个监听程序涉及在web.xml的web-app元素内放置一个listener元素。在listener元素内,listener-class元素列出监听程序的完整的限定类名,如下所示:
<listener>
<listener-class>package.ListenerClass</listener-class>
</listener>
虽然listener元素的结构很简单,但请不要忘记,必须正确地给出web-app元素内的子元素的次序。listener元素位于所有的servlet元素之前以及所有filter-mapping元素之后。此外,因为应用生存期监听程序是serlvet规范的2.3版本中的新内容,所以必须使用web.xml DTD的2.3版本,而不是2.2版本。
例如,程序清单5-20给出一个名为ContextReporter的简单的监听程序,只要Web应用的Servlet-Context建立(如装载Web应用)或消除(如服务器关闭)时,它就在标准输出上显示一条消息。程序清单5-21给出此监听程序注册所需要的web.xml文件的一部分。

程序清单5-20 ContextReporterjava
package moreservlets;

import javax.servlet.*;
import java.util.*;

/** Simple listener that prints a report on the standard output
* when the ServletContext is created or destroyed.
* <P>
* Taken from More Servlets and JavaServer Pages
* from Prentice Hall and Sun Microsystems Press,
* http://www.moreservlets.com/.
* © 2002 Marty Hall; may be freely used or adapted.
*/

public class ContextReporter implements ServletContextListener {
public void contextInitialized(ServletContextEvent event) {
System.out.println(”Context created on ” +
new Date() + “.”);
}

public void contextDestroyed(ServletContextEvent event) {
System.out.println(”Context destroyed on ” +
new Date() + “.”);
}
}

程序清单5-21 web.xml(声明一个监听程序的摘录)
<?xml version=”1.0″ encoding=”ISO-8859-1″?>
<!DOCTYPE web-app
PUBLIC “-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN”
http://java.sun.com/dtd/web-app_2_3.dtd“>

<web-app>
<!– … –>
<filter-mapping> … </filter-mapping>
<listener>
<listener-class>package.ListenerClass</listener-class>
</listener>
<servlet> … </servlet>
<!– … –>
</web-app>

15 J2EE元素

本节描述用作J2EE环境组成部分的Web应用的web.xml元素。这里将提供一个简明的介绍,详细内容可以参阅http://java.sun.com/j2ee/j2ee-1_3-fr-spec.pdf的Java 2 Plantform Enterprise Edition版本1.3规范的第5章。
l distributable
distributable元素指出,Web应用是以这样的方式编程的:即,支持集群的服务器可安全地在多个服务器上分布Web应用。例如,一个可分布的应用必须只使用Serializable对象作为其HttpSession对象的属性,而且必须避免用实例变量(字段)来实现持续性。distributable元素直接出现在discription元素之后,并且不包含子元素或数据,它只是一个如下的标志。
<distributable />
l resource-env-ref
resource-env-ref元素声明一个与某个资源有关的管理对象。此元素由一个可选的description元素、一个resource-env-ref-name元素(一个相对于java:comp/env环境的JNDI名)以及一个resource-env-type元素(指定资源类型的完全限定的类),如下所示:
<resource-env-ref>
<resource-env-ref-name>
jms/StockQueue
</resource-env-ref-name>
<resource-env-ref-type>
javax.jms.Queue
</resource-env-ref-type>
</resource-env-ref>
l env-entry
env-entry元素声明Web应用的环境项。它由一个可选的description元素、一个env-entry-name元素(一个相对于java:comp/env环境JNDI名)、一个env-entry-value元素(项值)以及一个env-entry-type元素(java.lang程序包中一个类型的完全限定类名,java.lang.Boolean、java.lang.String等)组成。下面是一个例子:
<env-entry>
<env-entry-name>minAmout</env-entry-name>
<env-entry-value>100.00</env-entry-value>
<env-entry-type>minAmout</env-entry-type>
</env-entry>
l ejb-ref
ejb-ref元素声明对一个EJB的主目录的应用。它由一个可选的description元素、一个ejb-ref-name元素(相对于java:comp/env的EJB应用)、一个ejb-ref-type元素(bean的类型,Entity或Session)、一个home元素(bean的主目录接口的完全限定名)、一个remote元素(bean的远程接口的完全限定名)以及一个可选的ejb-link元素(当前bean链接的另一个bean的名称)组成。
l ejb-local-ref
ejb-local-ref元素声明一个EJB的本地主目录的引用。除了用local-home代替home外,此元素具有与ejb-ref元素相同的属性并以相同的方式使用。

问题跟踪系统

二月 25, 2005

现在项目的开发的规模越来越大,同时进行开发的人数也越来越多,同时这些人员也往往呈现出分布化办公的趋势,因此在项目开发过程中的人员之间的协同成为项目成功的一个重要因素.我们这次项目中也需要采用一个系统进行任务的分配,进度跟踪以及任务反馈等等.开始是往公文流转系统这方面想,但是考察了一些系统之后,发现我们实际需要的可能只是一个问题跟踪系统.以前,问题跟踪系统大多局限于bug的跟踪管理,而现在的外延已经有所扩大,不仅仅应用于bug的管理,也可以应用于普通的项目管理过程中.这次主要看了一下一些系统:

  • JIRA无疑是我看到的最好的一个,J2EE环境.唯一的缺点是要收费,虽然有很多的license,但是要想免费蹭着用还是要有很高的门槛的.
  • Mantis据说部署很方便,PHP的应用.不过版本号怎么才0.2都不到,有点不可靠的样子,看来要多学学Larry Ellison才行.
  • Bugzilla大名鼎鼎,用Perl编写.但是要部署在windows系统上好像还是很困难,而且据说升级的时候会有很多问题发生.
  • OnTime使用起来也还不错,界面满清楚的,不过有两个缺点,一个是它部署在IIS+ASP.NET+MS SQL Server上,限定了只能部署在windows平台上了其实就,另一点更为致命的是2004版还不支持Unicode,限定了只能用在英语国家了,呼
  • Roundup Tracker没有使用过,主要是因为它的环境太偏了,用Python的是.不过口碑好像还不错.
  • 其他还有一些如Issue Manager, issue track,zentrack,trackstudio等等

XML数据的概念建模: 方法和工具
内容:
引言
1. 关于 XML 的建模
2. 进行 XML 概念建模的一般性问题
3. 三种解决方案
4. 总结及引申
参考资料
关于作者
对本文的评价
订阅:
developerWorks 时事通讯
developerWorks 订阅
(订阅CD 和下载)

李霞joylee4u@yahoo.com.cn
在读研究生, 武汉理工大学计算机科学与技术学院
2005 年 1 月

随着XML应用开发的深入,我们需要一种统一的建模方法来针对不同的应用为 XML 数据进行概念建模。本文概览了当前处理 XML 概念建模问题的几种方法及工具,了解这些方法将为我们对实际XML应用建模提供很好的思路。

引言
XML 技术是互联网下一个发展阶段的关键技术,它已成为互联网上表现结构化和半结构化数据的标准格式和数据交换的主要标准。在这种背景下,把 XML 作为一种数据库模型并从中提取信息在数据库领域逐渐受到注意。而且,XML 技术和关系数据库技术的融合能使数据更通用,能被异构系统接受,因此成为很自然的趋势。大量的 XML 文档由于不同应用之间交换数据而临时产生,而且也存在像数据库一样需要系统可靠地管理永久性 XML 数据的需要。为此,已经有许多的研究工作致力于融合 XML 应用和关系数据库。同样,参照关系数据库设计与建模的成功经验,我们也需要一种统一的建模方法学来针对不同的应用为 XML 数据进行建模。

1. 关于 XML 的建模
提到 XML,我们自然会想到它的半结构化特征。XML 是典型的树形结构,然而这种结构既然包含了元数据和数据,目的是为了能让机器更好的理解,它的物理表现也就不那么直接的利于人的理解。其定义语言的文本描述形式阅读和理解起来都有不便,因此我们需要可视化和标准化的设计方法和工具。比如利用现有的一些工具,如 XML SPY,可以通过设计更直观的图形结构来生成物理的 XML Schema/DTD (参见图1)。这样使得设计方便一些了,但是,这仍然没有脱离实现,而更好的关注应用领域。因此,我们需要更高层的概念模型来指导对 XML 数据结构的设计。

图1 XML Spy 绘制的 Schema 结构图
图1  XML Spy 绘制的 Schema 结构图

目前常用的描述 XML 数据结构和内容的文档定义语言主要有 DTD 和 XML Schema。作为 W3C 的推荐标准,XML Schema 虽然在确认 XML 文档上非常有用,但它并不能胜任需要理解所表现数据的语义的任务。而且,直接由 XML Schema 来定义 XML 文档结构的设计方法是以 XML 为中心的,这样过早的陷于底层的实现结构了,而好的设计应该以应用为中心。为了让设计者站在一个更高的角度,从更抽象的层面去关注应用领域,最好使用概念模型。

数据库设计是数据库应用的基础和主要内容。关系数据库的设计方法学经过长期的发展已经非常成熟,它对我们设计 XML 数据库提供了很好的借鉴作用和指导意义。回顾关系数据库的建模,我们知道,传统的关系数据库的设计方法大致如下:关系数据库的建模应该先从一个特定的应用领域分析入手,进行概念建模( Conceptual Modeling ),得到概念模型(即 ER 模型),然后,将 ER 模型转换为关系模型,即逻辑模型,再生成物理模型,即实际存储的物理表结构。

而概念模型的设计是整个设计过程中一个相当重要的步骤,因为它独立于最终的开发平台和实现环境。对于不熟悉底层实现细节的用户来说,概念模型是一种容易理解、方便使用的表示形式,如数据库设计中的 ER 图、软件开发中的数据流图( DFD )、面向对象设计中的 UML 图等。同时,一个好的概念模型的设计也会为其逻辑、物理模型的设计提供了一个良好的基础。

2. 进行 XML 概念建模的一般性问题
上面我们强调了为 XML 进行概念建模的必要性,那么,这种概念模型必须要能满足 XML不同于关系数据的全新特性。很明显,XML的结构与传统的结构化数据模型存在着一些差异,因此目前尚没有一种合适的机制来生成和描述 XML 的概念模型。

XML 语言的半结构化特征给 XML 建模带来了一些细节性问题,这里以 XML Schema 为标准,由于其强大的定义功能和灵活性,给模型的抽象带来了困难。比如,XML 文档的内容是有序的,不同元素之间有特定的顺序;一个元素可由其它子元素构成,子元素有顺序和可变的数目等;一个文档的模式可能是简单的,也可能是复杂的嵌套结构;由于其半结构化的特性,XML 不能直接支持多对多的关系;XML Schema 中会涉及到不同的类型,比如简单类型和复杂类型,元素的不同实例又可能会有不同的结构;单个元素的结构也会很复杂,元素可能会含有子元素,再加上可选、必选和多值等特性,增加了其结构的复杂性;XML 文档中的元素还会包含混合内容,比如具备原子性和不具原子性的值;XML 具有的名称空间这样的特性增加了建模的复杂性,要支持它比较困难。

总之,由于 XML 数据具有关系模型不能直接表示的半结构化特征,而且相对于关系模型,XML Schema 描述的文档结构可以十分复杂和自由,所以试图用一种模型来支持 XML Schema 的所有特性,这在当前还有一定困难。

3. 三种解决方案
目前已有的 XML 概念建模的解决方案主要有3种:一种是采用 UML 来设计 XML 模式,另一种则基于扩展 ER 模型,我们暂且称之为 XER,它即是用支持 XML 特性的扩展 ER 模型来为 XML Schema 建模。还有一种办法则是另起炉灶,它是一套全新的专门针对 XML 的建模方法:AOM( Asset Oriented Modeling )方法。

3.1 UML
作为较早出现的一种解决方案,UML 能为设计 XML 标准词汇( vocabulary )提供帮助[1]。下面的图2就是一个最简单的 UML 类图,它为 XML Schema 的一个 Book 实体描述了概念模型。类 Book 含有图示的3个属性,类的属性名后的[ ]中表示的是该属性出现的次数。本例中的[1..* ]就表示一本书可以有一到多个作者。

图2 Book 的 UML 模型
图2   Book 的 UML 模型

由于本文关注的是概念建模,这里只简要介绍使用 UML 为 XML Schema 进行三层建模的方法,具体实现过程可以参考后面的资料[1,2]。利用 UML 建模的过程分为三个部分:利用 UML 的类图进行概念建模、使用 UML Profile 进行逻辑建模、利用 UML Profile 到 XML Schema 的自动映射实现物理建模,即将 UML 模型转化成 XML Schema 了。由于 UML 使用 Profile 作为扩展机制,它就容易适应某些特定的领域。通过 Profile,用户针对 XML Schema 可以定义和使用自己的元素。这就允许用户能针对 XML 的特性来改进和扩展 UML 的描述能力。

从这个简单例子,我们可以看出,这里 Book 的属性看不出顺序之分,但在 XML Schema中,排序却是重要的特性, 要使 UML 支持它需要一些技巧。而且,在此 UML 图中也不能明显区分元素和属性。结合实际,假如再给 Book 添加一个自定义为复杂类型的 Chapter 元素,UML 默认的数据类型也不支持,UML 的表达中还需要引入新实体和联系。另外,UML 还缺乏针对 XML 的一些重要概念,如:名称空间、键等。

可见,UML 无法涵盖 XML Schema 所提供的全部功能和丰富内容,对 XML 的建模细节处理得不完整。但是,在 XML 模式设计领域,如能适当地扩展 UML Profile,将弥补基本 UML 模型中的差距。

至于用 UML 来设计 XML 模式的实现工具,常见的画 UML 图的工具基本都能胜任,但是必须结合 XML 的需求对 Profile 进行必要的扩展。比如 IBM 的 Rational Rose,另外流行的 Eclipse 平台上也有众多的可视化建模插件,典型的如:EMF,hyperModel 等。

3.2 XER
XER 是一种以 ER 模型为基础的支持 XML 可视化概念建模的方法[3]。由于它基于扩展的 ER 模型,参照 ER 的组成部分,这种 XER 的组成部分也包括实体和联系等。具体来说,结合 XML 的特性,XER 的实体包括有序实体、无序实体、混合实体;XER 联系则与 XML Schema 中元素的 minOccurs、maxOccurs 取值有关;另外 XER 也支持泛化、聚合等概念。可以用代表不同 XML 特征的图元来表示这些概念。仍以有序实体 Book 为例子,下面的图3、前文的图1和清单1展示了使用 XER 为Book 的 XML Schema 进行建模的三层模型,分别是概念模型、逻辑模型、物理模型。其中图1是用 XML Spy 绘制的相应 Schema 结构图,它表述了 Book 实体的逻辑层模型。

清单1 Book 实体对应的 XML Schema-物理层


<xs:element name="BOOK">
<xs:complexType>
<xs:sequence>
<xs:element name="title" />
<xs:element name="author" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="Chapter" minOccurs="1" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="title"/>
<xs:element name="abstract"/>
<xs:element name="section" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute name="chapno" type="xs:ID"/>
</xs:complexType>
</xs:element>
</xs:sequence>
<xs:attribute name="isbn" type="xs:ID"/>
</xs:complexType>
</xs:element>

图3 XER 关系图-概念层
图3  XER 关系图-概念层

本例中,在由 XER 表达的概念模型图3中,Book 是一个复杂类型,它包含一个为复杂类型的 Chapter 元素,这里的 XER 联系的两端表达了这样的关系:一个 Book 包含一到多个 Chapter(1:M),而每一个 Chapter 都属于一个 Book(1:1)。我们可以看到,由于 XER 在原 ER 的图元上加入很多新特性,增强了 ER 基本图元的描述能力,能更好的支持对 XML Schema 的表达,它比 UML 的表达能力更丰富一些,比如图3中对排序、区分元素与属性和对键的支持。

使用 XER 方法的实现工具可以是 Visio,PowerDesigner 等能绘制 ER 模型的工具,但由于 XER 引入了新的图元,也需要对这些建模工具的图元进行扩展。目前,PowerDesigner 也提供了基于 Eclipse 的插件版本[4],以利于扩展。

3.3 AOM 方法
Assert Oriented Modeling 是一种全新的建模方法[5]。它的特点是表达能力强、图形简洁和模块化。虽然提出的时间较短,但发展很快,已基本形成了自己的体系。在 AOM 方法中,引入了一个重要概念 Asset,用它来统一表示关系数据库中严格区分的实体和联系,即实体和联系都用 Asset 来表示。联系则用弧表示。它同样也定义了一系列自己的图元,以支持 XML 的特性,包括刚提到的 Asset、弧等基本图元,还支持属性、注释、约束、键、操作等概念,AOM 模型还区分层次,支持命名空间。这里给出它的一个简单图例,足以显示它较强的表达能力。

图4 AOM 模型图例
图4  AOM 模型图例

AOM 不仅有自己的理论,还有配套的图形化建模工具 KLEEN[5]。KLEEN 已被实现为 Eclipse 平台上的一个插件,它支持基于 Asset 的模型的创建及确认,并能从概念模型生成代码。它能从 http://www.aomodeling.org 下载得到。目前 KLEEN 的最新版本是0.9.7。

3.4 比较与评价
目前三种建模方法都处在研究之中。比较而言,UML 的优点是能够较为容易地创建模型,图形简洁明了,并且能确保模型的一致性,利于 Schema 的复用。但是,UML 省略了很多底层的细节,其中某些是 XML 的重要特性,因此不够友好。XER 根据 XML 的特征对 ER 进行了合适扩展,用它来为 XML Schema 进行概念建模更适合有传统数据库设计背景的用户。但 XER 暂时还不支持 XML 的名称空间。

AOM 则可以说是一种为 XML 度身订做的建模方法,且更加开放。AOM 的出现是由于传统建模方法难以处理日益复杂的数据模型:基于 ER 建模的方法毕竟更适合结构化数据;而概念建模对于以面向对象的数据模型为基础的 UML 来说显得过于复杂,它欠缺数据库建模的一些重要概念,比如键。当然相比之下,AOM 方法不如前两者成熟,有丰富的经验可以借鉴,相对来说它的资料和技术支持都还有不足。

在工具方面,我们发现这样的趋势,可视化工具都趋向于做成 Eclipse 平台的插件,更加的开放和可扩展。

4. 总结及引申
以上我们谈的是 XML 文档的概念建模,也可以考虑将上面的几种方法引申到 XML数据库的概念建模上。借鉴关系数据库的经验,对于 XML 也有可能集成子模式,得到总体概念模式-XML 数据库模式。并且,设计 XML 模式和 XML 数据库的模式的工作也可灵活选择,可以采用自顶向下或自底向上的方法。但数据库模式将会在更抽象的层次上提供一个集成化的全局外部模式。由于同一个数据库概念模式会产生许多不同的 XML Schema,所以 XML 数据库模式更利于用户理解总体上的概念模式。

并且随着关于 XML 新的研究的开展,我们感到,在 XML 建模方面还有融合领域本体的可能性和趋势。同时,不管选择以上三种方法中的哪一种,概念建模仅仅是第一步,我们还需要考虑从 XML Schema(DTD)到其概念模型之间的双向映射算法即自动转换问题,这是得到实用设计工具的必备工作。实际上,由于 XML Schema 的复杂性,这样的转换工作有一定难度。

综上所述,我们纵览了当前处理 XML 概念建模问题的几种方法及工具,旨在抛砖引玉。其实围绕 XML 的三层建模过程还有很多细节问题,而且目前三种解决方案都还未稳定,但是了解这些方法将为我们对 XML 应用建模甚至设计自己的概念模型提供很好的思路,利于指导实际 XML 应用的设计工作。

参考资料

  1. Carlson D,Modeling XML Vocabularies with UML(Part I、II、III),http://www.xmlmodeling.com,2001
  2. IBM developerWorks 中国网站,Benoit Marchal的UML建模系列文章:使用 XML:UML、XMI 和代码生成 (共四部分), http://www-900.ibm.com/developerWorks/cn/xml/x-wxxm23/
  3. Arijit Sengupta, Sriram Mohan, Rahul Doshi. XER-Extensible Entity Relationship Modeling,http://www.idealliance.org/proceedings/xml03,2003
  4. PowerDesigner 11 beta Online Documentation
  5. http://www.aomodeling.org
关于作者
李霞, 武汉理工大学计算机科学与技术学院,武汉,430063。E-mail:joylee4u@yahoo.com.cn,研究兴趣:XML与数据库、数据建模、工作流系统等

[转]为何不用MySQL?

一月 4, 2005

注意:这篇文档写于20005月。因此,它并不能说明MySQL的最新特性。但从中我们仍然可以了解RDBMS的一些基本概念、原理,从而在实践中更好地应用数据库,同时也才能对一些不实的炒作保持必要的警惕。

为何不用MySQL

作者:Ben Adida  译者:马维达

几乎每周、有时甚至更为频繁,总有人会问我们为何不采用MySQL作为OpenACS的RDBMS(关系式数据库管理系统)。ACS Classic team(ArsDigita)也一再地在他们的论坛上遇到同样的提问。如果MySQL对于Slashdot来说足够好的话,它也一定能够用于OpenACS,不是吗?

不对。 这篇简短的论文将尝试解释为何MySQL不仅对OpenACS来说是错误的选择,它也不应被用于任何处理关键数据的系统。

RDBMS的目的

RDBMS的目的是提供一种可靠的永久存储机制,在ACID测试中具体表述了这种机制的非常严格的特性。我将直接引用Philip Greenspun的精彩解释(以Oracle作为RDBMS的代表):

原子性(Atomicity

事务的执行结果或者被全部提交、或者被全部回滚(roll back)。要么所有的变动都生效,要么就没有变动生效。假定一个用户正在编辑一条注释,Web脚本告诉数据库“将旧注释值拷贝到审计表中,并用新文本更新活动表”。如果在拷贝之后、更新之前硬盘变满的话,审计表插入就将被回滚。

一致性(Consistency

数据库从一种有效状态转换到另一种有效状态。仅在服从用户定义的完整性约束时,一个事务才是合法的。不允许非法的事务,而且,如果不能满足某完整性约束的话,该事务会被回滚。例如,假定你定义了一条规则:论坛表中的帖子必须与有效的用户ID相关联。然后你雇用了Joe Novice来编写管理页面。Joe编写了一个删除用户页面,它不会检查删除是否会产生一些无主的论坛帖子。然而像Oracle这样的RDBMS将会进行检查,并中止任何事务,如果它产生的论坛帖子为已被删除的用户所拥有的话。

隔离性(Isolation

一个事务的结果对于其他事务是不可见的,直到该事务完成为止。例如,假定你有一个显示新用户和他们的照片的页面。按照出版者的要求,在页面中每个用户都有一张面部照片,如果用户没有照片的话就显示一幅表示无照片的图像。在新用户Jane在你的站点注册的同时,老用户Bill正在查看新用户页面。处理Jane的注册的脚本会对若干表进行插入:users、mugshots、users_demographics。如果Jane的面部照片很大的话,上述插入可能会需要一些时间。如果Bill的查询在Jane的事务提交之前开始的话,Bill根本不会在他的新用户页面上看到Jane,即使在Jane的事务中,对某些表的插入已经完成。

持久性(Durability

一旦提交(完成),事务的结果将是永久性的,并能免于未来的系统和介质故障。假设你的电子商务系统将来自某消费者的定单插入数据库表中,并指示CyberCash收取该消费者500美元的费用。突然间,在你的服务器收到CyberCash的回复之前,有人绊掉了机器的电源线。在这样的情况下,Oracle将不会忘记该定单。而且,如果有程序员将咖啡洒进了磁盘驱动器中,安装一个新磁盘,并将事务回复到咖啡泼洒时为止是可能的;数据将显示你曾试图收取某人500美元,并且还不清楚在CyberCash那里发生了什么。

如果你所要的是快速的裸存储,去使用文件系统。如果你想要在多台机器间进行共享,去使用NFS。如果你想要简单的可靠性,以对付过于简单的故障,去使用镜像。想要给它们加上SQL接口?去使用MySQL

现在,如果你所要的是这样的数据存储,它能够使你的数据集的若干方面保持恒定,能够对这些数据进行复杂的操作、而永不违反上述的那些约束,能够将多个用户同时进行的局部工作彼此隔离开来,并且能够从任何种类的故障中进行平稳的恢复,那就给你自己找一个真正的RDBMS。是的,它会比MySQL文件系统慢,就像TCP比UDP慢一样,但它们却提供了更好的服务担保。

MySQL的现状与未来

构建真正的 RDBMS是一项艰巨的任务,也许比任何其他系统问题都要艰巨。市场上的大多数产品(Oracle、Sybase、PostgreSQL、Interbase)已经进行了多年的开发,有些还超过了10或15年。

MySQL的开发人员声称他们牺牲了某些特性,以保证更好的性能。尽管这或许是一种追踪非关键数据(比如点击率追踪)的有趣方法,在处理关键数据时,牺牲完全的数据完整性是不可接受的,即使是为了速度也同样如此。

MySQL成熟时,OpenACS团队很高兴对其进行距离更近的考察。但是,MySQL团队似乎并不理解真正的ACID能力的概念和重要性:MySQL Todo在一个很长的列表中提到了“事务”,其中包括了诸如“睡眠进程占用CPU吗”这样的问题。此外,MySQL手册声称MySQL将很快通过表锁的使用来实现“原子操作”,但却“没有回滚”。这是对术语“原子的”明目张胆的误用:“原子操作”意味着或者所有操作都完成,或者没有操作完成。如果没有回滚能力的话,在一组语句的中间发生的硬件或电力故障将破坏块的原子性。

回滚不只是一种便利的特性,它是可靠的数据存储的关键性基础。

有许多很好的理由使用MySQL,但对可靠的、顺从ACID的数据存储的需求却并非是其中之一。

更多的细节

l MySQL没有子查询。

对于复杂的查询, MySQL用户必须执行两次或更多的系列查询,每一次都需要在应用和数据库间进行进程间通信或网络通信。这显著地降低了MySQL的速度优势。

l MySQL没有存储过程

l MySQL没有触发器或外键约束。

l MySQL只有表级锁定。

结语

企业级系统不会为了速度而牺牲特定的特性。RDBMS的ACID属性对于任何关键数据来说都是绝对必需品。在非 ACID顺从的系统上运行的关键网站是在自找麻烦。

OpenACS项目拒绝打破ACID测试的重要法则。我们要构建的是企业级的开放源码 Web工具包。PostgreSQL、很快还有InterBase将成为这一项目合适的候选RDBMS。而MySQL只是一个有着SQL接口的被美化的文件系统。

读后感:一直没有太深入的使用过MySQL,看到的介绍也一般都是说MySQL如何如何好的,今天看到这篇文章才发现MySQL甚至连存储过程都不支持!(不过据说最新的MySQL 5.0已经开始支持存储过程了,只是功能很弱)。原来用MS SQL Server 2000也没觉得有什么特点了,现在对比一下发现原来MS SQL Server 2000也超出了MySQL一大截,更不用说Oracle了。sigh,看来开源还有很长一段路要走(不过PostgreSQL似乎满令人振奋^_^)

将 XML 文档与 XInclude 合并在一起

发布日期: 5/20/2004 | 更新日期: 5/20/2004
Oleg Tkachenko

2004 年 4 月

适用于:
可扩展标记语言 (XML) 1.0
Microsoft .NET 框架

摘要:本文探讨了如何从多个文档构造单个 XML 文档的问题。它重点讨论了 XML Inclusions (XInclude),这是一种便于您用 XML 实现模块化的多用途机制。(15 页打印页)

下载 XInclude.NET-1.2.exe 示例代码。

*

本页内容
简介 简介

简介

模块化编程的思想要求您将任务细分成小的可管理单位。这种思想也适用于制作 XML 文档。通常,从几个较小的文档生成一个大型文档是有意义的。需要这种模块化方法的一些情况包括:将多个章节编撰成一部书,从单独维护的文档生成网页,或将标准页脚(例如,公司的免责声明)添加到文档中。

有很多方法可以解决如何从多个文档构造出单个 XML 文档的问题。本文重点讨论一个方法,该方法注定是便于用 XML 实现模块化的通用多用途机制:XML Inclusions (XInclude)

为什么要使用 XInclude?

读者可能会问的第一个问题是:“为什么要使用 XInclude,而不是 XML external entities?”答案是,XML 外部实体有很多众所周知的局限和不便于使用的含义,这些因素极大地妨碍了 XML 外部实体成为多用途包含工具。具体来说:

XML 外部实体无法成为一个成熟的独立 XML 文档,因为它既不允许独立的 XML 声明,也不允许 Doctype 声明。这实际上意味着 XML 外部实体本身无法包括其他外部实体。
XML 外部实体必须是格式规范的 XML(第一眼看起来好象没有这么差,但想象一下怎样将示例 C# 代码包括到 XML 文档中)。
未能加载外部实体是重大错误 (fatal error);严格禁止任何恢复。
只能包括整个外部实体,无法只包括文档的一部分。
外部实体必须在 DTD 或内部子集中进行声明。这将打开有很多含义的潘多拉盒子,例如,这些含义可能是:要求文档元素必须在 Doctype 声明中命名,以及对读取方的验证可能需要在其他文档的 DTD 中定义文档的全部内容模型。

人们认识到将 XML 外部实体用作包含机制的缺点已经有一段时间了,实际上,这些缺点导致了在 1999 年由 Microsoft 和 IBM 将 XML Inclusion Proposal 提交到 W3C。该建议定义了一个多用途 XML 包含工具的处理模型和语法。

几年后,XML Inclusions(也称为 Xinclude)的 1.0 版本成为 Candidate Recommendation,这意味着 W3C 相信它已经被广泛审阅并且解决了它要解决的基本技术问题,但它还没有成为正式建议。

那么,XInclude 真的解决了上述问题吗?当然。让我们来看它是如何做到的。

50,000 英尺以外的 XInclude

XInclude 定义了一个便于在 XML 文档中实现模块化的多用途包含机制。包括过程被正式定义为将很多 XML 信息集 (XML Infoset) 合并到单个复合的 XML 信息集中。作者通过包含指令来指定要合并哪些文档并控制合并过程。包含指令的 XInclude 语法基于大家熟悉的、容易产生和处理的 XML 构造:元素、属性和 URI 引用。

XInclude 支持包含非 XML 文本文档,并允许作者控制恢复过程。例如,您可以提供要包含的默认内容或备用文档,如果无法加载远程资源,就将包括这些默认内容或备用文档。

XInclude 还支持部分 XML 包含,也就是说,您可以定义(通过提供 XPointer 指针)应当包括 XML 文档的哪一(些)部分。

下面的基本 XInclude“Hello World”示例可以更清楚地演示这个机制。假如您有一些网页定义,并且想让它们全都包括带有公司版权信息的模板页脚:

page.xml:

<?xml version="1.0"?>
<webpage>
<body>Hello world!</body>
<xi:include xhref="http://blogger.org.cn/blog/templates/footer.xml" mce_href="http://blogger.org.cn/blog/templates/footer.xml" xmlns:xi="http://www.w3.org/2003/XInclude"/>
</webpage>

footer.xml:

<?xml version="1.0"?>
<footer>? Contoso Corp, 2003</footer>

page.xml after XML Inclusion processing:

<?xml version="1.0"?>
<webpage>
<body>Hello world!</body>
<footer xml:base="templates/footer.xml">? Contoso Corp, 2003</footer>
</webpage>
xinclude001.gif图 1“Hello World!”XInclude 示例。

下面是另一个介绍性示例,它演示了如何将外部非 XML 文本数据包含到 XML 文档中。假设您有一个存储在服务器上的 XML 文档,并且您想让它包含一个计数器,该计数器描述文档访问的次数:

<?xml version="1.0"?>
<catalog xmlns:xi="http://www.w3.org/2003/XInclude">
<p>This document has been accessed
<xi:include xhref="http://www.contoso.com/Counter.aspx?pid=catalog" mce_href="http://www.contoso.com/Counter.aspx?pid=catalog" parse="http://blogger.org.cn/blog/text"/> times.</p>
</catalog>

这是 XML Inclusion 处理后的文档:

<?xml version="1.0"?>
<catalog xmlns:xi="http://www.w3.org/2003/XInclude">
<p>This document has been accessed
45453 times.</p>
</catalog>

XInclude 语法

XInclude 语法非常简单,只是在 http://www.w3.org/2003/XInclude 命名空间中的两个元素,即 include 和 fallback。常用的命名空间前缀是“xi”(但可以根据喜好自由使用任何前缀)。对于那些习惯使用正式语法定义的人,XInclude 规范提供了 XInclude 的 XML 架构和 DTD。对于其他人,下面是它的摘要:

xi:include 元素

xi:include 元素充当包括指令。它定义了要包括哪些文档以及如何包括。它的属性是:

href ― 对要包括的文档的 URI 引用。
parse ― 它的值可以是“xml”或“text”,用于定义如何包括指定的文档(是作为 XML 还是作为纯文本)。默认值是“xml”。
xpointer ― 这是一个 XPointer,用于标识要包括的 XML 文档部分。如果作为文本包括 (parse=”text”),将忽略该属性。
encoding ― 作为文本包括时,该属性提供所包括文档的编码提示信息。
请回忆一下,通常,没有人可以说出任意一个文本文件的编码是什么。幸运的是,XML 不受这个问题的影响(因为如何检测 XML 文档的编码有严格的规则),这就意味着如果是作为 XML (parse=”http://blogger.org.cn/blog/xml”) 完成包括的,就可以忽略 encoding 属性。
accept、accept-charset 和 accept-language ― 这些属性可以用来帮助进行 HTTP content negotiation。当 XInclude 处理器通过 HTTP 协议取得资源时,它应当使用这些属性的值来设置 HTTP 请求中的 Accept、Accept-Charset 和 Accept-Language 头。如果对于相同的资源,单个 URI 可能基于 HTTP 头分析返回不同的表示形式(比如说,原始 XML 或 XHTML、ISO88591 或 UTF-8 编码、英语版本或希伯来文版本),那么该工具应该针对一种情况。

xi:include 元素可能只包含一个可选的 xi:fallback 元素,任何其他内容仅仅被忽略。

xi:fallback 元素

xi:fallback 元素提供了一个在丢失资源后恢复的机制。如果要包括的资源由于任何原因(连接问题、安全限制、资源不存在、URI 架构未知或者像 mailto: 一样不可获取,等等)而不可用,那么将包括 xi:fallback 元素的内容。下面的示例显示了如何使用 xi:fallback 元素:

<page xmlns:xi="http://www.w3.org/2003/XInclude">
<header>New This Week from MSDN</header>
<xi:include xhref="http://msdn.microsoft.com/rss.xml" mce_href="http://msdn.microsoft.com/rss.xml">
<xi:fallback>Sorry, MSDN news are unavailable.<xi:fallback>
</xi:include>
</page>

如果资源丢失并且 xi:include 元素为空,则不会包括任何内容。xi:fallback 元素没有属性,它必须是 xi:include 元素的直接子集,并且它的内容是不受限制的。由于对 xi:fallback 元素的内容没有限制,因此,它就可以包含另一个 xi:include 指令,从而提供要包括的可选恢复资源:

<page xmlns:xi="http://www.w3.org/2003/XInclude">
<header>New This Week from MSDN</header>
<xi:include xhref="http://msdn.microsoft.com/rss.xml" mce_href="http://msdn.microsoft.com/rss.xml">
<xi:fallback>
<xi:include xhref="http://msdn.microsoft.com/xml/rss.xml" mce_href="http://msdn.microsoft.com/xml/rss.xml">
<xi:fallback>Sorry, MSDN news are unavailable.<xi:fallback>
</xi:include>
<xi:fallback>
</xi:include>
</page>

保存基本 URI

留心的读者可能已经注意到出现在结果文档中的 xml:base 属性。这是一个重要的功能,当文档被包括到另一个文档(可能在其他位置)中时,它可以防止在被包括的文档中的相对 URI 引用被破坏。请参考下例。

位于 C:\Contoso\Inventory 目录中的 parts.xml 文档使用相对 URI 引用来引用相同目录中的 XML 架构 parts.xsd:

<parts xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="parts.xsd">
<part SKU="10023" quantity="100"/>
</parts>

位于 C:\Contoso\Reports 目录中的 report.xml 文档包括 parts.xml:

<report>
<inventory>
<xi:include xhref="http://blogger.org.cn/blog/C:ContosoInventoryparts.xml" mce_href="http://blogger.org.cn/blog/C:ContosoInventoryparts.xml"
xmlns:xi="http://www.w3.org/2003/XInclude"/>
</inventory>
</report>

经过 XML Inclusion 处理之后,report.xml 文档如下所示:

<report>
<inventory>
<parts xmlns:xsi="http://www.w3.org/2003/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="parts.xsd"
xml:base="../Inventory/parts.xml">
<part SKU="10023" quantity="100" />
</parts>
</inventory>
</report>

注意 xml:base 属性是如何保存被包括文档的基本 URI 的,该属性使 xsi:noNamespaceSchemaLocation 属性中的相对 URI 引用指向位于 C:\Contoso\Inventory 目录中的相同架构文件。

XInclude 处理模型

从技术上讲,XInclude 将包含定义为特殊类型的 XML 信息集转换。源信息集转换为结果信息集后,在结果信息集中每个 xi:include 元素被替换为它引用的信息集。该过程也可以被看作是信息集的合并。

XML Inclusion 是一个递归过程,因此,还会处理被包含文档中的每个 xi:include 元素。当然,为了保证安全,循环包含将会被检测,并将其当作重大错误。什么是重大错误?XInclude 定义了两种类型的错误,这两类错误都可能在包含过程中发生:“重大错误”,可以想象它是指出现了使正常处理过程无法继续的因素;和“资源错误”,这是指获取资源的尝试失败。重大错误是致命的,这就意味着处理过程必须停止,而资源错误则可后进行处理。

现在来看看如何处理 xi:include 元素。首先,它依赖于 parse 属性值。当 parse=”xml” 时(顺便说一句,这是默认值),获取引用的文档,并将该文档解析为 XML,然后替换源信息集以及其后代中的 xi:include 元素。可以设想,试图包含格式不规范的 XML 文档将导致重大错误,因为格式的规范性是 XML 的“圣杯”(译注:Holy Grail ― 圣杯/圣盘,据中世纪传说为基督在最后的晚餐上用过的那个杯或盘子,后来成为许多骑士追求的目标,此处是指格式的规范性是编写 XML 文档时追求的目标)。

如果 parse=”text”,将获取引用的文档,并将它视为纯文本,然后以同样方式替换 xi:include。这是非常有用的功能,例如,它允许将源代码或 XML 文档作为文本进行包含。如下示例显示了将 XML 文档作为文本包含在另一个文档中:

<doc xmlns:xi="http://www.w3.org/2003/XInclude">
For instance, consider the following SOAP message request to the Contoso Delivery Web Service:
<xi:include xhref="http://blogger.org.cn/blog/contoso-soap.xml" mce_href="http://blogger.org.cn/blog/contoso-soap.xml" parse="text"/>
</doc>

执行 XML Inclusion 之后,结果文档如下所示:

<doc xmlns:xi="http://www.w3.org/2003/XInclude">
For instance, consider the following SOAP message request to the Contoso Web Service:
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<Delivery xmlns="http://www.contoso.com">
<address>
<Street>One Microsoft Way</Street>
<City>Redmond</City>
<Zip>98052</Zip>
</address>
</Delivery>
</soap:Body>
</soap:Envelope>
</doc>

注意左尖括号字符被转义,因为整个 contoso-soap.xml 文档(尽管它是 XML)是作为单个文本节点被包含的。

在另一个文本包含可能有用的有趣事例中,您希望在 XSL 转换期间包含某个外部文本文档。虽然 XSLT 1.0 有 document() 函数,但该函数只允许您打开外部 XML 文档,而不能打开纯文本文档。例如,要将 CSS 文件包含到生成的 HTML 文档中,只要一个 xi:include 指令就足够了:

<xsl:template match="http://blogger.org.cn/blog/page">
<html>
<head>
<style type="text/css">
<xi:include xhref="http://blogger.org.cn/blog/main.css" mce_href="http://blogger.org.cn/blog/main.css" parse="text"/>
</style>
...

由于显而易见的原因,您仍然无法用该方式包含二进制资源。试图包含 XML 1.0 建议所不允许的字符将导致重大错误。

XPointer

到此为止,都很顺利。上面提供的示例说明了如何包含整个文档,但如果只需包含文档的一部分(比如说,包括某个书籍目录中特定作者的所有书籍),又该怎么办呢?请输入 XPointer

XInclude facilitates partial inclusion using XPointer pointers in xpointer attribute:
<para>
My favorite books are:
<xi:include xhref="http://blogger.org.cn/blog/books.xml" mce_href="http://blogger.org.cn/blog/books.xml" xpointer="xpointer(//book[@author='Stephen King'])"/>
</para>

上面的指令包含了所有 book 元素,它们的 author 属性值是来自 books.xml 文档的“Stephen King”。圆括号中的表达式您应当非常熟悉。它是 XPath 吗?不完全对,但非常接近。它是特殊类型的 XPointer,或更准确地说,是指向特殊 XPointer 架构 xpointer() 的指针,而 xpointer() 是 XPath 的扩展。现在让我们循序渐进地看一看 XPointer 的原理。

XPointer 代表“XML 指针语言”,它是用于进行 XML 寻址的可扩展的系统。XPointer 的基础是 XPointer 框架,后者定义了段落标识符的语义和基本语法。XPointer 框架定义了两种类型的指针:快捷 (shorthand) 指针和基于架构 (scheme-based) 指针。

快捷指针(以前称为 barename)事实上是对HTML 中段落标识符的大致模拟,后者指向命名定位点。而 XPointer 快捷指针最多只能按元素 ID 标识 XML 文档中的一个元素。ID 可以由 DTD、XML 架构或以应用程序特有的方式确定。

<xi:include xhref="http://blogger.org.cn/blog/books.xml" mce_href="http://blogger.org.cn/blog/books.xml" xpointer="bk101"/>

xpointer 属性中的 bk101 值就是一个快捷 XPointer 指针,它标识了 books.xml 文档中的某个元素,该元素的 ID 是“bk101”。

基于架构的指针是更复杂的指针类型,它由指针部分的序列组成,并可选择用空格分开。每个指针部分有一个架构名称和一些放在圆括号中的、架构特有的数据,例如 element(bk101) 或 xpointer(//book):

<xi:include xhref="http://blogger.org.cn/blog/books.xml" mce_href="http://blogger.org.cn/blog/books.xml" xpointer="element(bk101)xpointer(//*[@title='Dreamcatcher'])"/>

在以前的包含指令中,xpointer 属性值表示基于架构的 XPointer 指针,该指针标识了在 books.xml 文档(其 ID 是 bk101)中的元素,或者如果无法找到这样的元素,则它标识其 title 属性值是 Dreamcatcher 的元素。

xinclude002.gif图 2基于架构的 XPointer 指针结构

指针部分是按从左到右的顺序计算的,直到某个部分识别出某个子资源。无法识别或不被支持的部分将被忽略。但要注意,如果整个指针中没有任何指针部分能够识别子资源,则会发生错误。快捷指针同样如此。

如您所见,基于架构的指针提供了一种使段落标识更可靠的方式,因而允许您指定几个指针部分,其中每个部分可以按不同方式指向希望的子资源。如果指针部分未能识别子资源,则继续计算下一个指针部分,实际上提供某种替换的寻址行为。

XPointer 架构

W3C 定义了三种类型的 XPointer 架构,即 element()xmlns()xpointer()。另外,Simon St.Laurent 建议 xpath1() scheme,该架构使用规则的 XPath 1.0 语法,就像在 xpath1(//book) 中一样。

element() 方法便于按 ID(就像快捷指针一样)和按同辈元素之间的位置编号进行基本的 XML 元素寻址。例如,element(/1/3) 指针部分标识了根元素的第三个子元素,而 element(bk101/2) 则标识了 ID 为 bk101 的元素的第二个子元素。

xmlns() 架构表示辅助指针部分,它永远不标识自己的任何子资源,但允许定义绑定了其他指针部分的上下文的命名空间(这样,就可以根据需要将任意多个指针部分组合在一起)。例如,下面的 XPointer 指针标识了在属于“http://www.contoso.com”命名空间的文档中的所有 book 元素:

xmlns(co=http://www.contoso.com)xpointer(//co:book)

xpointer() 架构的功能最强大。它为对 XML 文档的各部分进行寻址提供了高级别的功能。它基于 XPath 1.0,并将 XPath 扩展为允许对字符串、点和范围进行寻址。请注意,xpointer() 架构仍然在发展,并且很可能有进一步的变化。

现在回到对 XInclude 的讨论。XInclude 规范要求任何符合规范的 XInclude 处理器都必须支持 XPointer 框架和 element() 架构。这实际上意味着您只能安全地使用文档中的快捷指针和基于 element() 架构的指针,但既然 Xinclude 还在发展之中,所以最好经常查阅 XInclude 处理器的文档。

我们已经讨论完理论部分。现在谈谈实践中如何编程。

XInclude 用法实践

我们首先需要支持 Xinclude 的环境。不要指望您喜欢的 XML 语法分析程序自动支持 XInclude。例如,很遗憾 MSXML 和 System.xml 都不支持 Xinclude。W3C 在 XInclude Implementations Report 中列出了可用的 XInclude 实现。其中一些实际上仅用于实验,但有几个具有生产水准。

我最喜欢的 XInclude 实现 XInclude.NET,这可能因为我是此项目的首席开发人员。

用于 .NET 的 XInclude ― XInclude.NET

XInclude.NET 项目受到 Chris Lovett 的 XInclude, Anyone? 一文启发,发现这篇文章时,我正在解决类似的问题:如何利用由不同团队分别编写的文档编撰出网页。通过方便地利用 .NET 框架的强大功能,可以在 System.Xml API 之上建立健壮、高性能的 XInclude 处理器。XInclude.NET 库的 1.2 版本最近已经发布,现在用户可以判断是否已实现该目标。

XInclude.NET 项目是 2003 年 11 月 10 日发布的 XInclude 1.0 Last Call Working Draft (编写本文时的最新版本)的实现,是用 .NET 框架的 C# 编写的。此外,它支持 XPointer 框架、element()xmlns()xpath1()xpointer()(仅 XPath 子集)架构。本文所附的代码示例包含 XInclude.NET 1.2 版本,该版本提供了预编译的 XInclude.dll 程序集、API 文档、示例和 XInclude 实现的源代码。

XInclude.NET 中的关键类是 XIncludingReader,可以在 GotDotNet.XInclude 命名空间中找到它。主要设计目标是为 XML 处理建立可插接的流管道。为了实现这个目标,XIncludingReader 实现了一个 XmlReader,该 XmlReader 可以围绕在另一个 XmlReader 的周围。该体系结构允许在不作任何重大修改的情况下将 XInclude 处理层很容易地插入到各种应用程序中。例如,当 XML 正在加载到 XmlDocument 中时,如果要启用对该 XML 的 XInclude 处理,只需用 XIncludingReader 将 wrapXmlTextReader 包装起来:

using System.Xml;
using GotDotNet.XInclude;
public class Class1 {
public static void Main(string[] args) {
XmlTextReader r = new XmlTextReader("document.xml");
XIncludingReader xir = new XIncludingReader(r);
XmlDocument doc = new XmlDocument();
doc.Load(xir);
//...
}
}

在执行 XSL 转换之前的 XML Inclusion 是同样简单的:

using System.Xml;
using System.Xml.XPath;
using System.Xml.Xsl;
using System.IO;
using GotDotNet.XInclude;
public class Class1 {
public static void Main(string[] args) {
XIncludingReader xir = new XIncludingReader("document.xml");
XPathDocument doc = new XPathDocument(xir);
XslTransform xslt = new XslTransform();
xslt.Load("stylesheet.xslt");
StreamWriter sw = new StreamWriter("result.html");
xslt.Transform(doc, null, sw);
sw.Close();
}
}

前面显示的 CSS 包含的示例假定 XSLT 样式表由 XInclude 处理器处理;以下代码显示了如何完成该操作:

public class Class1 {
public static void Main(string[] args) {
XPathDocument doc = new XPathDocument("document.xml");
XslTransform xslt = new XslTransform();
XIncludingReader xir = new XIncludingReader("stylesheet.xslt");
xslt.Load(xir);
StreamWriter sw = new StreamWriter("result.html");
xslt.Transform(doc, null, sw);
sw.Close();
}
}

XML Inclusion 过程与 XML 语法分析、验证或转换没有关系。这实际上意味着由您决定在什么时候允许 XML Inclusion 发生:是在语法分析之后,但在验证之前;还是在验证之后,但在转换之前,甚至在转换之后。

在执行 XML Inclusion 期间保存基本 URI 意味着在结果文档中会出现 xml:base 属性。这实际上意味着在“预验证包含”方案中 XML 架构作者应当期望 xml:base 属性出现在所包括的元素的最上层。实际上,xml:base 属性是核心标准 XML 属性,所以在这里有必要允许在架构中对每个元素包含 xml:base 属性。

XIncludingReader 如何工作

必须熟悉 XmlReader 体系结构。否则可以跳过本节。

XIncludingReader 实现基于 XmlReaders 链 (chaining) 技术,.NET Framework Developer’s Guide 中的 Customized XML Reader Creation 一节对此进行了描述。该技术与旧式 SAX 筛选很相似,它允许通过委托调用将 XmlReaders 进行链化,从而有效地提供高性能的方法来筛选或修改 XML(当 XML 正在被读取时)。

xinclude003.gif图 3 一条 XmlReaders 链

XIncludingReader 总是工作在另一个读取器(SAX 术语叫“父”读取器,它为 XIncludingReader 提供输入 XML 流)之上。当 XIncludingReader 的输入与 XmlReader 不兼容时(如在 System.IO.StreamSystem.IO.TextReader 中),将实例化一个临时 XmlTextReader 对象来充当父读取器。所得到的 XML(在完成 XML Inclusion 的位置)可以被 XIncludingReader 自己的客户端应用程序读取;记住,它只是 XmlReader。

大多数时候,XIncludingReader 不做任何事情;它只是向父读取器委派方法调用,并透明地公开父读取器的属性(例如,名称或值)来作为它自己的属性。虽然这样,但它总是监视父读取器所报告的 XML 元素的名称。一旦将父读取器放在 xi:include 元素上,XIncludingReader 就会被激活并开始执行包含过程。

父读取器并不会将 xi:include 元素公开给客户端应用程序,而是被压入堆栈,并实例化新的临时 XmlReader 以读取 xi:include 指令所引用的文档。该临时读取器的实际类型取决于是否存在 xpointer 属性(如果是这样,它会成为一个专用的 XPointerReader,它将实现 XPointer 并只读取指针节点所标识的内容),或者取决于包含操作是否是在文本模式下完成的(如果是这样,它会成为一个专用的 TextIncludingReader,它将把整个文档作为单个文本节点进行读取)。否则,它的行为只像一个 XmlTextReader。

不管怎样,父读取器都将被压入堆栈,而这个新的读取器会成为父读取器。普通读取会继续进行下去,直到从父读取器读取不到输入。之后,包括过程执行完毕,并且前一个父读取器从堆栈中弹出。

虽然这听起来很简单,但请记住这只是冰山一角。隐藏的部分超出了本文的范围,这些部分包括实现后备处理和极其重要地公开 XmlReader API 中的综合 xml:base 属性(Martin Gudgin 在他的网络日记中对此进行了详细介绍)。如果想要了解更多内容,只需深入阅读源代码。另外,请在该项目的 message board 上大胆地询问任何与 XInclude.NET 有关的问题,并将错误提交到 bug tracker

这样的仅向前、非缓存的流实现会占用非常少的内存,并且几乎不会对性能造成任何不利影响。在大多数时间里,XIncludingReader 所做的全部操作只是比较元素的名称、监视 xi:include 元素。在考虑到 XmlNameTable 的情况下,假设比较已经完成,这将是非常廉价的操作(因此,在 99% 的情况下这只是对两个对象指针进行的比较)。

遗憾的是,对 XPointer 指针的处理并不是低成本的。事实上,它需要将整个包含的文档加载到内存中,只是为了按 ID、XPath 选择路径或按 xpointer() 指针选择所需的部分。有趣的是,对 XPointer 指针的计算是通过将 XPointer 语法转换成 XPath 表达式在内部实现的,如下所示:

1. 快捷指针被转换成 id() function 调用:从 bk101 到 id(’bk101′)。

未来的 XInclude.NET 版本可能提供更完整和更有效的 XPointer 实现。

小结

XInclude 是正在发展的 W3C 标准,旨在便于实现 XML 中的模块化。它定义了对 XML 文档进行多用途包含或合并时所需的处理模型和语法。它不依赖于 XML 语法分析和验证,并且它的语法基于 XML 元素、属性和 URI 引用。

尽管 XInclude 还不是 W3C 推荐标准,但它的实现数量以及它在 XML 社区受到的欢迎正在不断增长。在本文中,我们已经介绍了 XInclude 和 Xpointer。我们还会讨论针对 .NET 框架的具体的 XInclude 实现。

致谢

我十分感谢 Dare Obasanjo 在我准备本文时提供的全部帮助。

转到原英文页面

[转]网站?XML?我的思考

十二月 13, 2004

作者:taowen(taowen.bitapf.org 或者 www.noasia.net/taowen
1、我用HTML进行设计
曾经我以为我蛮特别的,我喜欢用记事本来写很简单很简单的HTML。而且,我看的关于网页的第一个教程也就是教你<h1>啊这些标签的教程。相信那个著名的教程,很多人都有看过。只是很多看过了之后不一定会自己去手写这些代码,只是知道frontpage这样的工具背后的原理就好了。
但是时间久了还是觉得蛮累的。因为我写代码的时候毕竟是要靠自己的大脑去想象最终的外观会是什么,所以有的时候还是蛮想去用Dreamweaver这样的工具。也难怪那些所见即所得的工具会有那么多用户了,因为写的时候就直接看到了最终的呈现。
手写代码的时候,如果遇到了重要的内容,我会用<font>这样的标签特意去改变一下外观。遇到了列表的内容的时候会用<ul>啊这样的标签,产生1234的标号,或者一个个的圆点。其实有的时候觉得html挺简单的,因为标签的数量很有限嘛。
2、我使用HTML的表格
HTML的表格是很有意思的东西。当你遇到了要列表的东西的时候,如果是没有表头而且是一列的时候,你可能会用<ol>这样的单列。如果是两列,可能就会用<table>了。而且用起来也不是很复杂,<tr>就是开一行,<td>就是一列。当然还有很多种排法了。基本的也就是分方格,然后放东西。
表格有趣的地方是,你设计form这样的东西的时候也会用表格。虽然每个单元格的内容之间没有什么意义上的关联了。不像你的成绩单列表那样,数学成绩一列,语文成绩一列。在form中使用表格仅仅是为了最终的版式上的问题。利用单元格把form中的元件进行定位。
后来表格的排版作用用到了整个页面的排版,而且越用越复杂,表格加表格。直接导致了我这样手写代码的人无法去修改,去编写这样的页面。一度让我很伤心,觉得这个世界被Dreamweaver这样的工具的使用者掌握者,因为只有利用所见即所得才能够去设计这样外观丰富的主页。
3、我还用过css
css是让我激动的东西。我承认这点。我曾经梦想,我写网页的时候只要把每块内容指定好了class。以后要改变网页的外观就只需要把css换调就可以了。而且css可以是内含的也可以是外部用<link>链接的。我要把网站改版把css的内容换一下就可以了。
而且css还有@import,利用它我还可以在网站的每个目录下都放一个style.css,然后利用@import包含站点的样式表。这样每个网页就不用..这样的目录选择符来选择在父目录中的样式表了。这个特性着实让我很兴奋。
我的梦想越来越清晰,就是有朝一日,我写的网页能够很轻易的更改外观,而且编写的时候再也不用自己去使用<font>这样的标签了。
4、javascript也是让人好奇的东西
我相信网络发展还不如现在这么发达的时候就开始设计网页的朋友,一定对于各式各样的javascript非常熟悉。比如跟随鼠标的星星,跑马灯之类的东西。javascript设计出来是为了实现客户端的一定交互性的。javascript之所以能够交互是因为,它能够通过DOM操纵你看到的html页面,而且能够通过html元素中的事件属性得到你的输入。
因为javascript能够通过DOM把html的页面进行改变。这个性质其实也让我激动了好久。比如leoboard的最新帖子这样的信息,就是通过一个<script>的链接,链接到一个cgi上面,它产生的就是一段js脚本,通过document的函数写出一段html代码。也就是说,javascript能够“动态”的产生html代码。
由于javascript的这个功能,我又开始做梦了。希望能够整个网站都是通过互相包含的javascript组成。我把我的内容都写在javascript的变量之中,然后通过一定规则组合通过DOM的函数把内容以一定的模板风格写入到最终的html输出之中。
5、再闻css
一开始听说css,也许那个时候还是css1的时代吧。只知道css能够设定一定元素的外观显示,比如字体啊,空白什么的。关于css的排版功能一概不知。第一次做css的梦的时候,其实就是因为要把html进行排版控制,不得不用很多的table这个原因破灭的。所以当我知道css的功能很强大,可以进行各种版面控制的时候,就又开始做梦了。
把内容按照其性质放到一些<div class=”http://blogger.org.cn/blog/…”>这样的标签之中,用不同的css样式表,能够把这些div定位到页面的不同地方,而且显示也是不一样的。这样我就能够在写html的时候以任意的顺序来写,只管内容。而css会根据你对内容的描述(class是什么)把内容进行定位排版和显示。
所以说,以前我知道的css只把元素的内容和显示分开了,但是元素的位置还是与在源代码中的位置相关。而现在知道的css,能够让元素只管自己的事情,告诉css自己是什么(class是什么),不用再考虑自己在文档中的位置了。这样不就实现了内容与表现分离了吗?不就是我等用记事本写网页的网页设计者追求的吗?
6、内容与外观分离
我相信这句话很多人都听说过,梦想过。我的梦从模糊,到清晰,一直都在想着有朝一日我能够只管把我要传达的内容写下来,让其他的人来给我排版给我定外观。It is a Dream。
我们只从一个网页设计者的角度,而且是一个普通网页设计者的角度来谈这个问题。不要把什么中间件,应用服务器什么程序高手津津乐道的术语来压“我们”。
为什么要把内容和外观分离我相信很多人都有自己的体会。我的体会很简单。当年自己手写html的时候,我对我的内容很有信心,我知道我要说什么,我有内容。但是我对于如何排版很没有信息,我想让别人,或者自己以后来把内容进行排版。但是事情是很让人失望的,我基本上只有两个对策。把内容的格式变得简单得不能再简单,只是html的最基本元素的线性排列。除了<p>就是<p>这样的页面,朴素得不能再朴素。要么就是设计一个很好看,很复杂的页面。我自己手工的把内容粘贴到页面的模板之中去。如果有很多类似的网页,还要保证它们的风格是一致的。如果每个页面还有到其他页面的链接,比如是上一页,下一页之类的,It is a nightmare。
把内容与外观分离,就这样成为了我16岁的梦想。
7、perl,php,asp
我很坦白,我从来没有用过jsp,我不喜欢java提供的那些东西(请不要因此骂我浅薄,我知道它们的商业价值,但是现在仅仅是说网页设计)。
按照顺序,我用过的是这么三种网络脚本。perl是我接触的最早的一个网络脚本,那个时候一般都是用cgi这个名字。后来我有遇到了php,最后才是asp。
这里我只是谈网页的问题,不涉及到这些网络脚本怎么实现网络的交互的。也只是就着网络脚本,说说它们怎么又能实现内容与外观分离的。而且这三种虽然在访问文件,访问数据库,使用模板,产生html输出各个阶段和模块上各有不同,但是原理一样,方法相似,所以通称网络脚本。
网络脚本是让我激动的东西,而且同样作为engine驱动着很多现在正在运行的网站,比如经典的LAMP组合。它们关键的地方是能够产生html输出。因为html不再是你自己手写的了,所以不需要经过你的手就能把html的输出产生变动,这样就有灵活性。
为了文章能够分出更多的节,我按照我个人的认识顺序来看看网络脚本使用
8、网络脚本和文件
我最先看到网络脚本的使用,是leoboard这样的cgi程序,它使用文本作为数据的存放媒介。数据来源是文本,然后cgi中的perl程序会对文本进行解析,然后把解析的结果可能会放到变量之中,最后变成html的输出。
这里就是通过cgi程序作为中介,把文本的内容表现到了html之中。
9、网络脚本和数据库
后来我才看到了网络脚本和数据库的连用。经典的搭配有php+mysql和asp+access,我都有用过,不过都是很简单的。把数据储存在数据库中好处当然是多多,专业人士可以有很多事务啊之类的术语来描述其好处。不过显而易见的是,储存和获取的方法是标准化的,通过SQL嘛。而用文本作为存放媒介,文件格式以及解析,还有文件的完整性,容错这些都需要网络脚本设计者自己来控制,虽然很自由,但是更多的是劳累。同样,一种脚本有自己的一种格式,多浪费。要换论坛程序这样的情况出现的时候,又需要了很多麻烦。
10、网络脚本和模板
我记得当时我学asp的时候,很多文章就是这么介绍的。asp能够夹杂在html的代码之中,可以很方便的使用。而我使用perl的cgi的时候,很多程序又是使用满是$xxx的模板来做html输出的。这个时候,我就想who is better?
模板我还是蛮欣赏的。初级阶段的模板就是很多以前的cgi的网络文章程序中的那样,在html中放perl的变量名,然后最后输出的时候做一个替换。现在的模板当然要复杂好多,理论也很多。但是,我们可以看到的一点是模板做到了“把业务逻辑和表现逻辑分离”。
一般模板和脚本的关联就是通过一些两方面都知道名字的变量或者数组。然后脚本在变量中预先把内容存入这些变量之中,模板再把变量和数组中的内容提取出来插入到html之中。由于模板大部分是由html组成,所以比较适合给设计人员来设计。而且脚本的任务也就仅仅致力于从数据库也好,文本也好,这样的数据源中取出内容,进行一些加工,然后存入到变量之中。至少让脚本程序员免于考虑最终的外观问题。
11、网络脚本的时代
我们现在基本上就处在这么一个时代之中,大部分的页面已经不是手工编写的html了。而是由网络脚本动态产生的。方法步骤都是类似的:
数据源+网络脚本+模板=页面
似乎故事已经到了终结了。因为美梦已经成真了。利用数据库中存放数据,模板来控制显示,网络脚本进行一些计算和沟通工作,内容和外观分离的梦想已经实现了。而且,现下的很多现成的程序,一些CMS(内容管理系统),你直接在服务器上安装好,就能够享受其便利了。
但是,我们还有更多选择。
12、XML的登台
XML应该不是什么陌生的东西了。如果你不知道,说明你可能已经很久都没有关心过网页设计或者说是计算机这个行业了。XML的好处,长处,已经被说烂了。我就不多说了。
我把XML分为两类:作为数据或者文档的XML,以及功能性的XML。这个分类是我自己下的,功能性的XML指的就是HTML,SVG,MathML这些。可能SVG什么的你不了解,但是至少HTML你知道吧。关于HTML也是XML这个观点,你应该听说过。HTML为什么被我说成功能性的XML呢?因为如果你浏览器为观点,它认识HTML,它能够把HTML标记的能内容作出显示。而如果是一般的XML,它就不认得,如果是IE会调用内部的一个显示XML的模板把它显示出来,但是有的浏览器就不会。也就是说,一般的XML其中的内容元素,对于浏览器来说它是不知道什么意义的,而HTML的元素是对浏览器有特殊意义的。同样,SVG这些XML也是这样,虽然标准仍然在制定之中,浏览器对它的支援还需要一些插件。但是SVG的基本结构不会有什么变动了,它就是通过标签的标记,通过浏览器的读取产生二维的图像。关键的地方就是,浏览器认得SVG中的元素,知道它的意义,并且能够作出显示。
自然,对于浏览器没有特殊意义的XML就是文档数据型的。把XML分为文档为中心和数据为中心的这种分法是非常常见的。关于这个话题,我在后面继续说。
13、HTML作为一种功能性的XML
我现在把HTML完全作为一种表现语言,它的唯一功能就是把内容作出显示。也就是我把在HTML中表现内容的语意这个梦想给完全放弃了。HTML就是一个功能性的XML,它的目的就是让浏览器显示。要把内容和表现分离,我就是要从别的数据源中转换到HTML。
那是不是CSS就多余了?当然不会,css的目的是帮助HTML更好的表达如何显示这个要求。也就是XHTML+CSS共同表达的一个目的,网页的外观布局。它们的目的是一致的,而不是我以前想象中的HTML表达内容,css来表达外观。而且CSS的存在,能够表达更加精确更加丰富的内容,而且比用table这样的表格排版更加简洁明了。
14、XML作为HTML的源头
我把HTML表达网页的内容这个想法给放弃了之后,很自然的想法是把XML作为HTML的数据来源。但是这并不是很常见的做法。更多的做法是,利用数据库,利用文件然后用网络脚本进行提取,然后可能还要通过一道模板,直接产生HTML。其中并没有XML的位置,那么到底在产生HTML的过程中需不需要XML呢?
现在问题已经很明白了,HTML完全作为一种表现语言。焦点是对于HTML从何而来这个问题。务实的态度是尊重现有的解决方案,而且它们可以做得很好。这里只是对于XML能够在产生HTML的过程中的什么阶段进行参与工作,进行一些个人的看法的探讨。
15、XML直接产生HTML
这个可能是很多人头脑中首先想到的办法。利用XSLT,把XML转换成HTML。而且关于这个,我将在文章最后,给一个我个人的全面的想法。
我觉得利用XML产生HTML,主要是用在小型的纯发布的场合(比如个人主页),因为对于XML文件的更新和删除这些,并不是很完善。而且即使是使用XML数据库,也不能胜任大型的场合。而XML更多的是作为中间数据:
16、有数据库产生XML然后产生HTML
这可能是一个很好的方案。只是在现在看来有一些多余。因为网络脚本从数据库中提取了内容之后,直接就产生HTML了,或者调用一个模板也把HTML产生了。如果其中在多一个产生XML的过程,还需要再编写XSLT来产生HTML,让人觉得没有这个必要。
17、XML与数据库
很自然的,就延伸出了一个讨论就是“XML与数据库,用哪一个?”。其实这个问题之所以成为,我认为是XML的发展不成熟。XML有其结构和功能上的优越性,但是同样带来了很大的复杂度。对于XML进行查询,就比对于结构简单的数据库查询复杂变数大很多。同样,XML的表现力也要强很多。
另外还与XML的两个用法有关,XML一方面可以用作以数据为中心,比如网站的客户订单。这和关系型的数据库的特点是一致的,每个table的项是固定的,数据都是类似重复的。而XML同时还能用作文档为中心的,比如你写一篇文章时,用XML对文章进行标记。这样使得标记出现的位置,以及上下文就变得非常重要。
所以关于,在什么场合下用XML,什么场合下需要XML这种问题,很难有答案。至少有一点,随着XML技术的完善和被越来越多的人掌握,XML会在其适合的场合越来越多的使用。
18、XML与网站
如果仅仅是泛泛而谈XML与数据库,我觉得是很难定论。但是如果把讨论的范围缩小到网站,我觉得还是很容易得出答案的。
对于交互性的场合,比如论坛,数据经常要更新,XML就不适合。
对于发布性的场合,比如文章系统,XML就是一个很好的选择。
当然还要考虑查询是否方便。以及XML适合描述松散的信息,比如站长信息这样的数据存放到数据库中显然是overkill,而放到xml中就比较合适。所以,我个人认为如果是个人主页这样性质的网站,使用xml是非常合适的。
19、在个人主页中使用XML
个人主页一般都是无法购买那种有网络脚本支持的服务器的,更不用说数据库了,这个是来自于现实环境的限制。
个人主页的数据一般比较松散凌乱,而且文档比较多,比较适合XML来描述,这个是显示的需求。
所以综合了这两点,对于一般个人主页的站长来说,这样的组合方案是很不错的:
1、用XML来描述网站数据,用XSLT来做转换
2、注册一个免费的留言板
3、注册一个免费的BLOG
这样你就只需要一个HTML空间,同时又可以实现内容与外观分离,至少对我来说是一个梦想的实现。
20、最终实现指导
这个纯粹是个人意见,而且技术门槛相对比较高一些,估计没有人会采纳。
第一步:
描述你的网站内容。如何描述完全是你的自由,而且是否使用DocBook这样的东西来描述你的文章这样的东西也是你的自由。描述应该分布在很多个xml文件之中,可以利用XInclude技术,也就是利用<xi:include xhref=’http://blogger.org.cn/blog/…xml’/>这样的标签把所有的xml逐级串起来,最终是一个site.xml。找到了site.xml,就找到了你整个网站的内容。
你可以不用xinclude,而只是简单的用一个元素记录下文件位置,然后在XSLT中用document函数读取也是可行的。
第二步:
描述你的网站的结构,也就是页面的信息。这个我是用sitemap.xsl来做的。最终产生的就是一个sitemap.xml。里面由类似这样的元素组成:
<page name=”article1″ template=”article.xsl” output=”article1.html” param=”1″/>
这样有两个非常重要的作用,其一是能够让自动化的工具从产生的页面信息中提取内容,自动调用xslt处理器把网站给编译出来。其二是能够让页面索引到其他页面,比如你要在一篇文章的页面中链接到所属的分类的所有其他的文章,你就可以在sitemap.xml中提供出哪些页面是干什么的,比如所属分类是什么。然后具体page的xsl就可以在sitemap.xml中根据这些信息,然后找到页面最终写出超级链接。
第三步:
设计每个页面的xslt。xslt的输入有两个,一个是site.xml,另外一个就是sitemap.xml。从site.xml得到内容,从sitemap.xml得到其他页面的位置。
第四步:
利用你喜欢的脚本工具(设置是xslt+bat),自动化的完成整站的编译工作。你还可以提供选项选择编译什么页面。
第五步:
你也可以自己编写一个工具来实现新旧对比的ftp上传工作。

21、后记
既然把HTML作为了完全的表现工具,那么HTML的地位就和FLASH啊这些应该是平等的。所以网页设计完全可以把所有数据最终做到xml中,然后传递给FLASH,让FLASH来做表现层。这样同样做到了内容与外观分离。

OK,对于网页设计的技术的我的思考就基本上告一段落了。

XInclude,有人吗?

Chris Lovett
2000年5月29日

下载 下载本文的源代码 (38.4K)

如何创建包含来自其他地方的 XML 大块的 XML 文档呢?关于这一问题,程序员都知道怎么做,因为编程语言提供这一功能已有几十年了。每当您让一组编程人员处理相同的产品时,都会希望把它划分成能管理的大块。然而,如何处理 XML 呢?当我为我们组维护基于 XML 的 intranet Web 站点时,就遇到了这一问题。


XInclude 阐述了在树级合并两个 XML 资源所需的低级操作。


问题

有太多的方法可将人们想要在我们的内部 Web 站点上发布的信息划分成片段了。 我们有两个轴心。第一个轴心是按信息的类型划分,包括规范、开发信息、测试、文档和发布信息。

第二个轴心是按组划分。我们有四个小组,每个小组负责一个特定的领域。在 Microsoft 内部还有一些监视一切进展情况的人员;负责编制整个产品文档的集中的用户培训组;以及负责管理实际建立和发布所有内容的发布组。

各小组希望见到与他们的工作有直接关联的材料,而不想看到混杂在其中的其他组的信息。我们还需要为想要看到所有材料的人准备的主站列表。

ASP 解决方案?

解决此问题的强制方式是通过写入如下一些 Active Server Pages (ASP) 代码来汇编主站列表:

<%@LANGUAGE=JSCRIPT%>
<%
var list = new Array("/team1/specs.xml",
"/team2/specs.xml",
"/team3/specs.xml",
"/team4/specs.xml");
var master = new ActiveXObject("Microsoft.XMLDOM");
aster.load(Server.MapPath("/central/allspecs.xml");
var root = master.documentElement;
var doc = new ActiveXObject("Microsoft.XMLDOM");
for (i = 0; i < list.length; i++) {
doc.load(Server.MapPath(list[i]));
root.appendChild(doc.documentElement);
}
var xsl = new ActiveXObject("Microsoft.XMLDOM");
sl.load(Server.MapPath("/central/allspecs.xsl");
aster.transformNodeToObject(xsl, Response);
%>

这样将允许每个小组都能保留自己的本地列表,并能通过转到 Web 站点的特定区域来查看它们。但是,它仍能为其他人提供主站列表,使得皆大欢喜。

该方法存在几个问题。第一,从一个组的规范页面盲目复制所有内容的做法不够明智。我需要更聪明的解决方案。第二,它不参与客户机端的 XSL,所以 Web 服务器花费了宝贵的 CPU 时间来建立这些页面。

XSL 解决方案

为了改进上述问题,在常驻 XSL 大师 Jonathan Marsh 的帮助下,我用 XSL 建立了此操作方式的原型。如果您安装了 Internet Explorer 5,就会看到以下结果:

主页 规范
Team1.xml Specs.xml
Team2.xml Specs.xml
Master.xml MasterSpecs.xml

第 1 组和第 2 组有列出规范、交付、测试和发布信息的自己的本地页。(注意:此示范中仅提供了规范页。)主页附带简介,指向每个组,并指向 MasterSpecs 页。

MasterSpecs 页是有趣的地方。该页集合了 Team1 和 Team2 的规范。您可能会注意到,随着信息从其他页被编译,该页被异步植入。它的设计如下:

<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" xhref="http://blogger.org.cn/blog//voices/website.xsl" mce_href="http://blogger.org.cn/blog//voices/website.xsl"?>
<!DOCTYPE website SYSTEM "common.dtd">
<website xmlns:x="http://www.w3.org/1999/XML/xinclude">
<title>Master List of all Specs</title>
<p>
<font color="red"><i>Our team has been busy this week!</i></font>
</p>
<h2>Team 1</h2>
  <x:include xhref="http://blogger.org.cn/blog/team1/specs.xml#xpointer(/website/link)" mce_href="http://blogger.org.cn/blog/team1/specs.xml#xpointer(/website/link)"/>
<h2>Team 2</h2>
  <x:include xhref="http://blogger.org.cn/blog/team2/specs.xml#xpointer(/website/link)" mce_href="http://blogger.org.cn/blog/team2/specs.xml#xpointer(/website/link)"/>
&disclaimer;
</website>

请注意 x:include 元素。这些元素包含指向每组规范的 href 属性,该属性具有 XPointer URL 片段(英文) http://blogger.org.cn/blog/Non-MS link, 该片段精确定义主站列表中应包含规范中的哪些元素。(实际上,在我的原型中,它不是真正的 XPointer;它只是 XPath 表达式)。

website.xsl 样式表找到这些 x:include 元素;加载由它们引用的页;用匹配 XPointer 表达式的节点替换 x:include 元素;然后在聚合后的文档上重新运行样式表。

这意味着 MasterSpecs 作者可全盘控制 MasterSpecs 页的外观和感官,特别是在包含内容的地方。同时,MasterSpecs 作者不必担心与频繁更改的规范列表的同步,这些规范列表由各个小组维护着。

它是如何工作的?

我想您可能会问到这个问题。首先,XSL 样式表包含名为 xinclude.js 的辅助 JScript® 文件。并且,它还包含一个脚本块,每当一页加载完毕时即触发该脚本块。

<SCRIPT xsrc="http://blogger.org.cn/blog//voices/xinclude.js" mce_src="http://blogger.org.cn/blog//voices/xinclude.js"></SCRIPT>
<SCRIPT for="window" event="onload">
<xsl:comment><![CDATA[
ProcessXIncludes(document.XMLDocument);
]]></xsl:comment>
</SCRIPT>

Internet Explorer 5 中内置的 XML Viewer 提供了 XMLDocument 属性。该 xinclude.js 文件包含四种函数。

函数 用途
ProcessXIncludes 该函数采用原始的 XML 文档,复制它,然后调用 StartProcessing。该副本形成了新的组合文档的基础。
StartProcessing 该函数找到了所有 x:include 元素,并建立异步下载的 XML 文档的数组 (_docs)。它还分析将 XPointer 划分为片段的 href,并将其保存到另一个数组 (_pointers and _hrefs) 中。然后它保存真正的 x:include 节点 (_nodes),以便 CombineXML 可以用实际包含的节点替换它们。
HandleComplete 该函数是 onreadystatechange 回调,当 XML 文档状态发生变化时调用它。当准备状态达到 4(完成)时,该函数调用 CombineXML 处理包含的文档。当所有包含的文档都处理完后,HandleComplete 还重新运行原始的转换,并用此结果更新 HTML 页的正文。
CombineXML 该函数在下载的文档上运行 XPointer 选择,并用它找到的内容替换 x:include 元素。如果下载的文档发现错误,则 CombineXML 还会插入错误说明。

总而言之,这 133 行的代码 ― 包括 XSL 样式表中的脚本块,我认为很有价值。迄今为止,我用它维护了关于规范、发布信息和跟踪的标准的主站列表,它工作得很好。

最终解决方案是什么?

最终,如果在 XML 堆栈的下部 ― XML 分析程序或 XML DOM 内部的深处,支持 XInclude 处理,则效果会更好。这样,我的样式表就能完全忽视此步骤了,而且无论在客户机上还是在服务器上,样式表所起的作用都是一样的。

实际上,World Wide Web Consortium (W3C) 正在研究正确完成此项任务的标准方式。这种方法叫做 XIncludehttp://blogger.org.cn/blog/Non-MS link,它与 XLink 相关http://blogger.org.cn/blog/Non-MS link,XLink 是 W3C 正在研究的另一种规范。

您可以使用带属性值 show=”embed” 的 XLinks,来取得类似的效果。于是该资源将以图形方式,嵌入文档的显示中。但它与内含物有许多差别。嵌入的资源保留它自己的特性 ― 特有的文档树,具有从原始文档继承而来的对象模型和样式特性(不是从主机文档继承的)。一个近似做法是,用 XSLT 将这一 XLink 转换为显示目标资源中所需要部分的 IFRAME。

另一方面,XInclude 指定了在树级合并两个 XML 资源的必要操作。其结果是一棵单一的树,而不是两棵链接的树,并且一个单独的样式表即可建立该树的样式。该进程可在低层级执行(分析和创建文档树),而不在高层级执行(处理显示元素)。

可以用来改进该原型的措施:

  • 全面的 XPointer 支持。指定要包含的标记的范围的能力是非常有用的。
  • 将 XSL 样式表置入 W3C XSLT 格式。
  • 原型不处理包含文档中的相关链接。修正这一点将非常有用。适当的修正涉及另一个 W3C 规范,XML Basehttp://blogger.org.cn/blog/Non-MS link (XBase)。
下一页 »