Vincent.Chan‘s Blog

常用链接

统计

积分与排名

网站

最新评论

抽取界面 (II):对基本框架的若干扩展

级别: 初级

Martin Gerlach, 软件工程师, IBM Almaden 研究中心

2001 年 3 月 01 日

本 文是作者去年 12 月关于 Web应用前端的那篇文章的续篇。本文介绍对 XML 数据和 XSL样式表基本框架的若干扩展,并将集中讨论应用的后端问题,包括国家语言支持(NLS)、视图结构强化和性能问题。上一篇文章介绍了利用 XML 和 XSLT构建 Web 应用的基本结构,本文介绍在使 Web应用在线运行之前所要做的准备工作。

本文是系列文章“抽取界面”的第二篇。为了理解本文出现的概念,您不妨读一下第一篇文章, 用 XML 和 XSL 构建有良好适应性的 Web 应用前端 ,该文讨论了完成此应用所必须的步骤。研究此应用样例的代码也能帮助您理解本文的概念。与第一篇文章一样,本文仍假定您已经熟悉基于 HTTP 协议和 HTTP 请求-响应机制的 Web 应用,同时也假定您曾经用 HTML、甚或 JavaScript 制作过网页。您应懂得 Java 编程语言,知道如何将 Java servlet 用作 Web 应用的访问点。

已讨论过的内容

在第一篇文章中,我介绍了 WebCal,它是一个简单的、基于 Web 的日历应用程序,能够为多个用户维护日历。通过这个应用程序,我演示了如何建立一个不仅能够通过标准 Web 浏览器访问、而且能通过可理解其他格式 ― 例如,WML 和用于语音接口的 VoiceXML ― 的浏览器访问的复杂 Web 应用。那篇文章分三部分讨论上述内容:

  • 对 XML 数据和 XSL 样式表结构化的一种方法
  • 服务器端的 XSL 转换 ― 用 XSLT 和 XPath 生成输出结果
  • 用 XSLT 构建 HTML 表单




回页首


本文的内容

本文也将讨论三个主题,但这三个主题并不像第一篇文章讨论的三个步骤那样互相紧密关联,即它们相互之间不存在特定顺序。但每个主题在使 Web 应用在线运行之前的准备工作中都占有重要一席。这三个主题是:

  • 国家语言支持 (NLS):如果面对许多国家的客户,您将希望提供一个多语言用户界面。您希望系统自动选择用户的首选语言并用该语言显示与 NLS 相关的信息。
  • 强化应用结构: 通常,Web 应用由许多“视图”组成。在 WebCal 应用样例中,视图或者显示一个用户引发的各种事件,或者显示新事件的输入表单。登录页和注册页也是视图。视图可以按不同的标准分组。在 WebCal 中,日视图、周视图和月视图都可以看作是日历视图,它们共享同一个子导航 ― 用于输入新事件的链接。将视图按功能分组,开发人员就可以避免重复编码,从而减轻应用开发工作量,减少错误率。
  • 提高性能: 您希望自己的 Web 应用能够很快作出响应,用户也不希望点击一个链接后等很长时间。此时,您就会想到使用缓存机制,以更快、更可靠地为用户提供服务。

我们先从国家语言支持 (NLS) 着手。如果您对此已经很熟悉,则可转到下一主题, 强化应用结构。





回页首


国家语言支持 (NLS)

为应用赋予多语言用户界面的过程 ― 即加入国家语言支持的过程 ― 称为“国际化”。国际化是 Java 程序的核心问题,因为 Java 通常被设计在任何地方、在任何平台上、(尤为重要的是)以任何人的语言运行。Java 语言有一系列内建的国际化功能,在 developerWorks 上有这方面的文章。

一般而言,关于 NLS 有两方面的内容:

  • 识别客户机的首选语言
  • 在应用中的任何地方访问针对该语言提供的资源

识别客户机的首选语言
就 WebCal 而言(其他 Web 应用也一样),客户机可从世界各地方访问该系统。理想情况下,我们期望以客户机的首选语言提供用户界面。主要有两种方法可用来完成这一任务。

从 HTTP 请求的 Accept-Language 标头中可获得客户机浏览器的语言代码(例如, en-us 和 de)。该标头列出了浏览器所支持的语言,如清单 1 所示。清单中的第一个值即被作为浏览器的(或者说是客户机的)首选语言。


