级别: 初级
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 应用样例中没有使用,不过分组方法基本上有二种:
在导航元数据中定义组 (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
格式)。
关于作者