js|xml
XML和JSP是这些日子中最热的东西。本文介绍如何联合这两
种技术来建设动态网站。你还可以同时看一下DOM,XPath,XSL,
和其它Java-XML技术的示例代码。
我们在此假设你已经了解JavaServer Pages(JSP)和Extensible
Markup Language (XML)。但也许你对该如何综合使用它们仍然有些
迷惑。
JSP的应用很容易,你可以用它设计网页,使之看起来似乎和HTML一
样。唯一的不同是JSP是动态执行的。例如,它们可以处理表单form
和读写数据库。
XML的应用的说明则比较困难。似乎所有的产品都支持它,每个人也
好象都以各种不同目的在使用它。
在本文中,你可以看到如何使用一种相当先进的方式用XML来设计一
个系统。许多站点有巨量数据收集并以一种很标准或很不标准的方式
来显示它们。我将设计一个系统,它使用XML文件在web服务器上进行
存储,并用JSP来显示数据。
XML vs 关系型数据库
"等一下!"你可能问,"你用XML文件存储数据吗?为什么不使用数据库?"
这个问题问的很好。我的回答是,对很多目的用途来说,用数据库太
过浪费了。.要使用一个数据库,你必须安装和支持一个分离的服务器
处理进程(a separate server process),它常要求有安装和支持它
的administrator。你必须学习SQL, 并用SQL写查询,然后转换数据,
再返回。而如果你用XML文件存储数据,将可减少额外的服务器的负
荷。还有,你还找到了一个编辑数据的简单方法。你只要使用文本编
辑器,而不必使用复杂的数据库工具。XML文件很容易备份,和朋友共
享,或下载到你的客户端。同样的,你可以方便地通过ftp上载新的
数据到你的站点。
XML还有一个更抽象的优点,即作为层次型的格式比关系型的更好。
它可以用一种很直接的方式来设计数据结构来符合你的需要。你不需
要使用一个实体-关系编辑器,也不需要使你的图表(schema)标准
化。 如果你有一个元素(element)包含了另一个元素,你可以直接
在格式中表示它,而不需要使用表的关联。
注意,在很多应用中,依靠文件系统是不够充分的。如果更新很多,
文件系统会因为同时写入而受到破坏。数据库则通常支持事务处理,
可以应付所发生的请求而不至于损坏。对于复杂的查询统计要有反复
、及时的更新,此时数据库表现都很优秀。当然,关系型数据库还
有很多优点,包括丰富的查询语言,图表化工具,可伸缩性,存取
控制等等。
(注意:你可以使用简单的文件锁定来提供一个事务处理服务器,你还
可以在java中执行一种 XML index-and-search工具,不过这已经是
另外一篇文章的主题了。)
在下面这样的案例中,正如大多数中小规模的、基于发布信息的站
点一样,你可能涉及的大多数数据存取都是读,而不是写,数据虽
然可能很大,但相对来说并没有经常的更新变化,你也不需要做很
复杂的查询,即使你需要做,也将用一个独立的查询工具,那么成
熟的RDBMS的优点消失了,而面向对象型的数据模型的优点则可以得
到体现。
最后,为你的数据库提供一个查询器外壳来进行SQL查询并将他们转
化进入XML stream也是完全有可能的。
所以你可以选择这二种方式之一。XML正变成一种非常健壮的,便于
编程的工具,作为某个成熟的数据库的前端工具来进行存储和查询。
(Oracle的XSQL servlet即是这种技术的一个很好的例子。)
应用篇:一个在线相册
所有人都喜欢照相!他们喜欢展示自己的,亲人的,朋友的,度假
时的照片,而 Web 是他们展示的好地方。-- 即使千里之外的亲戚
都可以看到。我将着重于定义一个单独的Picture对象。(这一应用的
源代码在Resources中可以取得) 。该对象描述了表示一张照片所需
要的字段:title,date,一个可选的标题,以及对图片来源的一个指
向。
一个图象,需要它自己的一些字段:源文件( GIF/JPEG)的定位,宽
度和高度像素(以协助建立<img> 标记。 这里可以看到一个很简单
优点,即使用文件系统来代替数据库的时候,你可以将图形文件存
放在与数据文件相同的目录中。
最后,让我们来用一个元素扩展图片记录,该元素定义了一套缩略
图(thumbnail)来用于内容表或其它地方。这里我用了和先前同样
定义的图片内容。
一张图片的XML表示可以是这样的:
<picture>
<title>Alex On The Beach</title>
<date>1999-08-08</date>
<caption>Trying in vain to get a tan</caption>
<image>
<src>alex-beach.jpg</src>
<width>340</width>
<height>200</height>
</image>
<thumbnails>
<image>
<src>alex-beach-sm.jpg</src>
<width>72</width>
<height>72</height>
</image>
<image>
<src>alex-beach-med.jpg</src>
<width>150</width>
<height>99</height>
</image>
</thumbnails>
</picture>
注意,通过使用XML, 你将一张单独图片的全部信息放到了一个单独
的文件中,而不是将它分散放入3-4个表中。
我们将这称为 .pix file
-- 于是你的文件系统会是这样的:
summer99/alex-beach.pix
summer99/alex-beach.jpg
summer99/alex-beach-sm.jpg
summer99/alex-beach-med.jpg
summer99/alex-snorkeling.pix
etc.
技术篇
俗话说,要剥下猫的皮的方法何止一种。同样,将XML数据放到JSP
中也不止一种办法。这里列举了其中一些方法,(其实,很多其它工
具也可以做得同样出色。)
DOM: 你可以使用类(classes)来调用DOM接口(interface)对XML
文件进行分析检查。
XMLEntryList: 你可以使用我的代码来将XML加载到name-value
pairs 的java.util.List中。
XPath: 你可以使用一个 XPath处理器(如Resin)通过路径名在XML
文件中定位元素。
XSL:你可以使用某种XSL处理器将XML转换成为HTML。
Cocoon: 你可以使用开放源码的Cocoon framework
运行你自己的bean: 你可以写一个外壳类(wrapper class),使用某
种其它技术来将数据加载到字定义的JavaBean中。
请注意这些技术将和一个你从另外来源取得的XML stream执行得同样
出色,例如一个客户端或者一个应用服务器。
JavaServer Pages
JSP规范有很多替身,不同的JSP产品表现也不尽相同,不同版本之间
也有差别。我选择了Tomcat,这基于以下原因:
它支持大多数最新的JSP/servlet规范
它受到 Sun和Apache认同
你可以独立运行它而不需要另外配置一个Web服务器。
它是开放源码的
你可以选择任何你喜欢的JSP引擎,但要自己配置它,它必须至少支
持JSP 1.0规范。0.91和1.0之间有了许多区别。而JSWDK
(Java Server Web Development Kit) 可能刚刚好地适合要求。
JSP结构
当创建一个jsp网站 (Webapp), 我喜欢将公用的函数、导入、常量、
变量声明都放入到一个单独的文件init.jsp中。 然后用
<%include file="init.jsp"%>加载到每一个文件中去。
<%include%> 就象C语言的 #include, include在编译时使其中的
文本作为一个部分被加入并一起进行编译,相对地, <jsp:include>
标记则是使其中的文件被独立地进行编译,然后在文件中嵌入一个对
它的调用。
查找文件
当JSP启动时,初始化后第一件事情就是查找你要的XML文件。它是怎
么知道在众多文件中你要找的是哪一个? 它来自与一个参数,使用者
会在调用jsp的URL中加入参数: picture.jsp?file=summer99/alex-beach.pix
(或者通过HTML表单来传递文件参数)。
但是,当JSP接受此参数以后,你仍然只完成了一半工作,因为还要
知道文件系统的根目录在哪里。例如,在Unix系统中,实际文件可能
在这样的路径:
/home/alex/public_html/pictures/summer99/alex-beach.pix。
JSP文件在执行状态时没有当前路径概念。所以你为java.io包要给出
一个绝对路径。
Servlet API可以提供一个方法来将一个URL路径,从相对于当前JSP
或Servlet的路径转化成为一个绝对的文件系统路径。方法是:
ServletContext.getRealPath(String)。
每一个JSP有一个叫做application的 ServletContext对象。所以代
码可以是:
String picturefile =
application.getRealPath("/" + request.getParameter("file"));
或者
String picturefile =
getServletContext().getRealPath("/" + request.getParameter("file"));
它也可以在servlet中工作。(你必须加上 / 因为此方法需要传递request.getPathInfo
()的结果。)
这里有一个重要的提示:每当你存取本地的资源,要非常小心地检查输入数据的合法性
。
黑客或者粗心的用户,可能发送伪造的或错误的数据来破坏你的站
点。例如,请想一下以下的表达会发生什么结果:
如果输入了file=../../../../etc/passwd。这样用户回读到你的
服务器的password文件!
DOM (Document Object Model)
DOM 代表文档对象模型Document Object Model。它是浏览XML文档
的一种标准API,由World Wide Web Consortium (W3C)发展。 接口
在org.w3c.dom包中,文档参见W3C站点。
有许多可用的DOM分析器工具。我选择了 IBM的XML4J。但你可以使用
任何其它的DOM分析器。这是因为DOM是一套接口,而不是类 --所有的
DOM分析器(parser)必须返回同样地处理这些接口的对象。
遗憾的是,虽然很标准,DOM还是有两大缺陷:
1 API虽然也是面向对象的,还是相当笨重。
DOM parser并没有标准的API,所以, 当每一个分析器返回一
个org.w3c.dom对象,文档对象--分析器初始化和文件自身加载的方
式,对应于不同分析器通常总是特定的。
这个简单的上面已描述的图片文件在DOM中可以在一个树结构中通
过一些对象表示如下:
Document Node
--> Element Node "picture"
--> Text Node "\n " (whitespace)
--> Element Node "title"
--> Text Node "Alex On The Beach"
--> Element Node "date"
--> ... etc.
为了取得“Alex On The Beach”,你要做一些方法调用,游历DOM
树,而且,分析器可能选择分散“whitespace”文本nodes的一些数
据,你不得不使用循环和串联等 (你可以通过调用normalize()来
纠正这个问题。)分析器可能还包含了分离的XML实体(如 &),
CDATA nodes或者其它实体nodes (如<b>big<b>会被变成至少三个
node。也没有办法在DOM中简单表示"get me the text value of
the title element." 总之,在DOM中游历有一点笨重。(参见本文
用XPath取代DOM章节。)
2 从更高处看,DOM的问题是XML对象无法象Java对象一样可以直接得
到,它们需要通过 DOM API一个一个地得到。
你可以参考我的为Java-XML Data Binding技术讨论做的一些归纳,
那里也用了这种直接使用Java的方法来存取XML数据。
我写了一个小的工具类,叫做DOMUtils,包含了静态方法来执行公用
的DOM任务。例如,要获得根(图片)元素的title子元素的文本内
容,你可以编写如下代码:
Document doc = DOMUtils.xml4jParse(picturefile);
Element nodeRoot = doc.getDocumentElement();
Node nodeTitle = DOMUtils.getChild(nodeRoot, "title");
String title = (nodeTitle == null) ? null : DOMUtils.getTextValue(nodeTitle)
;
得到image子元素的值也同样直接:
Node nodeImage = DOMUtils.getChild(nodeRoot, "image");
Node nodeSrc = DOMUtils.getChild(nodeImage, "src");
String src = DOMUtils.getTextValue(nodeSrc);
等等。
一旦你需要将Java变量用于每一个相关的元素,你必须做的是将变
量嵌入到 HTML 标记中去:
<table bgcolor="#FFFFFF" border="0" cellspacing="0" cellpadding="5">
<tr>
<td align="center" valign="center">
<img src="<%=src%>" width="<%=width%>" height="<%=height%>" border="0" alt="
<%=src%>"></td>
</tr>
</table>
用JSP bean进行model/view分离
所有上面的picture-dom.jsp代码是很不吸引人的。虽然你可以在jsp中加入上万条
java代码,但这样就没有使用JSP JavaBeans方法看上去来得简单。
javabean存放了大量Java代码,并在JSP脚本中加以调用。
为了制作一个模型,将所有java代码放在JSP中来开始一个项目,这样
相对比较简单。一旦有什么新的想法可以直接回去展开代码,然后改写
为一些JavaBeans。虽然投资较高,但长远来看回报更好,因为你的应
用更为模型化。 你可以在多个页面中重复使用bean而不用担心剪贴带
来的不良后果。
在我们的案例中,一个典型的JSP JavaBean是从XML中展开字符串。
你可以定义Picture, Image和Thumbnails类,来表示主要的XML文件
中的元素。 这些beans带有构造器constructors或者setter方法,它
们从展开值中带入一个DOM node或一个文件名。你可以参
考picturebeans包或picture-beans.jsp.
在看代码时请注意以下几点:
* 我将接口的定义独立于类的执行,这样你可以很自由地选择在将来
进行替代,你可以将值存放在一个List中,或者在DOM自身,甚至可
以在数据库中。
* bean在一个自定义的包中被定义--picturebeans。所有JSP bean必
须要在某一个包中。大多数JSP引擎都不能从缺省包中发现类。
* 除了get方法,我也提供了set方法,当前你只是读取,但在将来,
你要让用户编辑图片,所以你要规划用语修改写入属性的功能。
* <%=picture.getCaption()%>取代了 <%=caption%>, 因为值是存放
在一个bean中而不是本地变量中。但是,如果你需要,你可以将本地
变量定义为
String caption = picture.getCaption(); 这也是允许的,这可以使
代码更容易阅读和理解。
* 通过thumbnails放大缩小
你可能已经注意到我的第一个的 JSP的输出, picture-dom.html,
使用了全部大小的源图片文件。我们可以稍微修改一下代码,使它显
示一个缩略图,我将把缩略图列表存放在XML数据文件中。
让我们定义一个参数,zoom,它的值决定了要显示哪一个缩略图。点
击这个缩略图后回显示全部大小的图片;点击Zoom In或Zoom Out按钮
将选择列表中下/上一种缩略图。
* 由于缩略图对象返回一个Image对象的java.util.List,所以要找到
正确的缩略图并不容易,如果用(Image)picture.getThumbnails().get(i)的话。
* 要制作Zoom In和 Zoom Out连接,你必须建立一个对同一页面的递归的引用,但
使用不同的参数。为了做到这一点,你要使用request.getRequestURI()方法。这
只为你提供了该servlet的路径,没有参数,所以你可以在此补上你要的参数。
<%
if (zoom < (thumbnails.size() -1)) {
out.print("<a href='" +
request.getRequestURI() +
"?file=" + request.getParameter("file") +
"&zoom=" + (zoom+1) +
"'>");
out.print("Zoom In</a>");
}
%>
这里是一个HTML的屏幕拷贝。
http://www.javaworld.com/jw-03-2000/jspxml/picture-dom.html
使用JSP bean
JSP规范定义了 <jsp:useBean>标记来自动实例化和使用JavaBeans。useBean标记可以总
是
被嵌入的Java代码取代,这里我也是这么做的。也是由于这样的原因
人们有时质问使用 useBean 和setProperty标记还有什么必要。这种
做法的优点是:
标记语法有利于HTML设计者独立工作。
useBean有一个scope参数,可以自动地决定bean是否必须存储为一个
本地变量,一个 session变量或一个application属性。
如果这个变量是持久的(session或application),useBean必要时可以
将它初始化,并切在它确实存在的时候才去取得变量。
长远看标记对今后的JSP规范版本来说更为便携(portable)或者更
改执行(例如,, 一个假定的JSP引擎在一个数据库中存储了变量,或
者跨服务器共享数据。)
这个应用中对应的useBean语句为:
<jsp:useBean id="picture" scope="request" class="picturebeans.DOMPicture">
<%
Document doc = DOMUtils.xml4jParse(picturefile);
Element nodeRoot = doc.getDocumentElement();
nodeRoot.normalize();
picture.setNode(nodeRoot);
%>
</jsp:useBean>
或者,如果你在DOMBean中定义一个setFile(String)方法:
<jsp:useBean id="picture" scope="request" class="picturebeans.DOMPicture">
<jsp:setProperty name="picture" property="file" value="<%=picturefile%>"/>
</jsp:useBean>
使用XMLEntryList
为了克服DOM APIs的一些不足,我创建了一个类,叫做XMLEntryList。这个类
执行了 Java Collections接口java.util.List,以及java.util.Map的
get和put方法,它提供了一套更直观的方法来在一个简单的XML树结构
中往返移动。你可以使用Collections API的标准抽象(abstraction)来
进行象获得 迭代或子视图等。在EntryList的每一个入口都有一个键
key和一个值,就象Map一样;键就是子结点(child nodes)的名字
而值要么是字符串,要么是下一级(child)XMLEntryLists。
XMLEntryList并不意味着可以完全代替DOM。它还无法执行某些DOM的
功能。但是,它是一个方便的外壳(wrapper)在你的XML数据结构上
用于执行基本的getting, setting和 list-oriented 功能。例如,
你可以使用这样的写法来得到图片node的caption元素:
String caption = (String)picturelist.get("caption");
caption字段的值早已被作为一个字符串分析和存储了。
缓存 Caching
尽管有很多优点,分析一个XML文件总是需要耗费时间。为了改进基
于XML的应用的性能,你需要使用某种缓存技术。这种缓存必须在内存
中保存XML对象,记住它们来自哪一个文件。如果对象被加载以后文件
被修改了,那么对象需要重新加载。我开发过一个用于这种数据结构
的简单方法,叫做CachedFS.java。你可以供给一个CachedFS 调用返
回功能(function),使用实际执行xml分析的内部类,将文件转为一
个对象。cache于是可以在内存中存储那个对象。
这里是创建cache的代码,这一对象有application scope,所以此后的
请求可以使用同一对象cache。我将把这些代码放到init.jsp,这样你
就不必将这些初始化的代码剪贴到其他JSP文件中去了。总之,你必须
在一个公共的地方定义application-scope对象。
<jsp:useBean id="cache" class="com.purpletech.io.CachedFS" scope="applicatio
n">
<% cache.setRoot(application.getRealPath("/"));
cache.setLoader( new CachedFS.Loader() {
// load in a single Picture file
public Object process(String path, InputStream in) throws IOException
{
try {
Document doc = DOMUtils.xml4jParse
(new BufferedReader(new InputStreamReader(in)));
Element nodeRoot = doc.getDocumentElement();
nodeRoot.normalize();
Picture picture = new DOMPicture(nodeRoot);
return picture;
}
catch (XMLException e) {
e.printStackTrace();
throw new IOException(e.getMessage());
}
}
});
%>
</jsp:useBean>
XPath
XPath在XML tree中是一个简单的用于定位node的语法。它比 DOM更
容易使用,因
当你要转入另一个node时不必每次都要产生方法调用,你可以把整
个路径嵌入到一个字符串中去,例如:
/picture/thumbnails/image[2].
Resin产品包含了一个XPath处理器,你可以将它加入到自己的应用
中去。你可以使用 Caucho XPath对象加载于其自身,不必购买Resin
体系的其它产品。
Node verse = XPath.find("chapter/verse", node);
Resin也包含有一个脚本语言,与JavaScript兼容,允许在jsp中对
XPath和XSL的简单存取。
XSL
这篇文章讨论了在JSP中嵌入Java来从XML node中展开数据。完成做同
样工作还可以有另外一种常见的模型:Extensible Stylesheet Language (XSL)。
这一模型和JSP模型有着根本的不同。在JSP中,主要内容是HTML,它包含了
一些Java代码片段;而在XSL中,主要内容是XSL文档,它包含了一些
HTML片段。如果要讨论XSL和 Java/JSP之间的关系,这里的空间已经
不够了。在JavaWorld杂志中将有一篇文章来探讨如何同时使用XSL和
JSP。
目前的结论和未来的发展之路
在读完这篇文章后,相信你应该有了一个JSP-XML应用及其强大威力
的很好的思路及结构认识。然而你也要知道一些它的局限。
开发JSP-XML应用中最令人烦闷的是为每一个 XML schema中的元素
element创建JavaBean。XML Data Binding 组织正在开发一种技术,
可以为每一个给定的schema自动生成Java类。同样的,我也开发了
一种原型-开放源码的Java-XML data binding技术。另外,IBM
alphaWorks最近也推出了XML Master, 或称为XMas,这是另一
种XML-Java data binding系统。
另外一种可能性是扩展文件系统的功能,建立一些更加强大的功能,
如查询和事务处理。自然地,我也开始期望这种功能类型也可以作为
开放源码工程来得到发展。那么,有没有谁愿意写一个XML搜索引擎