您的位置:寻梦网首页编程乐园Java天地小龙亭之JSP实践之旅
 

Serving XML with JSP  

Today's e-commerce sites must interoperate with software components of all types, not just Web browsers

 

原著:Duan Yunjian /Willie Wheeler

Blueski.xlt 01/01/15为您编译

  Download 程序及附加文档(250K)

 

对于每一个成功的电子商务站点,都应该满足二个主要的要求: 1 具有实际可用性;2 在不同的计算环境下的互操作性。

从可用性来看,各种Web应用,例如电子商务站点,看上去应该和基于桌面的应用有所区别。现在我们可以很遗憾地看到,桌面应用中充斥着很多平庸的甚至更糟糕的内容,并没有很好的可用性,这也许是因为客户的钱在发现问题之前已经支付出去了。但是在 Web应用中,平庸的产品在支付以前就已经是一目了然的了,而其它的站点随时会加入到竞争行列中来。
另一方面,电子商务系统必须能和其它系统一起协同工作。这些系统分布在各种各样的软硬件平台上。包括那些带有大大小小显示器的桌面电脑,PDA,蜂窝电话,等等。在这个商业竞争高度激烈的行业里,没有一家公司愿意去冒因为“不兼容”而失去客户或潜在商业伙伴的风险。
除了上述2个方面外,电子商务站点应该提供动态内容,因为商业数据显然是动态的。如何将这些要求组合起来,已成为一种真正的挑战。例如,表示动态内容的标准方式是在服务器上动态地生成HTML,然后发送到客户端,我们假设那里是浏览器。这可能会违反可用性,因为HTML在开发用户界面时没有提供所需要的足够丰富的操作控制。同样这也违反互操作性,因为客户可能不用浏览器而使用其它设备。

在这篇文章中,我们将探索一条同时满足可用性、互操作性以及动态内容要求,从而可以满足所有电子商务开发项目要求的途径。我们的途径就是使用JavaServer Pages (JSP)来服务于XML (serving XML with JSP)。我们将通过一个简单的e-travel旅游服务站点来演示这些概念。我们将使用JSP来生成动态内容,用XML来帮助完成可用性和互操作性的实现。

 

 
图 1 Welcome to D&W Travel 放大!

要运行这些代码,你要跟着做很多事情,不过所有的都是免费的。