清单 1: HTTP Accept-Language 标头示例
												
														
Accept-Language: en_us;en_ca;de;

在 清单 2 中,首选语言是用 HttpServletRequest.getHeader("Accept-Language") 和基本的 Java 字符串操作提取的。

另一种方法是,我们可以让 Web 应用的用户自行选择首选语言,然后根据用户的选择生成语言代码。您所要做的就是创建一个网页,其中含有供用户选择首选语言的表单。具体实施细则请参阅 第一篇文章 的步骤 3,“使用表单”。

用户首选项与浏览器的语言环境

通过让用户选择将浏览器的语言环境用作他的语言(或语言环境)首选项,您就可以将用户首选项和浏览器输入结合起来使用。这也适用于其它类似的首选项,例如 12 小时制和 24 小时制。

我们为什么要研究 HTTP 标头或用户首选项的解析过程呢?在独立的国际化 Java 应用程序中,语言代码是通过查询系统获得的。在查询过程中, ResourceBundle 类负责为系统语言代码查找正确的资源。但是,在我们的应用样例中,用户的语言(客户机语言)完全与应用所在系统的语言(服务器语言)无关,我们不得不用一个固定的语言代码代表用户的首选语言。

访问针对客户机首选语言提供的资源
一旦成功地获得正确的客户机语言代码,我们就需要使用固定的语言代码查找翻译文本。 清单 3 说明了如何使用清单 2 中的 Locale 对象 locale 完成此项工作。

在第一篇文章中,我介绍过一个基于 XML/XSL Web 应用框架的应用样例。在该框架中,我们需要区分需要 NLS 的两个不同场所:在 Java 代码中和在 XSL 样式表中。

Java 中的 NLS
必须在 Java 中完成待显示文本的 NLS 查找工作。就此处的情形而言,XML 数据可以包含待显示文本 ― 也就是通过 XSL 转换将 XML 数据复制为客户机所要求的格式。在这种情况下,可以使用 ResourceBundle 类(请参阅 清单 3 )来查找已翻译好的字符串常量,并将国际化字符串置入 XML 数据中。

XSL 中的 NLS
有些用户界面可能需要使用特殊的输出格式。例如,我们或许不希望在 Web 浏览器上和 WAP 电话上显示完全一样的输入域标签。对 WAP 电话而言,我们可能希望使用很短的标签,而在 Web 浏览器中,标签就可以长一些,还可以加入帮助内容轮翻显示等功能。在这种情况下,同一个视图的适用于不同格式的 XSL 样式表就可以包含不同的文本。在 WebCal 的 XSL 样式表中,我使用了许多字符串常量: Login 、 Username 、 Password 、 Day 、 Week 、 Month 、本周的每一天,等等。为了将它们国际化,一种方案是实现 XSL 中的资源包查找,另一种方案是在 XML 生成期间(即在 Java 中)完成查找,同时把所需的全部翻译内容添加到 XML 文档中供样式表使用。

利用我在 WebCal 中使用过的 James Clark 的 XSL 处理器 XT(请参阅 参考资源 ),上面的两种方案都能实现。XT 允许回调在 XSL 处理期间访问处于 classpath 中的任何类的静态方法。

设想我们在 com.ibm.almaden.webcal.WebCalUtils 中有一个静态查找方法,如 清单 4所示。

如果能从 XML 中获得语言代码(也就是,如果 Java 代码已在 XML 生成期间加入语言代码),您就可以从您的式样表中调用此方法。另一方面,假如语言代码已经被作为根元素 webcal 的 lang 属性加入 XML 数据中,则您也可以使用 清单 5中重点介绍的 XSL 指令完成查找工作。

为保证此代码样例顺利工作, lsLookUp() ( 请参阅清单 4 )使用的资源包必须在每个语言文件中定义关键字 SUNDAY、MONDAY 等等。尽管可以使用非字符串的数据类型在 XSL 和静态 Java 方法之间传递参数,但我建议使用字符串传递,以避免过多的转换开销。

国际化的其他方面 除了语言之外,关于 NLS 和国际化还有许多问题。如果您还计划使用时间、日期、或者货币符号,您可能需要使用更灵活的显示方式和输入域。

