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 样式表中,我使用了许多字符串常量: LoginUsername PasswordDayWeekMonth 、本周的每一天,等等。为了将它们国际化,一种方案是实现 XSL 中的资源包查找,另一种方案是在 XML 生成期间(即在 Java 中)完成查找,同时把所需的全部翻译内容添加到 XML 文档中供样式表使用。

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

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

如果能从 XML 中获得语言代码(也就是,如果 Java 代码已在 XML 生成期间加入语言代码),您就可以从您的式样表中调用此方法。另一方面,假如语言代码已经被作为根元素 webcallang 属性加入 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() 方法中实现。但我更喜欢下面的方法,尽管相似却更有效。其代码样例参见清单 1113

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

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

为了让 清单 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 阅读(293) 评论(0)  编辑  收藏 所属分类: XML


只有注册用户登录后才能发表评论。


网站导航: