XML 和 JSP 是当今最流行的话题。 这篇文章引导你如何运用这两种技术去创建动态Web站点。用XML文件去储存数据,用JSP文件去显示它。同时还可以了解DOM, XPath, XSL等其它 Java-XML技术。。
作者 Alex Chaffee
我先假设读者与其他大多数Java 程序员一样,对JSP(JavaServer Pages )和XML(Extensible Markup Language)有一定的了解,但是不清楚如何运用它们。在这篇文章中,您将学习如何用XML设计一个系统。许多网站都有大量的格式各异的数据,它们的表现方式或多或少的都没有遵循一定的标准。而我在此推荐用这样一种方法来设计网站,即用XML文件保存数据,用JSP文件显示这些数据。
想必大家都已经知道了HTML的局限性了把。随着站点的发展,需要一种方法来共享和交换数据。不管是内容出售,订单处理,还是报表生成,都需要对数据进行定义。XML正好可以发挥作用,其他的应用只能阅读和翻译信息,而XML可以赋予数据以意义。也许你会觉得奇怪,为什么要用XML来储存数据,而不用数据库呢?答案是在很多应用方面,数据库显的太“猛”了一点。为了要使用数据库,你必须要安装和支持一个独立的服务器处理,安装DBMS,建立DBA,并且还要去学习SQL语言,学会如何写查询语句并把结果返回。但是如果你使用XML文件,你不必去为准备额外的数据库Server而花费,且好处是可以很简单的编辑处理数据,就象是用一个文本编辑器一样,而不是复杂的数据库管理工具。XML文件也十分容易备份,共享和下载,以及通过FTP上传新数据至网站。
XML的另一个稍稍抽象的优点是采用了层次结构而不是关系结构来定义数据,可以根据需要直接了当的设计应用的数据结构,也不必使用实体关系设计器去进行模式的规范化操作。如果您有一个成员包含另一个成员,您可以通过层次结构直接表示出来,而不用使用连接表。从这种意义上说XML有利于信息的表达和结构化组织,可以准确定义数据,从而使数据搜索更有效。
对于许多应用来说,文件系统是没有足够的能力满足需要。当有大量的数据需要更新时,它的弱点就暴露了。并发写操作的冲突等是大问题。数据库有良好的事务处理机能,丰富的索引与复杂查询功能,完全可以为数据库提供一个包装器,以便将创建查询并将它们转化成XML流。这样XML就变成了一个强大的且编程友好的数据库前端部件(Oracles XSQL servlet是一个实现的例子)。
用XML来定义数据 :在线相册的例子
每个人都喜欢照片。互联网是个工具可以展示自己,朋友,充物及各种活动。这个例子以定义一个简单照片对象为主(源代码可以从参考资料处获得)。展示了定义一个照片对象所需要的属性有:标题,日期,简短说明以及图像的位置。
定义一幅图像需要的属性:
图像文件(GIF or JPEG)的位置
图像的高度(像素)
图像的宽度(像素)
在这里把文件系统当作数据库来存储信息有一个简洁的优点,即图像文件和数据描叙文件可以保存在同一位置下。
最后用一个元素来定义小照片图像(thumbnail images)集来扩充相片记录的定义,以便在任何地方使用。
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,可以把所有的关于一幅照片的信息放入一个简单的文件,而不放在分散的表里。这个文件称为pix文件。这样文件系统就如下面的样子所示。
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: 用类实现DOM接口来分析与检视XML文件
XMLEntryList: 把XML加载进 java.util.List 的name-value对
XPath: 用 XPath 处理器 (就象Resin一样) 通过路径名来定位XML中的元素
XSL: 用 XPath 处理器来转化XML为HTML
Cocoon: 使用开放代码的Cocoon框架
Roll your own bean: 可以写一个包装类加载数据进入一个的定制JavaBean
这些方法能很好地等同运用在客户端或应用程序服务器端接受XML流时。
Java Server Pages
JSP有很多实现版本, 不同的JSP版本实现了不同的且互不兼容的规格.作者在这里选用的是Tomcat, 基于如下的几点理由:
它支持最新的JSP 和 Servlet 规格
Sun和Apache认可它
你可以单独运行它而不用单独配置一个Web服务器
它的源码开放
(关于Tomcat的更多信息, 请看参考资料.)
你可以随便选用你喜欢的JSP引擎,但需要你自己配置它。一定要确认所选用的引擎支持JSP1.0规格。从0.91到1.0的版本有很多地方不一样。只要JSWDK (Java Server Web Development Kit) 能正常工作就可以了。
JSP 构造
当构造一个基于JSP驱动的WEB站点时,建议将公用函数,导入模块,常量和变量定义放在一个独立的名为init.jsp的文件里。然后用<%@include file="init.jsp"%>的方式把它添进每个JSP文件中。<%@include%>指示的作用类似于C语言的#include定义符,将被包含文件(例如init.jsp)作为包含文件(例如picture.jsp)的内容一起进行编译。注意,作为比较, <jsp:include>标志指示编译器将它作为一个独立的JSP文件编译,并在被编译好的JSP中嵌入对它的调用。
查找文件
当启动JSP时,在初始化工作结束之后的第一件事是查找所需要的XML文件。怎么知道需要哪些文件呢?答案是以CGI程序方式来传递所需要的参数。用户可以通过URL picture.jsp?file=summer99/alex-beach.pix的方式或用HTML的form对象来传递文件参数。
当JSP收到参数之后,你还得知道 filesystem的根目录位于什么地方。在UNIX系统之下,文件位置是以/home/alex/public_html/pictures/summer99/alex-beach.pix方式来指定的。JSP在执行时是没有相对路径的概念的,所以必须在java.io包内提供绝对路径。
根据所采用JSP 或 Servlet的版本,Servlet APT函数提供了转换URL路径为绝对路径的方法。ServletContext.getRealPath(String)是实现的窍门。每个JSP版本都有个 ServletContext对象(称为application)。因此代码可以象下面的样子:
String picturefile =
application.getRealPath("/" + request.getParameter("file"));
or
String picturefile =
getServletContext().getRealPath("/" + request.getParameter("file"));
它们在servlet中也能工作。注意必须增加"/",因为request.getPathInfo()的结果将被传递给这个方法。
另外重要的是,当访问本地资源时,必须特别小心所传过来的数据,黑客或粗心的用户可能发送伪造的数据来黑了你的站点。例如用file=../../../../etc/passwd时用户是可以看服务器的口令的。
文档对象模型
DOM代表Document Object Model,它是一组用来浏览XML文档的标准API。由World Wide Web Consortium (W3C)所制定。其接口位于包org.w3c.dom内,其文档位于W3C站点(参考资料)。
有许多DOM解析器的实现。因为DOM是一个接口集合而不是类的定义,每一种解析器都必须返回忠实地实现这些接口的对象。我选用了IBM所实现的XML4J。
尽管接口集是标准的,但是DOM有两个主要的缺陷:
1. API函数,尽管是面向对象的实现,但还是相当烦琐
2. 没有为DOM解析器设计标准的API,虽然每一种解析器都返回org.w3c.dom.Document对象,但是解析器的初始化以及文件的载入一直都是各种解析器所独有的。
上面所描述的相片文件,如果用DOM来表达,则可用树结构中的几个对象来描述:
Document Node
--> Element Node "picture"
--> Text Node " " (whitespace)
--> Element Node "title"
--> Text Node "Alex On The Beach"
--> Element Node "date"
--> ... etc.
为了获得值“Alex On The Beach”,必须调用几个方法,遍历DOM树。进一步的说,解析器可以选择为去访问任意数量的空白结点。解析器也可以包括区别XML实体(例如 &),CDATA结点,或其他元素结点(例如:<b>big<b> bear可以至少被转化成3个结点。其中之一是元素b,包含一个 文本结点,文本值为big)。在DOM中是没有办法简单的说一句:"get me the text value of the title element."就可以了的。简言之,遍历DOM树不怎么愉快。
从另外一个较高的视角来看,DOM问题就是访问XML对象不能象访问JAVA对象那样直接。对它们的访问必须通过DOM API来分解。
请参照我在 Java-XML 数据绑定技术讨论中所下的结论,在那里采用了直接访问Java对象的方法来访问XML数据。
我写过一个小小的工具类DOMUtils,包含一些静态方法来实现普通DOM任务。例如获得根结点 (picture)的子结点(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子元素的值的办法,也很简洁:
Document doc = DOMUtils.xml4jParse(picturefile);
Element nodeRoot = doc.getDocumentElement();
Node nodeTitle = DOMUtils.getChild(nodeRoot, "title");
String title = (nodeTitle == null) ? null : DOMUtils.getTextValue(nodeTitle);
等等!
一旦为相关元素建立了Java变量,就必须用JSP标志将它们嵌入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>
完整的代码如下。HTML的输出使用的是JSP文件。
<%
// invoke this file like: picture-dom.jsp?file=foo.pix
%>
<%@include file="init.jsp"%>
<%
String picturefile =
application.getRealPath("/" + request.getParameter("file"));
System.out.println(picturefile);
Document doc = DOMUtils.xml4jParse(picturefile);
Element nodeRoot = doc.getDocumentElement();
Node nodeTitle = DOMUtils.getChild(nodeRoot, "title");
String title = (nodeTitle == null) ? null : DOMUtils.getTextValue(nodeTitle);
Node nodeCaption = DOMUtils.getChild(nodeRoot, "caption");
String caption = (nodeCaption == null) ? null : DOMUtils.getTextValue(nodeCaption);
Node nodeDate = DOMUtils.getChild(nodeRoot, "date");
String date = (nodeDate == null) ? null : DOMUtils.getTextValue(nodeDate);
Node nodeImage = DOMUtils.getChild(nodeRoot, "image");
Node nodeSrc = DOMUtils.getChild(nodeImage, "src");
String src = DOMUtils.getTextValue(nodeSrc);
Node nodeWidth = DOMUtils.getChild(nodeImage, "width");
String width = DOMUtils.getTextValue(nodeWidth);
Node nodeHeight = DOMUtils.getChild(nodeImage, "height");
String height = DOMUtils.getTextValue(nodeHeight);
%>
<html>
<head>
<title><%=title%></title>
</head>
<body>
<center>
<table bgcolor="#CCCCCC" border="0" cellspacing="0" cellpadding="8">
<tr>
<td align="left" bgcolor="#6666AA" width="100%">
<b><%=title%></b>
</td></tr>
<tr>
<td align="center" valign="top">
<table bg