对于从 XSL 中调用的方法,存在过载能力方面的一些限制。详细信息请参阅 XT 文档(请参阅 参考资源)。与在 XML 生成期间完成查找并在 XSL 处理之前将翻译好的字符串包括在 XML 数据中的方法相比较,使用进入 Java 的回调不会产生相同的性能结果。

NLS 小结
这一部分详细分析了国家语言支持问题,说明了如何确定用户的首选语言以及如何用 Java 或 XSL 样式表针对这种首选语言执行 NLS 查找。

下面讲解如何改进应用结构,以使代码更易于维护和扩展。





回页首


强化应用结构

仔细观察 WebCal 应用样例,您会注意到所有的日历视图不仅共享相同的主导航和通用链接(Home 和 Logout),而且还共享包含新事件(New Event)链接的相同子导航条。第一篇文章介绍应用样例时,我认为每个视图的子导航是互不相同的。

在复杂的 Web 应用中,您或许需要将视图按功能分组 ― 例如,分为布局和导航 ― 这样,您就不必一遍又一遍地为每个视图进行定义,而且在需要变动时您只需修改一段代码,无需再修改组中每个视图的样式表。这一点在 WebCal 应用样例中没有使用,不过分组方法基本上有二种:

  • 在导航元数据中定义组
  • 使用XSL 层叠样式表

在导航元数据中定义组 (WebCal: /web/<format>/navigation.xml)
观察 web/html/navigation.xml 中的 navigation.xml 文件,可以发现日历视图的以下 xml 代码:


清单 6: 未分组的视图
												
														
<actions>
...
<action name="ShowDay">
<sub-nav>
<link type="internal" text="New Event" href="ShowNewEvent">
<pass-param name="date"/>
</link>
</sub-nav>
</action>
<action name="ShowWeek">
<sub-nav>
<link type="internal" text="New Event" href="ShowNewEvent">
<pass-param name="date"/>
</link>
</sub-nav>
</action>
<action name="ShowMonth">
<sub-nav>
<link type="internal" text="New Event" href="ShowNewEvent">
<pass-param name="date"/>
</link>
</sub-nav>
</action>
...
</actions>

显然,这种结构难于维护:如果要为三个日历视图添加一个新的子导航链接,就必须在所有三个 <action> 元素中添加。如清单 7 所示,通过分组的视图维护 XML 不是更容易吗?


清单 7: 分组视图
												<actions>
...
<group name="calendar">
<sub-nav>
<link type="internal" text="New Event" href="ShowNewEvent">
<pass-param name="date"/>
</link>
</sub-nav>
<action name="ShowDay">
<sub-nav/> <!-- no view specific sub navigation -->
</action>
<action name="ShowWeek">
<sub-nav/> <!-- no view specific sub navigation -->
</action>
<action name="ShowMonth">
<sub-nav/> <!-- no view specific sub navigation -->
</action>
</group>
...
</actions>

这样一来,您就可以将新的子导航链接添加到 <group> 节点的 <sub-nav> 节点中,它就会在所有三个日历视图中显示出来。由于只需要在一个地方定义该链接,从而提高了结构的可维护性。对本例而言似乎,这似乎没有带来什么好处,但实际的 Web 应用要比 WebCal 复杂得多。

为简单起见,我们假定分组以后再没有未分组的视图。这意味着 navigation.xml 文件中的 <actions> 元素再没有 <action> 子元素。 <actions> 元素只有 <group> 子元素,后者至少再带有一个 <action> 子元素。(通常,这表示在 <group> 节点之外不会有 <action> )。同样,负责为视图生成 XML 的 Java 代码中也要包括包含此视图(或者 action)的 XML 元素 ― 例如, <groupname>calendar</groupname> ,如清单 8 所示。


清单 8: 视图 XML
												
														
<webcal>
<http-params>
<param name="action" value="ShowWeek"/>
<param name="date" value="2001-02-11"/>
</http-params>

<groupname>calendar</groupname>
<user>...</user>
<navigation> ... </navigation> <!-- groups and actions as shown above -->
<now date="2000-11-07T15:30"/>
<week date="2001-02-11" previous="2001-02-04" next="2001-02-18">
<day date="2001-02-11"/>
...
</week>
</webcal>


