|
Norbert Lindenberg 2003 年 12 月
English: Developing Multilingual Web Applications Using JavaServer Pages Technology 日本語: JavaServer Pages 技術(shù)による多言語 Web アプリケーションの開発
JavaServer Pages (JSP) 技術(shù)現(xiàn)已成為深受 Web 應(yīng)用程序開發(fā)者歡迎的工具。 使用 JSP 技術(shù),開發(fā)者不需要其他的編程知識就可以設(shè)計(jì)出動態(tài)的 web 網(wǎng)頁。 同時(shí),Web 開發(fā)者可以使用一種可擴(kuò)展的標(biāo)記機(jī)制來管理基礎(chǔ)軟件組件的功能。
通過 Java 標(biāo)準(zhǔn)制定組織(Java Community Process)開發(fā)的一個(gè)擴(kuò)展功能可為多語言應(yīng)用程序的開發(fā)提供更有力的支持。 JavaServer Pages 標(biāo)準(zhǔn)標(biāo)記庫除了其他一些功能,還定義了一套可實(shí)現(xiàn)本地化和地區(qū)敏感(locale-sensitive)格式化的標(biāo)記。
行文方面,本文首先對 JavaServer Pages 技術(shù)進(jìn)行了簡要介紹,以使您能夠更好地理解如何使用它們解決國際化的問題。 然后,我會針對多語言 web 應(yīng)用程序的開發(fā)討論幾個(gè)核心問題,并介紹如何使用 JavaServer Pages 技術(shù)解決它們:這些問題包括地區(qū)確定和本地化、字符編碼、格式化以及解析。
JavaServer Pages 技術(shù)
JavaServer Pages(和幾種相關(guān)技術(shù))構(gòu)成了 web 應(yīng)用程序的表示層。 使用 JSP 技術(shù),開發(fā)者可以創(chuàng)建動態(tài)的 web 頁面,這些頁面可以與商業(yè)邏輯(business logic)、數(shù)據(jù)庫以及其他可從網(wǎng)絡(luò)上獲取的服務(wù)形成互動關(guān)系。
JavaServer Pages
使用 JSP 技術(shù)開發(fā)的網(wǎng)頁結(jié)合了 HTML、XML 或其他含有類似 XML 標(biāo)記(這些標(biāo)記與基礎(chǔ)軟件庫連接)的靜態(tài)內(nèi)容,通常這些軟件庫使用 Java 編程語言編寫。 在這種環(huán)境中,非常重要的 Java 技術(shù)有 JavaBeans 組件架構(gòu)(作為 JSP 和 Java 類之間的常規(guī)用途接口)、用于訪問 SQL 數(shù)據(jù)庫的 Java 數(shù)據(jù)庫連接(JDBC)API 以及各種用于 XML 處理的庫。
JSP 頁面本身按照 servlet 格式被編譯為 Java 代碼,以便執(zhí)行。 Servlet 是 web 服務(wù)器的擴(kuò)展,它被編譯并關(guān)聯(lián)至服務(wù)器,從而可以獲得比腳本語言更快的執(zhí)行速度。 Servlet 直接以 Java 編程語言編寫,并經(jīng)常與 JSP 網(wǎng)頁一起使用,其中 servlet 作為控制部分而 JSP 頁面作為應(yīng)用程序的視圖部分。
JavaServer Pages 和底層的 servlet 技術(shù)為處理 HTTP 請求和回應(yīng)信息,以及使用 Cookies 或 URL 重寫進(jìn)行會話維護(hù)都提供了廣泛的支持。
使用 JSP 技術(shù)的一個(gè)很重要的原因在于它可以將網(wǎng)頁作者和應(yīng)用程序開發(fā)者的工作進(jìn)行分離。 盡管可以將 Java 語句直接嵌入 JSP 網(wǎng)頁,但是,開發(fā)者們已經(jīng)認(rèn)識到最好避免如此,而現(xiàn)在更傾向于使用自定義標(biāo)記。
JavaServer Pages 標(biāo)準(zhǔn)標(biāo)記庫
JavaServer Pages 標(biāo)準(zhǔn)標(biāo)記庫(JSTL)包含了一系列涵蓋數(shù)個(gè)功能領(lǐng)域的自定義操作,這些功能在 JSP 網(wǎng)頁中經(jīng)常被使用。 該庫建立在許多參與者開發(fā)自己的庫時(shí)所獲得的經(jīng)驗(yàn)基礎(chǔ)之上,它提供了一種應(yīng)用程序可以依靠的標(biāo)準(zhǔn)接口,并且可以獨(dú)立于他們運(yùn)行的服務(wù)器之外。
除了自定義標(biāo)記,JSTL 還引入了一種表達(dá)語言,這種語言運(yùn)進(jìn)一步地減少了在 JSP 網(wǎng)頁中使用腳本語言的需求,同時(shí)還引進(jìn)了標(biāo)記庫驗(yàn)證程序以限制在 JSP 網(wǎng)頁上對腳本和標(biāo)記庫的使用。 這種改進(jìn)版本的表達(dá)語言,以及限制腳本的功能已在隨后被集成到 JSP 2.0 規(guī)范之中,所以只有使用 JSP 1.2 時(shí)才要求 JSTL。
自定義操作包括的主要內(nèi)容是:
- 變量操作: “核心”庫中的一些操作,可在不同的領(lǐng)域(網(wǎng)頁、請求、會話和應(yīng)用程序)中定義、刪除變量或者將變量植入生成的頁面中。
- 控制流: “核心”庫中的幾種操作,提供了基于標(biāo)記的控制流結(jié)構(gòu)(例如條件和迭代器),以減少對嵌入腳本語言代碼的需求。
- URL 相關(guān)操作: “核心”庫中的一些操作,可讓 JSP 網(wǎng)頁導(dǎo)入由 URL 定義的內(nèi)容,可將內(nèi)部形式的 URL 重寫為外部形式(這可能包括收集會話跟蹤信息)或重新定向至一個(gè)不同的網(wǎng)頁。
- XML 處理: “xml”庫中的操作,包括解析 XML 文檔并使用 XPath 表達(dá)式來解壓縮內(nèi)容,基于 XPath 表達(dá)式的控制流以及使用 XSLT 樣式表進(jìn)行的轉(zhuǎn)換。
- 關(guān)系數(shù)據(jù)庫訪問: “sql”庫中的操作,允許 web 應(yīng)用程序執(zhí)行簡單的 SQL 查詢和更新。
- 國際化和格式化(本文的中心內(nèi)容):“fmt”庫中的操作,支持地區(qū)確定、本地化、字符編碼決定和地區(qū)敏感格式化與解析。
地區(qū)確定和本地化
設(shè)計(jì)多語言 web 應(yīng)用程序時(shí),您必須首先決定如何確定用戶的語言和地區(qū)首選項(xiàng),以及如何使這些首選項(xiàng)與該應(yīng)用程序和基礎(chǔ)的 Java 運(yùn)行環(huán)境支持的一套地區(qū)設(shè)置相匹配。 這部分首先描述了 web 應(yīng)用程序必須具有的外部環(huán)境和要求。 下一步,我們將了解相關(guān)的 Java 2 Standard Edition (J2SE) 平臺提供的功能,最后我們將了解 JavaServer Pages 標(biāo)準(zhǔn)標(biāo)記庫的標(biāo)記如何連接到環(huán)境和 J2SE 中。
確定用戶首選項(xiàng)
web 應(yīng)用程序有兩種方法來確定用戶的語言首選項(xiàng):首先,它可以由瀏覽器使用 HTTP 請求報(bào)頭字段 Accept-Language 傳輸至服務(wù)器的語言和地區(qū)首選項(xiàng)。 盡管標(biāo)準(zhǔn)規(guī)定了許多語言標(biāo)記,但是一般使用 ISO 639 語言代碼(如 ja 為日文)和 ISO 3166 國家代碼(如 IT 代表意大利)。 瀏覽器通常讓用戶創(chuàng)建一個(gè)語言列表作為其首選項(xiàng)的一部分。 然而,這種方法不太可靠;用戶不一定會創(chuàng)建該列表,而且該列表不一定會包含該應(yīng)用程序支持的地區(qū)設(shè)置。 由于這些不確定性,多語言應(yīng)用程序通常采用第二種方法:他們讓用戶直接從支持語言的列表中選擇,并把選擇的語言作為用戶資料的一部分進(jìn)行保存,或只在進(jìn)行該會話的時(shí)候保存。 一個(gè)好的方法是,在對用戶一無所知的情況下,首先使用 Accept-Language 信息,在應(yīng)用程序的開始頁面中給予用戶直接選擇語言的機(jī)會。
將 Accept-Language 地區(qū)設(shè)置主要用于語言和文化首選項(xiàng)是沒什么意義的。 例如,它們不應(yīng)該被解釋為表示用戶居住的國家。 同樣,在很多情況下瀏覽器提供的地區(qū)設(shè)置只有一個(gè)語言代碼,而一些地區(qū)敏感的功能(例如,日期格式)則隨著國家的不同而相異。 在很多情況下,通過語言假設(shè)主要國家的規(guī)范是合理的(如果沒有指定國家);例如,如果指定了日本語,則可使用日本通用的日期格式。 然而,如果是根據(jù)國家而設(shè)置的重要功能(例如貨幣),則必須給用戶一個(gè)機(jī)會來修正該假設(shè)。
在許多情況下,web 應(yīng)用程序是由若干組件組合而來的,這些組件可能已經(jīng)本地化為不同的語言。 一個(gè)特別值得一提的組件是 Java 運(yùn)行環(huán)境,它在一些地區(qū)敏感區(qū)域可能具備支持超過 40 種語言中的 100 種區(qū)域設(shè)置的功能(例如日期格式),遠(yuǎn)遠(yuǎn)超出了典型的 web 應(yīng)用程序。 因此,應(yīng)用程序開發(fā)者必須決定是否在整個(gè)應(yīng)用程序中限制所支持語言的本地化功能,或者充分發(fā)揮每個(gè)組件的功能優(yōu)勢。 第一種方法的優(yōu)勢在于用戶可以看到的全部頁面都使用同一語言,而第二種方法可能導(dǎo)致頁面中存在不同的語言——其中一種語言出現(xiàn)在絕大多數(shù)文本中,而另一種則出現(xiàn)在例如日期的格式中。
Java 2 Standard Edition Platform 中的本地化
為了了解 JSTL 如何確定應(yīng)用程序被哪些地區(qū)設(shè)置支持,我們來看看在基礎(chǔ)的 Java 2 Standard Edition 平臺中是如何進(jìn)行本地化的。 java.util 軟件包的核心主要有兩種類:Locale 和 ResourceBundle。
Locale 對象只是用來確定地區(qū)設(shè)置的:它們結(jié)合了 ISO 639 語言代碼(例如,ja 代表日文)和 ISO 3166 國家代碼(例如,IT 代表意大利),還可能包含一個(gè)(非標(biāo)準(zhǔn)化的)變量字符串。 注意,HTTP 的地區(qū)標(biāo)識符使用相同的 ISO 標(biāo)準(zhǔn),所以對比通常比較容易。
ResourceBundle 對象是本地化對象的容器,形成成鍵/值對。 一個(gè)基礎(chǔ)資源束定義了一個(gè)基礎(chǔ)束的名稱、一套鍵以及默認(rèn)值(通常是英文值,但不是必須的)。 例如,一個(gè)簡單的 Messages 資源束可能定義 greeting-day 鍵的默認(rèn)值為 Hello。 其他的特定語言和國家束可以被定義,其名稱由基礎(chǔ)名稱組成(通過后綴指示其語言、國家和變量)并提供已本地化的值。 例如,德語 Messages_de 資源束可以給出 Guten Tag 值(針對 greeting-day 鍵),而一個(gè)奧地利 Messages_de_AT 束可能用 Servus 值來覆蓋該值。 資源束可以作為 Java 類或簡單的“屬性”文本文件來執(zhí)行。
JavaServer Pages 應(yīng)用程序的本地化方法
要對基于 JavaServer Pages 技術(shù)的應(yīng)用程序進(jìn)行本地化,方法通常有兩個(gè)。 第一個(gè)方法是使用國際化的頁面,這些頁面常可以通過自定義標(biāo)記從資源束獲得與特定地區(qū)設(shè)置相關(guān)的內(nèi)容。 如果頁面需要保持復(fù)雜的結(jié)構(gòu)并與所有地區(qū)設(shè)置同步,則通常會采取這種方法。 第二種方法使用單獨(dú)的特定地區(qū)設(shè)置頁面以及分發(fā)到適當(dāng)頁面的 servlet(取決于用戶的地區(qū)選擇)。 如果頁面包含的主要是文本或者地區(qū)設(shè)置間的結(jié)構(gòu)截然不同時(shí),則通常會采取這種方法。
地區(qū)確定和 JSTL 中的本地化
JSTL 構(gòu)建于 J2SE 工具之上,它可進(jìn)行地區(qū)確定和本地化。 使用任何一種 JSP 本地化方法(如上所述)均可以進(jìn)行地區(qū)確定,而本地化功能的目的是為國際化的頁面提供支持。
JSTL 對上述兩種確定用戶地區(qū)首選項(xiàng)的方法都提供支持。 應(yīng)用程序可以使用 JSTL 的 <fmt:setLocale> 操作,指定一個(gè)固定的地區(qū)(通常是用戶從支持語言列表中所直接選擇的)。 一旦使用了該操作,指定的地區(qū)設(shè)置將應(yīng)用于所有的地區(qū)敏感操作中。 如果沒有使用 <fmt:setLocale> 操作,地區(qū)敏感操作將會從地區(qū)選擇列表中搜索第一種支持的地區(qū)設(shè)置,這些地區(qū)設(shè)置通常由 Accept-Language 報(bào)頭提供。
下面是一些您可以用于 web 應(yīng)用程序開始頁面的代碼片斷。 這些代碼片斷可讓用戶非常輕松地選擇他或她的地區(qū)設(shè)置。 假設(shè)這些代碼是 locale-choice.jsp 頁面中的一部分:
<%-- Interpret user‘s locale choice --%>
<c:if test="${param[‘locale‘] != null}">
<fmt:setLocale value="${param[‘locale‘]}" scope="session" />
</c:if>
<%-- Offer locale choice to user --%>
<a href="locale-choice.jsp?locale=en-US">USA</a> -
<a href="locale-choice.jsp?locale=de-DE">Deutschland</a> -
<a href="locale-choice.jsp?locale=ja-JP">日本</a>
<%-- Use URL rewriting to ensure proper session tracking --%>
<form method="get" action="<c:url value=‘/locale-choice.jsp‘ />">
<input type=submit value="Stay in session">
</form>
第一部份(此部分必須在生成的 HTML 頁面任何內(nèi)容之前)表示用戶的地區(qū)選擇,該選擇作為一個(gè)請求參數(shù)顯示在 JSP 頁面上。 如果定義了 locale 參數(shù),則它將被用于進(jìn)行會話的地區(qū)設(shè)置。
第二部分(此部分是生成的 HTML 頁面內(nèi)容的一部分)為用戶提供了返回同一頁面的鏈接,但是根據(jù)選定的國家提供了 locale 參數(shù)設(shè)置。 注意,在本地語言中已經(jīng)給出國家的名稱,所以即使頁面的其他部分已經(jīng)本地化為用戶不能識別的語言,但是用戶仍然可以容易地識別這些名稱。 例如,“日本”是“日本”的數(shù)字字符引用,即“日本”的日語單詞。 新版的瀏覽器如果裝有日本字體,將會正確地轉(zhuǎn)換這些文本;對于舊版的瀏覽器,使用圖片則可能更合適一些。
最后一部分顯示如何使用 <c:url> 標(biāo)記生成 URL,此 URL 包含了一個(gè)會話 ID,如果需要對該會話進(jìn)行追蹤的話(如果用戶啟用了 cookies,則 cookies 將代替 URL 重寫)。 這將確保一旦選擇了地區(qū)設(shè)置,該選擇將應(yīng)用于該 web 應(yīng)用程序中的所有頁面。
如果從 web 應(yīng)用程序本身的用戶界面中選擇了地區(qū)設(shè)置,然后使用 <fmt:setLocale> 進(jìn)行設(shè)置,那么就可以假設(shè)該應(yīng)用程序確實(shí)支持此地區(qū)設(shè)置。 另一方面,如果沒有使用 <fmt:setLocale> 并且 JSTL 必須從 Accept-Language 報(bào)頭中的地區(qū)設(shè)置列表中找到一個(gè)支持的地區(qū)設(shè)置,那么情況會變得更復(fù)雜。
要決定哪個(gè)地區(qū)設(shè)置是被支持的,JSTL 將參考該應(yīng)用程序所使用的資源束。 有兩種操作可用于訪問資源束:<fmt:bundle> 和 <fmt:setBundle>。 它們的基本功能是相同的:它們查詢一個(gè)資源束并創(chuàng)建一個(gè)“本地化環(huán)境”,在這個(gè)“本地化環(huán)境”中包含了對該資源束和用于請求該資源束的地區(qū)設(shè)置的引用。
<fmt:bundle> 和 <fmt:setBundle> 操作所使用的資源束查詢允許多次請求地區(qū)設(shè)置(這樣它就可以處理由 Accept-Language 報(bào)頭提供的列表),并使用由 web 應(yīng)用程序定義的備用(fallback)地區(qū)設(shè)置。 如果使用 <fmt:setLocale> 操作設(shè)置地區(qū),那么 <fmt:bundle> 和 <fmt:setBundle> 操作將請求用于該地區(qū)設(shè)置的束,或者,如果不成功的話,將請求用于備用地區(qū)設(shè)置的束。 如果沒有使用 <fmt:setLocale>,隨后的操作將請求用于由 Accept-Language 報(bào)頭提供的地區(qū)設(shè)置和備用地區(qū)設(shè)置,直到請求成功。 在每種情況下,基本查詢(查詢束的基本名稱和一個(gè)請求地區(qū)設(shè)置)將為請求地區(qū)設(shè)置本身搜索一個(gè)資源束,隨后搜索更簡單的地區(qū)設(shè)置(首先從請求地區(qū)設(shè)置中舍棄變量,然后舍棄國家組件)。 如果全部的請求地區(qū)設(shè)置以及備用地區(qū)設(shè)置的查詢都失敗,那么將使用基礎(chǔ)束。
以下是一些例子。 讓我們假設(shè)某個(gè)應(yīng)用程序擁有用于 en、zh_CN、zh_TW、ja 和 ko 的束。 備用地區(qū)設(shè)置被設(shè)置為 en。 沒有使用 <fmt:setLocale> 標(biāo)記。 下表顯示最終本地化環(huán)境的束和地區(qū)設(shè)置(用于一些請求地區(qū)設(shè)置列表):
|
被請求的地區(qū)設(shè)置
|
結(jié)果束
|
結(jié)果地區(qū)設(shè)置
|
|
zh_SG、zh_CN
|
zh_CN
|
zh_CN
|
|
zh、ja
|
ja
|
ja
|
|
es_MX
|
en
|
en
|
|
en_US
|
en
|
en_US
|
|
ja、zh_CN
|
ja
|
ja
|
ResourceBundle 類的行家會注意到,JSTL 操作所使用的查詢策略與 ResourceBundle 所使用的查詢策略是不同的。 ResourceBundle 使用的策略只接受一個(gè)請求地區(qū)設(shè)置,這不足以處理由 Accept-Language 報(bào)頭所提供的地區(qū)設(shè)置列表,并且它會恢復(fù) Java 運(yùn)行環(huán)境的默認(rèn)地區(qū)設(shè)置,此默認(rèn)地區(qū)設(shè)置與 web 應(yīng)用程序和其用戶并不相關(guān),使用它將導(dǎo)致不可移植性。
那么,為什么查詢資源束時(shí)存在兩種不同的操作呢? 它們的區(qū)別在于它們使用的方法:<fmt:bundle> 標(biāo)記提供了一個(gè)用于嵌套標(biāo)記的環(huán)境,而 <fmt:setBundle> 操作將最終本地化環(huán)境存儲在一個(gè)變量中,此變量可以在相同頁面中被后繼操作訪問,并且可以被其他頁面中的操作所訪問(這取決于該變量的范圍)。
<fmt:message> 操作是一種利用本地化環(huán)境的 JSTL 標(biāo)記。 它最簡單的形式是,它從一個(gè)本地化環(huán)境的資源束中為一個(gè)指定的鍵獲取一條信息并將該信息插入生成的頁面中。 下面例子顯示了它的不同用法:
<fmt:setBundle basename="Errors" var="errorBundle" />
<fmt:bundle basename="Messages">
<%-- Localization context established by <fmt:bundle> tag --%>
<fmt:message key="greeting" />
<p>
<%-- Localization context established by <fmt:setBundle> tag --%>
<fmt:message key="emptyField" bundle="${errorBundle}" />
</fmt:bundle>
其次,為什么有一個(gè)請求地區(qū)設(shè)置與本地化環(huán)境相關(guān)聯(lián)? 這個(gè)地區(qū)設(shè)置是 JSTL 將格式化標(biāo)記限制到應(yīng)用程序所支持的語言范圍內(nèi)的方法,這樣展現(xiàn)在讀者面前的頁面語言將完全統(tǒng)一。 嵌套于 <fmt:bundle> 標(biāo)記中的格式化操作使用該標(biāo)記的本地化環(huán)境來確定它們應(yīng)該使用的地區(qū)設(shè)置。 例如,讓我們觀察下面的頁面片斷:
<jsp:useBean id="now" class="java.util.Date" />
<fmt:formatDate value="${now}" timeStyle="long" dateStyle="long" />
<p>
<fmt:bundle basename="Messages">
<fmt:formatDate value="${now}" timeStyle="long" dateStyle="long" />
</fmt:bundle>
如果 HTTP Accept-Language 地區(qū)設(shè)置是 fr 和 en,并且基礎(chǔ)的 Java 運(yùn)行環(huán)境對這兩種語言的日期格式都支持(但 web 應(yīng)用程序的 Messages 束只存在于 en),那么第一個(gè)日期采用法文格式,而第二個(gè)則采用英文格式。 因此,頁面設(shè)計(jì)者可以決定是使用統(tǒng)一的語言還是通過選擇適當(dāng)?shù)臉?biāo)記嵌套來利用所有現(xiàn)有的本地化信息。
最后,為什么本地化環(huán)境使用請求地區(qū)設(shè)置而不使用由資源束找到的地區(qū)設(shè)置? 答案是,這樣可以避免丟失重要的信息,某些格式標(biāo)記可能需要這些信息。 很多應(yīng)用程序不能區(qū)分相同語言中不同變量之間的區(qū)別,而且只提供(例如)英文資源束,期望著這些文本在英國、澳大利亞和新加坡都能被同樣理解。 然而對于日期格式,國家是很關(guān)鍵的——對于英國讀者來說,“2/6/02”表示“ 2002 年 6 月 2 日”,但對于習(xí)慣美國規(guī)范的讀者來說,則表示“2002 年 2 月 6 日”。 所以,在很多情況下,如果使用了請求地區(qū)設(shè)置(而不是資源束地區(qū)設(shè)置),則國家信息將會被保留。
字符編碼
當(dāng)前,我們使用兩種截然不同的模塊表示存儲在計(jì)算機(jī)中或通過網(wǎng)絡(luò)傳輸?shù)奈谋荆号f的字符編碼模式專門用于較小的語言集合、國家和/或操作系統(tǒng)(包括如 ISO 8859 系列、 Windows 代碼頁和 EUC 編碼);而新的基于 Unicode 編碼的模式能夠(至少理論上能夠)表示所有的語言并可以在任何地方使用。
舊的模塊具有很大的劣勢:
- 每種舊的字符編碼方法通常只支持一個(gè)小的語言集合。 例如,Shift-JIS 支持日文和英文,但不支持其他的亞洲或歐洲語言。 ISO 8859-1 支持一些西歐語言但不支持東歐語言。
- 字符轉(zhuǎn)換可能會帶來意料之外的信息丟失。 開發(fā)者通常選擇 ISO 8859-1 作為德語、法語和其它西歐語言的編碼方法,然后會很驚訝地發(fā)現(xiàn)“€”字符(德國、法國和許多其他歐洲國家通用貨幣的標(biāo)志)不被這種編碼所支持。 為了防止這種信息丟失,您必須使用 Windows-1252、ISO 8859-15 或其他編碼方法,這取決于瀏覽器的基礎(chǔ)操作系統(tǒng)。
當(dāng)前版本的主要軟件系統(tǒng)所包含的創(chuàng)建、分發(fā)和解釋 web 內(nèi)容都支持新的模塊;它們通常將 Unicode 用于內(nèi)部處理,或者至少知道怎么使用(用于 web、基于 Unicode 編碼的)UTF-8。 基于 Unicode 的編碼有著以下顯著的優(yōu)勢:它們支持多語言頁面并清晰區(qū)分地區(qū)設(shè)置(從字符編碼處理)問題。 同樣,因?yàn)榫幋a轉(zhuǎn)換而帶來的信息丟失的風(fēng)險(xiǎn)也很小,同時(shí)基于 Unicode 的編碼與現(xiàn)在的服務(wù)器和客戶端系統(tǒng)比較吻合。
盡管如此,很多 web 開發(fā)者仍然不太愿意使用 UTF-8。其中的原因可能包括對舊版本的瀏覽器支持不充分,或者缺少支持它的工具。
JavaServer Pages 技術(shù)對新舊兩種模塊都支持。 現(xiàn)在我們來看看字符編碼問題所涉及的各種不同領(lǐng)域,并了解 JSP 技術(shù)和 JSTL 如何處理它們。
處理源程序頁編碼
JSP 源文件的編碼通常由可用的編輯工具決定,所以可能使用特定國家和操作系統(tǒng)的編碼。 字符編碼與 JSP 運(yùn)行環(huán)境(“容器”)之間的通訊方法有許多種,隨著時(shí)間推移其中的機(jī)制和規(guī)則已不斷改進(jìn)。 同時(shí) JSP 源文件相應(yīng)存在著兩種語法:標(biāo)準(zhǔn)語法和基于 XML 的新語法。
在檢測字符編碼時(shí),JSP 2.0 規(guī)范將在這兩種語法中進(jìn)行辨別。 對于采用 XML 語法的文件,編碼將被檢測為采用 XML 規(guī)范;這意味著 UTF-8 或 UTF-16 為默認(rèn)的編碼,而其他的編碼必須在文件開始處的 XML 聲明中予以說明。 對于采用標(biāo)準(zhǔn)語法的文件,容器將考慮兩種主要的信息來源:首先它們訪問應(yīng)用程序的配置描述符,查詢一個(gè) page-encoding 元素,該元素位于 jsp-property-group(其 URL 格式與文件相匹配);然后在此頁中查詢 pageEncoding 屬性。 如果兩者都沒有,容器也會尋找 contentType 屬性中的 charset (參閱下一部分“處理 Web 頁面編碼”),或使用 ISO 8859-1 作為最終的備用選擇。
以下是基于 JSP 2.0 的應(yīng)用程序的一些簡單建議:對于采用 XML 語法的文件,確保沒有使用 UTF-8 或 UTF-16 編碼的文件能夠正確識別它們的字符編碼。 對于采用標(biāo)準(zhǔn)語法的文件,如果您對所有源文件使用 UTF-8,則請?jiān)谂渲妹枋龇兄皇褂靡粋€(gè)元素 page-encoding 來闡述它。 如果您使用特定地區(qū)設(shè)置編碼,則根據(jù)該地區(qū)設(shè)置來組織或命名您的文件,并使用 page-encoding 元素來描述它們的關(guān)系。 例如,如果全部的韓文文件以 EUC-KR 編碼并保存在 /ko/KR web 的應(yīng)用程序的子目錄中,請使用以下語句:
<jsp-property-group>
<url-pattern>/ko/KR/*</url-pattern>
<page-encoding>EUC-KR</page-encoding>
</jsp-property-group>
如果應(yīng)用程序中的源文件不能以這種方式組織,則為每個(gè)源文件添加 pageEncoding 屬性。 不過請切記,此屬性必須能夠在文件的開始處找到并且只能用于標(biāo)識 ASCII 擴(kuò)展碼的字符編碼。 后一個(gè)限制考慮到了 UTF-8 和許多舊的字符編碼,但沒考慮 UTF-16 或基于 EBCDIC 的編碼。 不建議根據(jù) contentType 屬性中的 charset 值來標(biāo)識源程序頁編碼;這個(gè)值應(yīng)只用于標(biāo)識 web 頁面編碼(參見下一部分)。
關(guān)于源文件字符編碼,JSP 1.2 規(guī)范沒有清楚地區(qū)分使用標(biāo)準(zhǔn)語法的文件和使用 XML 語法的文件。 它也沒有提供識別配置描述符中的字符編碼的方法。 為確保正確檢測字符編碼,設(shè)計(jì)用于 JSP 1.2 容器的應(yīng)用程序應(yīng)總是識別每個(gè)使用 pageEncoding 屬性的源文件中的字符編碼。
JSTL 定義了一個(gè)<c:import> 操作,該操作允許包含由 URL 指定到 JSP 生成的頁面的外部數(shù)據(jù)。 該操作允許字符編碼規(guī)范,如果外部數(shù)據(jù)沒有指定它本身的編碼時(shí)會使用此規(guī)范。
處理 Web 頁面編碼
web 應(yīng)用程序必須選擇生成的 web 頁中使用的字符編碼(該編碼被稱為“反應(yīng)字符編碼”),它基于目標(biāo)瀏覽器的性能、頁面內(nèi)容的編寫系統(tǒng)和語言以及可能的瀏覽器主機(jī)的操作系統(tǒng)。 根據(jù) HTTP 規(guī)范,字符編碼在 Content-Type 實(shí)體報(bào)頭的 charset 參數(shù)中被指定。
如果所有目標(biāo)瀏覽器都支持 UTF-8,一般來說最好使用這種編碼,這樣就可以支持多語言文檔并避免字符轉(zhuǎn)換帶來的信息丟失。
如果不能使用 UTF-8 ,必須小心謹(jǐn)慎地使用應(yīng)用程序?qū)⒆址幋a與使用的語言相匹配,包括一些特殊字符。 為防止出現(xiàn)錯(cuò)誤,可能需要在整個(gè)頁面里使用同一種語言,如本文開始部分“地區(qū)確定和本地化”中所述。 同樣,也有必要避免使用“€”字符。
Web 應(yīng)用程序可以直接指定一個(gè)頁面的字符編碼,也可以讓 JSP 技術(shù)根據(jù)地區(qū)設(shè)置信息間接決定。
- 通過頁面的
contentType 屬性的顯式規(guī)范最為簡便,該屬性可讓應(yīng)用程序連同生成頁面的內(nèi)容類型一起來指定字符編碼。 如果應(yīng)用程序在處理請求時(shí)需要設(shè)置字符編碼,則它需要使用一個(gè)自定義操作或一些 Java 代碼來調(diào)用 javax.servlet.ServletResponse.setContentType 方法或新的(在 Servlet 2.4 中)javax.servlet.ServletResponse.setCharacterEncoding 方法。
- 間接地,每當(dāng)字符編碼創(chuàng)建一個(gè)本地化環(huán)境時(shí),它們也是由 JSTL 格式化操作(包括
<fmt:message>)以及 <fmt:bundle>、<fmt:setBundle> 和 <fmt:setLocale> 操作無條件地來決定。 通過 ServletResponse.setLocale 方法,它們將本地化環(huán)境的地區(qū)設(shè)置或指定的地區(qū)設(shè)置映射到一個(gè)字符編碼并根據(jù)頁面的內(nèi)容類型對其進(jìn)行設(shè)置。 Servlet 2.4 規(guī)范通過配置描述符中的 locale-encoding-mapping-list 元素為應(yīng)用程序提供了一個(gè)控制映射的方法。 如果應(yīng)用程序沒有提供此元素,或者當(dāng)您使用基于舊的 Servlet 規(guī)范的容器時(shí),那么從地區(qū)設(shè)置到字符編碼的映射取決于該容器;典型的實(shí)現(xiàn)依賴于舊的字符編碼。
間接決定字符編碼是可行的,只要舊的字符編碼可以被接受,并且整個(gè)頁面使用相同的語言而且避免出現(xiàn)常用字符編碼所不支持的特殊字符。 然而,若要利用 UTF-8 則要求使用顯式規(guī)范。 因?yàn)?Servlet 2.4 規(guī)范使顯式規(guī)范優(yōu)先于隱式規(guī)范,所以將字符編碼設(shè)置為 contentType 屬性的一部分已足夠——隨后使用 JSTL 格式化操作不會影響字符的編碼。 不過,在早期版本的 Servlet 規(guī)范中,并不保證地區(qū)設(shè)置信息中的顯式規(guī)范優(yōu)先于隱式?jīng)Q定。 如果需要與基于舊規(guī)范的容器兼容,您必須通過在顯式字符編碼規(guī)范和首次使用自定義操作之間調(diào)用 ServletResponse.flushBuffer 來凍結(jié)字符編碼,這些自定義操作可能間接決定字符編碼。
處理請求參數(shù)編碼
JSP 技術(shù)不僅能夠生成 web 頁面,而且還可以接收和解釋與 HTTP 請求一起收到的參數(shù)——通常是來自某種表格的輸入,這種表格屬于前面生成的 web 頁面的一部分。 用于這些參數(shù)的字符編碼并非在任何地方都被指定,但實(shí)際標(biāo)準(zhǔn)是瀏覽器使用的編碼要與包含這些表格的網(wǎng)頁使用的編碼相同。
這意味著 web 應(yīng)用程序需要跟蹤先前生成的網(wǎng)頁的編碼。 一個(gè)常用的機(jī)制是把編碼的名稱存儲到表格本身的一個(gè)隱藏域中,在下一個(gè)請求時(shí)解壓縮為第一個(gè)參數(shù),然后用它來解碼出其他的參數(shù)。 然而,JSP 頁面還可以使用會話管理來跟蹤請求之間的信息。
應(yīng)用程序可以使用 JSTL 自定義操作 <fmt:requestEncoding> 來指定要編碼的參數(shù)的編碼方法。 如果應(yīng)用程序總是發(fā)送 UTF-8 編碼的頁面,那么可以簡單指定這種編碼為請求編碼。 否則,如果它將直接指定生成頁面的編碼,它應(yīng)該將該編碼作為會話信息的一部分進(jìn)行跟蹤并直接將其傳遞給 <fmt:requestEncoding> 操作。 如果它依賴于字符編碼的間接決定,則它會簡單地使用 <fmt:requestEncoding> 操作而無需指定一種字符編碼;間接決定生成頁面的編碼這一操作還會在會話中儲存信息,<fmt:requestEncoding> 可以檢索和使用這些信息。
格式化和解析
以本地化的格式表示數(shù)據(jù)(如數(shù)字和日期)是任何類型地應(yīng)用程序都要完成的常見任務(wù),就如同用戶提供的輸入解釋。 不同語言和文化所使用的格式區(qū)別很大,所以如果開發(fā)者不依靠現(xiàn)有的庫的話,那么這個(gè)工作就不會是一項(xiàng)簡單的任務(wù)。
幸運(yùn)的是,確實(shí)存在這樣的庫。 Java 2 Standard Edition (J2SE) 平臺提供了在 java.text 軟件包中用于格式化和解析常用數(shù)據(jù)類型的類庫,并且 Sun 已將這些類庫本地化為 100 多種地區(qū)設(shè)置。
JavaServer Pages 標(biāo)準(zhǔn)標(biāo)記庫提供了自定義操作,可將這些功能直接應(yīng)用到 JSP 頁面中。
用于格式化和解析操作的地區(qū)確定
您可以在預(yù)定義的本地化環(huán)境中對數(shù)字和日期使用格式化和解析的操作(例如,如果標(biāo)記嵌套于一個(gè) <fmt:bundle> 標(biāo)記中),或者在這種環(huán)境以外進(jìn)行操作。 如果您在預(yù)定義的本地化環(huán)境中使用操作,則它們將使用此本地化環(huán)境的地區(qū)設(shè)置。 否則,它們將決定 <fmt:bundle> 和 <fmt:setBundle> 操作(如前文所述)的,用于修改的資源束查詢策略所使用的地區(qū)設(shè)置。 主要的區(qū)別在于:與查找資源束不同,算法通過使用 java.text.NumberFormat.getAvailableLocales 方法(用于數(shù)字格式化和解析操作)或者 java.text.DateFormat.getAvailableLocales 方法(用于日期和時(shí)間格式化和分解操作)來決定受支持的地區(qū)設(shè)置。
數(shù)字格式化和解析
JSTL 用于數(shù)字格式化和解析的自定義操作 <fmt:formatNumber> 和 <fmt:parseNumber> 基于 J2SE 類 java.text.NumberFormat,用于處理簡單的數(shù)字以及百分比和貨幣值。
特別值得一提的是它們對貨幣格式化的支持。 傳統(tǒng)上,許多格式化庫假設(shè)貨幣符號可以從地區(qū)設(shè)置中得出——例如,如果地區(qū)設(shè)置是中國,那么貨幣符號就是人民幣(RMB)。 在一個(gè)跨境交易的環(huán)境中,這并沒有多大的意義。 如果某個(gè)英國公司以英鎊來計(jì)算價(jià)格,而 web 應(yīng)用程序?qū)r(jià)格顯示為人民幣(RMB)的形式,就會出現(xiàn)兩個(gè)問題:第一,人民幣的匯率比英鎊低;其次,人民幣換回英鎊會比較困難。 由于貨幣的選擇屬于商業(yè)上的決定,所以貨幣必須作為值的一部分而不是格式的一部分。
因此,<fmt:formatNumber> 操作可以讓應(yīng)用程序指定一個(gè) ISO 4217 貨幣代碼或貨幣符號,它將覆蓋本地化數(shù)字格式使用的默認(rèn)貨幣。 假設(shè)應(yīng)用程序使用一個(gè)帶有值和貨幣屬性的價(jià)格 bean,下面的頁面語句片斷可以對價(jià)格進(jìn)行格式化:
<fmt:formatNumber type="currency" value="${price.value}"
currencyCode="${price.currency}" />
如果 JSP 頁面指定了一個(gè)貨幣代碼,則底層的 NumberFormat 對象會嘗試對指定的貨幣使用一個(gè)貨幣符號,該指定的貨幣已本地化為所處本地化環(huán)境的地區(qū)設(shè)置。 例如,如果為美元指定貨幣代碼 USD,則它可能會使用符號“$”(如果地區(qū)設(shè)置是en_US),在其他可接受該貨幣符號的地區(qū)設(shè)置中將使用 US$,或者如果本地化符號未知,則使用備用貨幣代碼 USD。
日期和時(shí)間的格式化和解析
用于日期和時(shí)間的格式化和解析的 JSTL 自定義操作 <fmt:formatDate> 和 <fmt:parseDate> 基于 J2SE 類 java.text.DateFormat 并用于處理不同的日期和時(shí)間表示方法。
令人感興趣的一點(diǎn)是顯示的日期和時(shí)間不僅僅取決于一種指定地區(qū)設(shè)置的格式,還取決于時(shí)區(qū)信息。 用戶通常對服務(wù)器時(shí)區(qū)不感興趣,但另一方面,要找出用戶所在地的時(shí)區(qū)卻并不簡單。應(yīng)用程序可以通過使用一些客戶端的 JavaScript 代碼來找出用戶的當(dāng)前時(shí)區(qū)與格林尼治標(biāo)準(zhǔn)時(shí)間的偏差,或讓用戶指定當(dāng)前時(shí)區(qū)并將其作為用戶信息的一部分。 JSTL 操作并沒有解決這個(gè)問題,但它們提供了兩個(gè)自定義操作,可以用來告知有關(guān)時(shí)區(qū)的日期和時(shí)間的格式化和解析:<fmt:timeZone> 和 <fmt:setTimeZone>。 與 <fmt:bundle> 和 <fmt:setBundle> 一樣,<fmt:timeZone> 標(biāo)記可為嵌套標(biāo)記定義時(shí)區(qū),而 <fmt:setTimeZone> 將時(shí)區(qū)儲存在一個(gè)變量中以供后繼操作使用。
信息格式化
<fmt:message> 操作(前面已提及)不但能從一個(gè)資源束中獲取一個(gè)字符串并將其插入至生成的頁面中,而且它還可以執(zhí)行參數(shù)替換并根據(jù)需要格式化參數(shù)。 它基于 java.text.MessageFormat 類,因此該操作獲取的字符串實(shí)際上是一個(gè) MessageFormat 模式字符串。 <fmt:param> 操作提供了必要的自變量。
例如,如果 JSP 頁面包含了以下語句:
<jsp:useBean id="now" class="java.util.Date" />
<fmt:bundle basename="Messages">
<fmt:message key="greeting">
<fmt:param value="${now}" />
</fmt:message>
</fmt:bundle>
并且找到的資源束是德語,而且 為 greeting 鍵提供了“Willkommen! Heute ist der {0,date,long}.”值,那么生成日期為(假設(shè))2002 年 6 月 21 日的頁面內(nèi)容將會是“Willkommen! Heute ist der 21. Juni 2002.”
結(jié)論
如本文所述,JavaServer Pages 技術(shù)(特別是 JavaServer Pages 標(biāo)準(zhǔn)標(biāo)記庫)為您提供了一個(gè)開發(fā)多語言應(yīng)用程序的堅(jiān)實(shí)基礎(chǔ)。 您需要仔細(xì)考慮以下幾個(gè)設(shè)計(jì)選擇:如何確定用戶的語言和地區(qū)設(shè)置首選項(xiàng),如何構(gòu)造您用于本地化的 JSP 頁面,是否采用單一語言的頁面或充分利用現(xiàn)有的地區(qū)設(shè)置支持,以及使用哪一種字符編碼模塊。 JSP 技術(shù)使您能夠選擇其中任意一種,這樣您就可以將頁面以最佳的方式展示給全世界的讀者,而且最重要的是,以他們自己的語言來展示。
參考書目
R. Fielding et al.: Hypertext Transfer Protocol -- HTTP/1.1. RFC 2616. The Internet Society, 1999.
Dave Raggett et al. (ed.): HTML 4.01 Specification. World Wide Web Consortium, 1999.
Tim Bray et al. (ed.): Extensible Markup Language (XML) 1.0 (Second Edition). World Wide Web Consortium, 2000.
Java 2 Platform, Standard Edition, v 1.4.2 API Specification.Sun Microsystems, 2002.
Danny Coward (ed.): Java Servlet Specification. Version 2.3.Sun Microsystems, 2001.
Danny Coward, Yutaka Yoshida (ed.): Java Servlet Specification. Version 2.4.Sun Microsystems, 2003.
Eduardo Pelegrlopart (ed.): JavaServer Pages Specification. Version 1.2.Sun Microsystems, 2001.
Mark Roth, Eduardo Pelegr lopart (ed.): JavaServer Pages Specification. Version 2.0.Sun Microsystems, 2003.
Pierre Delisle (ed.): JavaServer Pages Standard Tag Library. Version 1.0.Sun Microsystems, 2002.
Pierre Delisle (ed.): JavaServer Pages Standard Tag Library. Version 1.1.Sun Microsystems, 2003.
關(guān)于作者
Norbert Lindenberg 是 Sun Microsystems 的 Java Web Services 團(tuán)隊(duì)內(nèi) Java Internationalization 技術(shù)主管。在加盟 Sun 之前,曾經(jīng)供職于 General Magic 和 Apple Computer,參與過多個(gè)國際化項(xiàng)目。他畢業(yè)于德國的卡爾斯魯厄大學(xué),擁有計(jì)算機(jī)科學(xué)理科碩士學(xué)位。
|