首先,你需要一个带 JSP引擎的Web服务器,我们推荐Apache的Tomcat3.1 
(http://jakarta.apache.org/tomcat)

他有Windows, Solaris和Linux版本。(如果你用了Tomcat,记得设置JAVA_HOME环境变量,使它指向JDK所在路径,然后在配置文件server.xml中加入我们的名为dw-travel的应用的路径。)

 其次,你需要一个支持XSL(XSLT)的浏览器,目前只有一个选择:Microsoft Internet Explorer 5 (www.microsoft.com/windows/ie)。对于 Netscape ( 即使是Netscape 6 预览版1)都不行。(不过这并不违反我们关于互操作性的申明,请继续往下看好了)

第三,你需要Java Plug-in (www.java.sun.com/products/plugin),因为我们的应用包含了一个基于Java 1.2的applet。

第四,代码要求使用 Sun的Project X XML parser(www.java.sun.com/products/xml)。 (任何W3C兼容的DOM parser都可以工作,但需要在CityDao.java中对代码做少许修改,因为你用了不同的parser。)

最后,当然地,你要下载这些代码

 
2. Choose a City
 放大

 对D&W Travel站点的介绍

D&W旅游服务是一个虚拟的e-travel服务,她的目标是帮助来自全球的游客可以方便的规划他们的行程。 


如果是一个很成熟的版本,那么她将通过一些图形界面使游客得到一个完善的旅游计划。 而本文的目的是,只要能提供足够的功能开发,满足用户查找旅馆就可以了。
当某个用户来访,她看到的是一张友好的页面,其中可以允许登录或注册一个新的帐号,查找酒店,或者看看用户使用规则(参见图1)。


实际上我们只提供了"Find Hotels"的功能。其它的功能只是一个样子而已。如果你点击了,得到的结果是返回主页面,或者返回连接错误的一个提示。 

 

 
3. Here Are Your Hotels 放大.


 

当用户选择 "Find Hotels" ,她会被带入到第二页(图2所示)。
她可以从地图上选择一个城市。如果她点击的地方没有城市,则什么也不会发生,如果点击的正好是一个城市,那么就选择了该城市,而如果她点击的地方是一个有很多城市的地区,那么会有一个对话框出现,并要求你选择一个确切的城市。

 

 
图 4. Choose a City
放大!



 

一旦用户选了一个城市,她可以看到第三页,该页列出了城市中的所有酒店(参见图3)。在我们的站点里,总共只设了3个城市中的酒店:Los Angeles, San Diego和Pittsburgh。如果你选了别的城市,酒店列表将是空的。.

 

 
图 5. Class Diagram
放大!


 

application的框架结构

我们的application(译注:以下不再翻译为‘应用’)使用了标准的3-tier结构(参见图4)。各层是逻辑的,而不是实际的物理上的。第一层是表示层,让用户看到application数据。中间层由商业对象和application逻辑组成,它提供了定义良好的接口,表示层可以方便地存取,从而适应于商业服务中的各种请求。第三层是数据层,它保持了商业对象的持久性(business object persistence)。 通常数据层是一个或多个数据库,不过在我们的示例中文本文件已经足够了。3层模型比传统的 client/server结构更灵活,可伸缩,所以这也是我们使用它的原因了。

在我们的application中,第一层包括了浏览器、Java applets和对应的application (有些是基于Java的,有些不是)。在本文中,我们选择IE5和CityMapApplet地图。

在第二层中我们有基于Java的Web application,使用了Model-View-Controller (MVC)型式,我们将在下一节阐述。

在第三层中我们只使用了一个文件,其中包含了酒店数据。(我们的application也包括了一个XML文件,它包含了城市数据,不过我们也可以把它视为一个Web文档,因为它存在于Web app文档的根目录,客户可以通过简单地指向它而直接获得)。

表示层向中间层发送消息,(通过HTTP请求),中间层用XML回复,或者是静态生成的(由XML文件提供),或者是动态生成的(由JSP提供)。我们可以看到,使用XML代替HTML,意味着我们可以得到动态的内容而不必牺牲可用性和互操作性。(图5 是D&W Travel站点的类图)

Web表示层 

application将为用户产生二种类型的数据。一是城市数据,包括名称和相关数据。这是静态的,如果客户发出城市数据的请求,我们不必选择哪些数据要发送,我们将发出全部的数据。所以,我们将数据直接放在一个文件中。 二是酒店数据,包括名称、地址(有时还包括城市名)和电话号码。这是动态的数据因为客户请求不同城市将得到不同的酒店列表。所以我们要采用不同的策略,需要一个服务器端的机制来读取酒店数据,决定哪些是需要发出的,然后发出它们。虽然,我们也可以分别为每个城市定义一个静态文件,但如果以后查询条件改变,修改程序或处理将很困难。
OK.现在我们来解释为客户数据请求所提供服务的服务器端的机制。在标准的MVC型的基于Java的Web application中, JavaBean与模型的后端(可能是database,也可能是Enterprise JavaBeans) 建立连接。HTML页和JSP页面是视图(view)。servlet或JSP 前端组件组成了连接器controller。(更具体的说明可参考JavaPro 2000/4: "Divide and Conquer Your Workflow with JSP")正如我们在一开始所指出的那样,这种方式还是为可用性和可操作性带来了一定的困难,客户端必须是HTML,这就限制了可用性和可操作性。我们不使用这种方式,因为D&W Travel站点有一个applet (不具备通常的HTML-parsing性能),它需要从Web app得到城市数据。展望未来,很显然serrving HTML 无法满足我们所提到的互操作性的要求,而新的客户电子设备将越来越多。
解决之道是把数据作为未做格式处理的 XML,而不用已格式化了的HTML。如果数据是静态的,(例如我们的城市数据),我们可以直接使用 XML文件。如果数据是动态的 (就象是我们的酒店数据),则可以使用JSP来动态生成XML。这正是D&W Travel站点所做的。
下面让我们详细地看一下Web层:
所有和酒店有关的客户端请求来自于hotel/index.jsp这一前端组件:

<jsp:useBean id="ctrl" class=
   "dw_travel.hotel.HotelController" scope="session">
    <% ctrl.init(application, session); %>
</jsp:useBean>

<% ctrl.service(request); %>
<jsp:forward page= "<%= ctrl.getNextPage() %>"/>

 <jsp:useBean>元件为client端创建并初始化了一个session-scope的 HotelController 实例(如果原来不存在的话)。然后就可以用HotelController来处理客户端的请求了。最后,通过HotelController得到下一个页面是什么,然后把请求导向到该页。我们已经提过,页面包含了XML内容,而该页面究竟是静态XML文档或者动态生成的XML 将取决于请求的内容是静态的还是动态的。

顺便提一下,我们选择了index.jsp作为前端组件的名字,因为Tomcat缺省地把index.jsp作为 welcome文件。亦即Tomcat把URL路径 /dw-travel/hotel/ 指向到/dw-travel/hotel/index.jsp。这样 /dw-travel/hotel/就是hotel模块的入口了。如果你不使用Tomcat,那么你的servlet引擎有可能不把index.jsp作为welcome 文件。这时你最好稍加弥补,例如:
<web-app>
   <welcome-file-list>
      <welcome-file>
         index.html
      </welcome-file>
      <welcome-file>
         index.jsp
      </welcome-file>
   </welcome-file-list>
</web-app>

(实际上,在我们的在线代码中,我们已经为你做了这些,所以是比较独立的。)

 

 
6. Sequence Diagram (Cities) 放大.


让我们仔细看一下HotelController (查看文档)。HotelController是session-scoped, 每一个客户的session都有自己独立的实例。 HotelController接受了命令(在客户的请求中作为 HTTP参数进行了编码), 并以这样的方式存取模型:执行命令,然后判断哪一个文档应该回复给客户端。如果客户端请求的是城市数据,HotelController根本不必存取模型。它只要简单地将cities.xml传递给客户端,这只是一个静态的文件(参见图6)。如果请求的是酒店数据,HotelController将从数据层加载酒店数据(这里,仅仅是hotels.txt) 并将它载入酒店JavaBean的一个Vector变量中。然后HotelController 将这个Vector放入session域中,这样hotels-xml.jsp (查看文档)就可以对它进行处理了。最后,它将页面设为hotels-xml.jsp,该页面可以将动态生成的XML传递到客户端(见图7)。

 

 
7. Sequence Diagram for XML Broswer 放大


实现互操作性的关键是HotelController可以假设客户端能够处理XML。在目前的各种浏览器中,只有IE5能处理基于XSLT的XML。但基于XML的通信的发展是非常快的。同时,还有一个较好的方法,那就是发展在服务器端检测客户浏览器类型的技术,检测以后,可以根据不同的客户端,要么由servlet或JSP直接生成,要么由服务器端的 XSLT生成HTML或者XML。这样可以保证我们的 application能够和不同的浏览器互操作。

这里,我们只是比较简单地提供了XML,并且假定客户端可以对它进行处理。这一讨论似乎加剧了只能使用IE5和我们所提倡的互操作性之间的分歧。不过,可以使用XML并不意味着不能使用HTML。

 

基于Applet的表示(Presentation)
第二张页面中的地图是用叫做 CityMapApplet 查看文档的Java applet实现的,它显示了大约900个城市,大部分定位都是准确的。(这是由学生在课程中进行编制的,海拔和经纬度等方面还有一些错误)。applet 从服务器上读取城市数据然后加以提交。 城市下方是一个世界地图(使用GNU Generic Mapping Tools生成)。通过选择城市来查找酒店的功能实现是很简单的:如前所述,用户具有查找一个酒店的兴趣,application为用户提供选择城市的界面,拥护选择了一个城市,application返回该城市中的酒店的列表(如图3)。

实际上,使用applet并不是实现选择城市的唯一解决之道, 并不是所有人都会在浏览器中安装Java Plug-in,尽管任何人都可以下载它,但使用HTML来替代似乎更有竞争力。但在另一方面,从某种人-机交互的观点来看,applet在提供多表单交互作用等方面表现很好,所以各种不同的用户,要求各种不同的性能和功能,需要各种不同的设计来达到各种不同的可用性。Applets提供了保证持续可用性的可能。因此applet依然是开发客户端界面的方法之一。 尽管你一直使用HTML来代替applets,有时你也需要保持使用applets的可能性。

让我们看一下CityMapApplet是如何取得数据的。CityMapApplet使用了一个CityDao数据存取对象查看文档来得到一个城市的Vector变量。CityDao.read()产生了一个DOM树(也就是一个org.w3c.dom.Document) XML文档表示,位于applet指定的cities.xml文件的URI:

// uri points at cities.xml Document document = XmlDocument.createXmlDocument(uri);

然后它沿着DOM树取出城市数据,放入一个Vector变量。最后,它返回Vector变量。

注意,CityMapApplet从静态的cities.xml中读取XML数据。 如果一个客户请求了城市数据,它总是取得所有的数据,而不是城市数据的一个子集。

一旦applet从CityDao.read()得到了城市数据的Vector,它即已做好了接受客户输入的准备。当用户选择了一个城市以后,applet将提示浏览器转向一个具有城市参数的URL,并且以XML形式返回该城市的酒店列表:

 url = new URL(getCodeBase(), "../?cmd-get-hotels=1&ctry-name=" + ctryName + "&city-name=" + cityName);
 getAppletContext().showDocument(url);

applet的AppletContext成员是applet所在的操作环境即浏览器的一个Java表示。 Java对浏览器的权限有着严格的限制(尤其是同JavaScript相比),但它确实拥有象要求浏览器转向一个新的URL这样的权限。 这就是showDocument()方法所做的。这样,在上述的代码中,applet并不关心实际的 application要做什么,它是可重用的。

我们还要简单提一下 hotels-xml.jsp 查看文档。在前面Web-tier的讨论中,该JSP 动态地生成了一个代表 酒店提供商的 XML,它通过 HotelController放置到session中。由于该XML包含了纯数据(也就是说,它不包含与页面表达有关的信息),我们仍要有一个办法为XML加入表示信息,我们把下面这行加入到了 XML中:

<?xml:stylesheet type="text/xsl" href="hotels.xsl"?>

该行告诉XML客户端,hotels.xsl样式表 查看文档将用来表示 XML数据。在我们的案例中,该XSL翻译结果 (XSLT)将是一个定义良好的HTML文档(图 8),例外的只是开始和结尾处的标记需要符合XML标准。将XML转换为HTML 似乎是目前XSLT最常用的功能。但别忘了还有别的功能,例如将 XML翻译为Wireless Markup Language (WML), Scalable Vector Graphics (SVG), MathML, 或者任何其它的基于 XML的标记语言。这样,如果要支持无线用户,只需要一个XSL样式表就可以将XML数据转换到WML了。这可是在互操作性方面的巨大收获!

 
8. XSLT in Action
放大.



要应用 Extensible Stylesheet Language (XSL) 样式来处理 XML,客户端必须具有一个 XSL处理器。当前,IE5是唯一可用的XSL处理器。虽然讨论XSL有些超出了本文讨论的范畴,我们还是要给出一些关于它的工作原理的内容。

 XSL是用 XML定义的一种标记语言,它有一些带有一定格式的符号的标记,还有一些XML转换的标记。由于我们正进行 XML到HTML的转换,所以我们不使用 XSL的格式对象,而关注于 XSLT。

 XSL样式的根元素是 <xsl:stylesheet>。我们将 XSL namespace (在 www.w3.org/TR/WD-xsl)绑定到xsl: prefix,这通过xmlns:xsl 属性来实现,如下所示:

<xsl:stylesheet xmlns:xsl=
   "http://www.w3.org/TR/WD-xsl">

XSLT是基于模板的设想的。你可以用它来定义模板的规则,这些规则可以将XML的结点映射到和表示有关的模板上。例如,在hotels.xsl中的一个模板规则为<hotel>元素定义了一个HTML模板。它在定义时使用了 <xsl:template>元素。下文的match元素的值表示这是的 XML 源文件的<hotel>元素的模板规则。

<xsl:template match="hotel">
<tr>
<td><xsl:value-of select="@id"/></td>
<td><xsl:value-of
   select="name"/></td>
<td><xsl:value-of
   select="address/street"/></td>
<td><xsl:value-of
   select="address/city"/></td>
<td><xsl:value-of
   select="address/zip"/></td>
<td><xsl:value-of
   select="address/country"/></td>
</tr>
</xsl:template>

模板可以包含任意的HTML,只要它们不破坏XML的定义良好的性质。模板通常也包含XSL元素来执行转换工作。这里我们可以看到,当这个模板应用到<hotel>元素时,它产生了一个HTML表的行,并且有6个列,第一列是 <hotel>的id,第二列是<name>子元素,第三列是<hotel>元素的<street>孙元素 ( <address>的子元素),等等。这足以表明XSL转换的含义:我们使用了一个 XSL样式将XML文档转换到一个新的XML文档(在这里,是一个遵循XML规则的HTML文档)。 XSLT是相当灵活的 (但也并不是无限制的):它允许你重新排列元素、忽略部分元素,以及将它们映射到整个模板。

hotel的模板规则告诉 XSL处理器当<hotel>被选中的时候该做什么。但并不提示哪一个<hotel>元素被选择了。要做到这一点,可以看一下源文档的根结点模板规则:
<xsl:template match="/">

这是XSL处理器要执行的第一个模板规则,因为根结点是XSL处理器首先遇到的。 hotels.xsl中的根结点的模板包含了大量的HTML,甚至包含了和CSS的一个样式 (这是完全允许的)。我们再看一下模板中间的行:

<!-- Spacer -->
<tr><td colspan="4" height="6"></td></tr>
<!-- Content -->
<xsl:apply-templates select="hotels"/>
<!-- Footer --> [etc.]

<xsl:apply-templates>元素表示在该点上XSL处理器必须开始执行<hotels>模板规则(小心:和<hotel>模板规则不同)。该点是值得重申的:模板规则可以援引其它的模板规则。其中的select属性允许XSL处理器探究是哪一个子结点引发了模板规则。由于select属性的值是"hotels",当前上下文的所有<hotels>子结点都将被选中。这里,当前上下文结点是文档的根结点,这是因为与当前所选的模板匹配的是文档的根结点。我们的XML 数据只包含一个这样的子结点,即<hotels>元素。如果我们看一下出现在根结点后面的<hotels>模板规则,可以发现它构成了一个HTML表,而且在适当的位置为<hotel>元素执行了模板规则:

 

<table border="1">
   <tr>
      <th>ID</th>
      <th>Name</th>
      <th>Street</th>
      <th>City</th>
      <th>Zip</th>
      <th>Country</th>
   </tr>
   <xsl:apply-templates
         select="hotel"/>
</table>

这引发了<hotel>模板规则(就是产生一个表行<hotel>元素的),并应用于当前上下文结点的每一个<hotel>子结点,即<hotels>元素,因为当前所选的模板是与<hotels> 元素相匹配的。

所有这些的结果是HTML文档,它以XSL样式所定义的外观将XML文档中的数据进行了表示。一旦浏览器的XSL处理器完成了转换工作,HTML文档即在客户端显示出来了。

总结

在本文中我们探讨了如何使用JSP来处理动态内容,使用XML来代替HTML是符合互操作性的一种方式,这样,我们可以为各种各样的的客户类型提供服务。