在第一篇文章的清单 26 中,我介绍过一段为视图创建子导航链接的 XSL 代码。 清单9 是一段用视图分组创建子导航的 XSL 代码,它说明了如何从 XML 数据中获取当前的 action 和 group (如清单 8 所示)。(在该清单中, action 代表 HTTP 的 action 参数在服务器上触发的一些具体操作。每个这样的操作都生成一个视图,并会显示这个显示。)

在 WebCal 中,子导航是区分视图和组的唯一导航标准,但更复杂的应用可能会有若干个由分组机制处理的参数。另外一种分组机制要使用 XSL 层叠样式表。

XSL 层叠样式表
<xsl:include> 除了在导航元数据中完成分组外,还可以在样式表中用于控制主面板 (main panel)(例如, WeekViewMain.xsl )。这样,某个组的视图样式表就会包括该组中所有视图使用的 XSL 模板。这就好比许多 Java 方法调用一个工具方法。第一篇文章介绍了如何由 frame.xsl 定义的总框架和与具体视图相关的主面板来构建所有视图。在同组视图的总面板中,可以包括一个与具体组相关的“内层框架”样式表,它产生组中所有视图共同具有的那些响应。最后,内层框架再调用与具体视图相关的样式表,就像在 frame.xsl 中调用主模板那样。这种“层叠”机制如图 1 所示。


图 1:子框架

“层叠”机制能应用到任何一层,从而形成组嵌套。

小结:强化应用结构
本部分介绍了如何对应用提供的视图进行分组,以便于开发和更改管理。我推荐了两种实现分组的方法:

  • 允许在 XML 元数据中定义组 ― 例如,在导航信息中。
  • 使用 XSL 层叠样式表。




回页首


性能:缓存探讨

现在我们研究一下我们的 Web 应用的性能。有多种方法可用来提高类似 WebCal 样例那样的原始应用的性能,最强有力的方法之一便是缓存。

生成 XML、然后再用 XSLT 将它转换成客户机首选格式的过程涉及许多步骤。根据对系统需求的不同,可以在几个不同的起点引入缓存。基本而言,可以通过对转换的中间结果进行缓存来提高性能,此处可以使用 URL 请求(就是 action 名和所有的 HTTP 参数)、用户名和预期格式的组合,并将该组合用作缓存的散列键。

存在三种缓存方法,它们可以结合使用:

  • 缓存 XSL 样式表
  • 缓存 XML
  • 缓存响应,表示 XML 已经用 XSL 样式表转换成了用户的首选格式。

下面将对三种方法逐一深入探讨。

缓存 XSL 样式表
观察第一篇文章中的 XSL 转换代码(清单 16),可以发现每次将对一个请求的 XML 响应(或者新产生的,或者从缓存中获取的)转换为客户机的首选格式时,在实际转换发生之前总要完成以下两个步聚:

  • 创建 XSLProcessor 类的一个实例
  • 上面创建的处理器对转换所需的样式表进行分析。

XSLProcessor 实例可以在样式表分析完成后进行缓存,以备后用。由于处理器与请求者和参数无关,缓存散列键就可以仅由 HTTP 的 action 参数值组成。该参数决定了要显示的视图和客户机的首选格式(例如 HTML)。这样,当用户请求某个视图后,缓存的处理器实例就可用来处理其他用户对此视图的请求。

此方案的好处在于避免了每次请求视图时都要实例化一个 XSLProcessor 实例并进行样式表分析。这就意味着一个缓存的处理器可以被所有用户使用,效率很高。

