級別: 初級
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
格式)。
關于作者