此方案可在 WebCal 的 WebCalUtilities.transform(...) 方法中实现,如 清单 10 所示。它将样式表 URI 作为散列键,其中包含 action 的名称和格式(URI 的格式如下: http://localhost/webcal/<format>/<actioname>.xsl )。

缓存 XML
WebCal 已经实现了一项简单的性能优化:navigation.xml 文件或用于其他格式的文件在 servlet 首次被加载并初始化时就同时被读入。产生的导航 XML 文档碎片先接格式缓存,然后在响应客户机请求时添加到所生成的视图 XML中。

下一步是缓存在响应请求时创建的完整 XML 文档。为此,您只需使用一个 hashtable 或者一个类似的集合来存储 XML 文档。这样,每当用户返回到他们已经访问过的页面时,URL 请求总会或多或少有些相似之处 ― 至少相关的参数、用户名、action 名称、期望格式、视图特定的参数将会是相同的。如果用这些参数计算散列键,则很容易在缓存中找到同一视图的旧版本。该方法的好处在于不用频繁地访问后端应用(数据库、EJB 等)。

该方法可以在 WebCal 的 ShowAction.displayResult() 方法中实现。但我更喜欢下面的方法,尽管相似却更有效。其代码样例参见清单 11到 13。

缓存响应
这一方法是刚介绍的 XML 缓存和样式表缓存的结合。缓存响应的优势是,如果之前曾有过同样的请求并且结果仍存在缓存中,那么就可以在很短的时间内直接从服务器上得到响应。它的工作机制与上面介绍的 XML 缓存方法很相似,只是在这里整个转换过程的最后结果都被缓存,使用的散列键与上面的相同。此方法的优点是访问后端的频率较低, 并且不必反复分析样式表。

此方法可以在 WebCal 的 ShowAction.displayResult() 方法中实现,如清单 11 到 13所示。

为了让 清单 11 正常运行,必须对 ShowAction.java 或其超类 Action.java 作一定的修改,如清单 12 所示:


清单 12: 修改后的 Action 类 (在 Action.java 或 ShowAction.java 中)
												
														

在 Action.java :
protected static Hashtable cache = new Hashtable();
...
// 字节数组的包装(内部类)
protected class CachedResponse
{
public byte[] responseBytes;
public CachedResponse(byte[] responseBytes)
{
this.responseBytes = responseBytes;
}
}


最后,执行 XSL 转换的方法还应返回转换的结果,以便将其保存在缓存中。我在此处选择 byte[] (字节数组)作为数据类型是因为它独立于任何具体的字符编码。 清单 13 显示了必要的修改。

缺点:缓存清除和依赖性
利用缓存提高性能有几个缺点。每当一个用户通过 Web 应用接口作了某些更改时,XML 缓存或响应缓存必须执行相关性检查。从缓存中删除需要更新的页面。

处理此问题最简单的办法是为作出更改的用户清除缓存。但是,如果系统允许用户间交互 ― 例如,数据共享、小组日历、会议邀请和调度变更 ― 则当仅有一个用户更改某些数据时,所有用户的缓存都要被清除。

为了获得更高的效率,缓存还应当保存每个页面的相关性信息 ― 页面所依赖的那部分系统。例如,WebCal 中的星期视图依赖于用户登录的那一周所创建、修改和删除的事件。相关性信息及其它附加信息,例如比较缓存数据日期与后端(数据库)数据日期的时间戳,可以存储于 清单 12 定义的 CachedResponse 类中。

正面评价:不必清除 XSL 处理器缓存。与样式表关联的 XSL 处理器可以一直使用到样式表改变为止,但样式表的更改一般不会在运行时发生。

不仅仅是缓存
缓存中的信息可用来分析用户行为,例如登录后通常进行什么操作。使用这些信息,可以预先生成页面并以动态链接方式提供给用户,也可只将预先生成的页面保存于缓存中,当客户机发出请求时即可快速访问。

性能:学有所获
本部分介绍了如何使用不同的缓存方法提高应用的性能:缓存 XSL 处理器实例,缓存 XML,以及缓存 XSL 转换的实际结果。缓存的内容可用于统计计算,这反过来又可用来预先生成用户在会话期间可能会请求的页面。





回页首


小结

在第一篇文章中,我介绍了使用 XML 和 XSL 转换创建可扩展 Web 应用的基础知识。本文接着介绍了如何添加一些高级功能。

主要内容是:

  • 使用国家语言支持实现多语言全球访问
  • 强化应用结构,以方便维护、更改管理以及进一步的开发。
  • 缓存是如何提高性能的。

要使 Web 应用更富有专业色彩并使它们能够随时在线运行,所有这些都是很重要的因素。





回页首


参考资料

下载 WebCal 应用样例(Windows .zip 格式)。





回页首


关于作者

Martin Gerlach,在 IBM Almaden 研究中心计算机科学部从事毕业后研究工作 , 拥有德国汉堡应用科学大学的计算机科学学位。可以通过 mgerlac@almaden.ibm.com 与 Martin 联系。


posted on 2006-03-21 23:30 Vincent.Chen 阅读(347) 评论(0)  编辑  收藏 所属分类: XML