程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 更多編程語言 >> Rational >> 使用IBM Rational Application Developer輕松實現JavaServer Faces Web程序的

使用IBM Rational Application Developer輕松實現JavaServer Faces Web程序的

編輯:Rational

使用IBM Rational Application Developer輕松實現JavaServer Faces Web程序的全球化

在 RAD V7 或者後續版本中使用 JavaServer Widgets Library(JWL)

了解如何使用 IBM Rational Application Developer 來實現 JavaServer Faces Web 程序的全球化。本文描述了開發全球市場所面臨的挑戰,並介紹了怎樣使用 JavaServer Faces Widget Library(JWL)來處理這個問題。

從版本 7 開始,IBM®Rational®Application Developer 包含了 JavaServer Faces Widget Library(JWL),它是一個 Java™Server Faces (JSF)- 以及用於快速開發網絡程序的基於 JavaScript 的庫。

JWL,hxclient 的 JavaScript 庫,實施了對 JWL 構件的客戶端支持。它還包含了一系列所謂的“JSF 轉化器”,可以幫助開發員分析和格式化日期,時間以及特定位置模式的來回號碼,更特別的是,這些工具就是 JavaSimpleDateFormat 和 DecimalFormat 的 JavaScript 實施。這些工具對於設計成支持多種語言的程序來說十分有用,因為它們幫助您處理來自客戶端位置敏感數據輸入和輸出的挑戰。

本篇文章還解釋了與 JavaServer Faces 程序中多線程相關的全球化挑戰問題,並提供了一個解決方案。本文作者假設您有關於 JSF 和 JWL 的基礎知識。

全球化基礎知識

在網絡程序中,輸出語言是由 HTTP 請求報頭的 Accept-Language 區域所決定。用戶可以指定喜好的語言和帶有浏覽器設置的場所。

JSF 框架分析 HTTP 請求報頭。您可以通過使用如列表 1 所示的報頭來獲取該值。

列表 1. 獲取關於語言和場所的請求

Locale locale = FacesContext.getCurrentInstance().getExternalContext().getRequestLocale();

場所值用於決定用於顯示的預言。

使用 JWL 來處理場所敏感輸出與輸入

在快速引入全球化之後,現在我們已經做好准備,討論全球化 JSF 網絡程序中面臨的兩個挑戰:

使用本地格式顯示客戶日期和時間

顯示和結束本地數字格式

hxclient 在頁面中是怎樣初始化的

只要您在使用頁面中的 JWL 標簽,您就必須確保頁面中安裝有庫,並得到了合適的初始化。如果您設置了浏覽器場所請求為“ja”(日語),並查看使用 JWL 調用的 HTML 頁面的源代碼,您將會發現如列表 2 所示的代碼。

列表 2. 使用 JWL 的網絡頁面的結構

<script type="text/JavaScript" language="JavaScript"
src="/sample/.ibmjsfres/hxclient_core_v3_0_8.js"></script>

<script type="text/JavaScript" language="JavaScript"
src="/sample/.ibmjsfres/hxclient_S_v3_0_8_ja.js?viewLocale=ja">
</script>

<script type="text/JavaScript" language="JavaScript">
   if (hX_5) hX_5.setResourceServer("/sample/.ibmjsfres");
   if(hX_5 && hX_5.setLocale) hX_5.setLocale("ja");
</script>

該代碼可以完成三件事:

包含頁面上的 hxclient 內核腳本庫

包含頁面上的 hxclient 場所特定的腳本庫

創建當前的頁面場所

正如您所看到的那樣,您不需要手動創建場所,因為 JWL 會通過閱讀場所請求來自動決定場所。對於 hxclient 的自動初始化,您已經做好准備將其用於日期,時間和數字格式了。

使用 JWL 的本地格式來顯示客戶日期和時間

對於網絡程序,開發員想要顯示頁面上的最新請求時間。在全球化的程序中,時間必須是當地格式的。

例如,一個美國的用戶可能會想要按以下方式查看日期時間格式 :

Last Refresh: Friday, May 8, 2009 1:35:07 PM GMT+08:00

但是一個日本的用戶也許會看到如下所示的日期時間格式:

前回の最新表示: 2009 年 5 月 8 日金曜日 13 時 41 分 07 秒 GMT+08:00

因為時間是在客戶端計算的,所以 JavaScript 並沒有通過內置 API 來提供一個方案:Date.toLocaleString(). 這是因為:

返回值的場所並不是由浏覽器的場所設置所決定的,而是由客戶操作系統的場所決定。

開發員沒有機會指定日期的格式。

日期時間轉換器是 JWL 客戶腳本庫的一個工具。它是 Java SimpleDateFormat 類的 JavaScript 實施,它可以很好的支持 ICU4J(Unicode Java 庫的國際構件 Library)。它使得客戶端的日期/時間格式變得像處理 Java™一樣容易。在本例中,我們使用 DateTimeConverter 和 ICU4J 來生成客戶端的本地日期/時間。

為了快點開始,讓我們來看客戶端的腳步是什麼樣的:

列表 3. JavaScript 的日期/時間格式

function getLocalizedCurrentTime() 
{ 
  var converter = hX.getConverterById("date_converter");
  if(null == converter) 
  { 
    //construct a new DateTimeConverter and add it to converter set 
    hX.addConverter("date_converter", new hX.DateTimeConverter( 
      "format:EEEE, MMMM d, yyyy h:mm:ss a z", 
      "ICU4J:true"));
  }
  converter = hX.getConverterById("date_converter");
  var date = new Date();
  //format client date and return
  return converter.valueToString(date);
} 

您所要做的只是定位日期/時間。但是這些參數會傳遞給 DateTimeConverter 構建器:

“ICU4J:true” 允許 DateTimeConverter 接受模式的特定 ICU 特征。

“format:EEEE, MMMM d, yyyy h:mm:ss a z” 意味著 DateTimeConverter 用於格式化日期/時間的模式。

到目前為止,我們已經向您展示了,您需要什麼客戶代碼來格式化日期/時間。但這並不足夠。考慮一下模式。您知道對於不同的場所模式會是什麼樣的嗎?

例如,對於美國用戶的模式是這樣的:

EEEE,MMMM d,yyyy h:mm:ss a z

而日本客戶的模式是這樣的:

yyyy'年'M'月'd'日'EEEE H'時'mm'分'ss'秒'z

還好模式並不是硬代碼的,因為開發員並不可能知道不同場所的所有模式。像 “yyyy mm dd”這樣的模式對於某個開發員來說可能是合理的,但是對用戶看來就是很古怪的 。

因此,答案就是從服務器端獲取模式,因為 ICU4J 已經為所有開發員的使用准備好了模式。列表 4 中的代碼展示了,我們怎樣根據本地的請求獲取一個模式:

列表 4. 通過使用 ICU4J 來生成日期/時間模式

public class FormatterUtils
{
   public static String getDateTimePattern(int dateFormat)
   {
     Locale locale =
       FacesContext.getCurrentInstance().getExternalContext().getRequestLocale();
     CalendarData calData = new 
       CalendarData(ULocale.forLocale(locale), null);
     String[] dateTimePatterns = calData.getStringArray("DateTimePatterns");
     return dateTimePatterns[dateFormat + 4] + " " + dateTimePatterns[dateFormat];
   }
}

參數 dateFormat 顯示了格式模式使用的方法。基本上,在 ICU com.ibm.icu.text.DateFormat 類中有四種預定義的類型:

Full

Long

Medium

Short

對於不同格式的參數,方法 getDateTimePattern 的返回值也有所不同。例如,對於 en-us 場所,有四種類型的返回值:

EEEE,MMMM d,yyyy h:mm:ss a z

MMMM d,yyyy h:mm:ss a z

MMM d,yyyy h:mm:ss a

M/d/yy h:mm a

所以通過從四個類型中選擇一個,您已經准備好了格式模式,讓我們假設您使用的是 Full 格式。下一步是將該格式模式應用到客戶端,以使用 hxclient。在 Java™Server Pages(JSP™)腳本中,這很容易做到。對於客戶端的模式,JavaScript 代碼應該像列表 5 所示。

列表 5. 將格式模式捆綁到客戶代碼

<script>
  var datetimeFormatPattern = "<%=FormatterUtils.getDateTimePattern()%>";
 
  function getLocalizedCurrentTime() 
  { 
    var converter = hX.getConverterById("date_converter");
    if(null == converter) 
    { 
      //construct a new DateTimeConverter and add it to converter set 
      hX.addConverter("date_converter", new hX.DateTimeConverter( 
        "format:" + datetimeFormatPattern, "ICU4J:true"));
    }
    converter = hX.getConverterById("date_converter");
    var date = new Date();
    //format client date and return
    return converter.valueToString(date);
  }
</script>

現在已經完成了。通過調用 JavaScriptgetLocalizedCurrentTime 方法,您可以得到客戶端的日期和時間文本(例如,圖 1 顯示是英語,圖 2 顯示的是日語)。

圖 1. 顯示 en-us 的日期和時間

圖 2. 顯示 ja-jp 的日期和時間

顯示和接受本地 JWL 的數字格式

顯示帶有本地格式的數字,與顯示日期/時間相類似。像 1000.1 這樣的十進制數字在美國應該顯示成 1,000.1,而在德國會顯示成 1.000,1。

不像日期和時間,這些數字應該從服務器端獲得,在這裡 ICUDecimalFormat 可以完成這一點。但是有些情況下,您可能想要格式化客戶端的數字(例如,接受用戶輸入並顯示值)。在 JWLhxclient 庫中, NumberConverter 實施了客戶端上 DecimalFormat 的邏輯。

同樣,讓我們首先直接跳到完整的客戶代碼:

列表 6. 格式數字的 JavaScript

<script>
   var decimalFormatPattern = "<%= FormatterUtils.getDecimalFormatPattern() %>";
   var decimalFormatSymbols = "<%= FormatterUtils.getDecimalFormatSymbols() %>";

   function formatDecimal(value)
   {
     var converter = hX.getConverterById("number_converter");
     if(null == converter)
     {
       hX.addConverter("number_converter",
         new hX.NumberConverter("pattern:" + decimalFormatPattern,
         "locale:" + decimalFormatSymbols, "ICU4J:true"));
     }
     converter = hX.getConverterById("number_converter");
     var output = cvt.valueToString(value);
     return output;
   }
</script>

有三種參數會傳遞給 NumberConverter 構建器:

“ICU4J:true”:它允許 NumberConverter 接受在模式中 ICU 特定的特征。

“pattern:”+ decimalFormatPattern: 這是在轉化值的時候會用到的數字模式。

“locale:” + decimalFormatSymbols: 這是轉化值時會用到的場所信息。它包含了十進制分隔符,百分比字符等等之類的符號。

至於 DateTimeConverter,模式和場所信息應該從服務器端獲得:

列表 7. 使用 ICU4J 來生成數字模式和符號

public class FormatterUtils
{
   private static DecimalFormat getDecimalFormatter()
   {
     Locale locale =
       FacesContext.getCurrentInstance().getExternalContext().getRequestLocale();
     return (DecimalFormat)NumberFormat.getInstance(locale);
   }

   public static String getDecimalFormatPattern()
   {
     return getDecimalFormatter().toPattern();
   }

   public static String getDecimalFormatSymbols()
   {
     Locale locale =
       FacesContext.getCurrentInstance().getExternalContext().getRequestLocale();
     DecimalFormatSymbols symbols = new DecimalFormatSymbols(locale);
     //The information is provided as a string of 6 characters with fixed format:
     StringBuilder sb = new StringBuilder();
     sb.append(symbols.getGroupingSeparator());
     sb.append(symbols.getDecimalSeparator());
     sb.append(symbols.getPercent());
     sb.append(symbols.getPerMill());
     sb.append(symbols.getMinusSign());
     sb.append(symbols.getCurrencySymbol());
     return sb.toString();
   }
}

正如您在列表 6 中看到的那樣,getDecimalFormatPattern 和 getDecimalFormatSymbols 用於傳遞頁面中的模式和場所信息。對於服務器端的協助,您可以使用 JavaScriptformatDecimal() 功能,來格式化 JavaScriptNumber 類型變量。列表 8 向您展示了一個這樣的例子。

列表 8. 使用客戶代碼來格式化數字

<script>
   //Suppose current locale is "de"
   var value = 1000.1; //type of value is Number
   var formatted = formatDecimal(value); //the formatted value is "1.000,1" in Germany
</script>

頁面中的數字通常會像圖 3 那樣格式化(該例展示了德語中的匯容量統計):

圖 3. 頁面中的格式化數據:

在處理數字時,不但要注意輸出還要注意輸入。輸入隨著用戶的習慣而不同。一個德國的用戶可能會輸入 1.000,1 或者 1000,1。但是這兩種格式的數據都應該識別為十進制的數據 1000.1。對於開發員來說,這是一個艱難的任務,因為他們需要寫上千行的代碼以識別輸入。

好的消息是 JWLhxclient 可以轉換數字。您可以使用該功能來將用戶輸入轉化為 JavaScript Number 對象。該對象通過自動執行這些步驟,來將顯示的數字和值區別開來:

接受用戶輸入。

通過使用 NumberConverter,來分析 String 對象的輸入到 Number 對象。

使用轉化值以進行計算。

通過再次使用 NumberConverter,來將計算結果格式化回至 String 對象。

使用格式化的值以進行顯示。

列表 9 中的代碼舉了一個例子,展示了怎樣分析用戶輸入(對於 Deutsch 或者 German,場所是“de”):

列表 9. JavaScript 分析的輸入數字

<script>
   var decimalFormatPattern = "<%= FormatterUtils.getDecimalFormatPattern() %>";
   var decimalFormatSymbols = "<%= FormatterUtils.getDecimalFormatSymbols() %>";

   function formatDecimal(input)
   {
     var converter = hX.getConverterById("number_converter");
     if(null == converter)
     {
       hX.addConverter("number_converter",
         new hX.NumberConverter("pattern:" + decimalFormatPattern,
         "locale:" + decimalFormatSymbols, "ICU4J:true"));
     }
     converter = hX.getConverterById("number_converter");
     var output = cvt.stringToValue(input);
     return output;
   }

   var parsedValue = formatDecimal("1.000,1"); //the parsed value is 1000.1
   parsedValue = formatDecimal("1000,1"); //the parsed value is 1000.1
   parsedValue = formatDecimal("oops"); //parsing fails, null is returned
</script>

列表 9 中的代碼在以下方面與列表 6 十分相似:從服務器端獲取模式和場所信息,創建一個 NumberConverter 的范例,然後執行該任務。唯一的區別是調用的方法:stringToValue(). 方法的名字是不言而喻的:它分析一個 String 對象,並試著將其轉化為 Number 對象。如果在轉化期間發生了什麼錯誤,那麼該方法將會返回 null。因此,NumberConverter 也可以用於識別用戶輸入。

到目前為止,我們已經介紹了 JWLhxclient 腳本是怎樣幫助您處理客戶端的數字分析和格式問題。在例子中的代碼中,我們總是需要得到服務器端的場所信息,以生成格式模式。因此,在接下來的章節中,我們將會討論更高級的話題,就是場所信息是怎樣傳遞的,以及在 JSF 網絡程序中是怎樣使用這些信息的。

在多線程 JSF 程序中全球化道路的風險

使用 JSF 進行全球化很容易,但是並不是極簡單的。特別是在多線程的程序中,如果設計缺乏完善的考慮,錯誤的假設將會使得您的全球化支持,變得更像是應用一系列的補丁。接下來我們將要介紹的技術,將會使得您的多線程 JSF 程序變得更加強壯。

決定顯示的語言

全球化通常構建於場所的基礎之上。因此,怎樣實現全球化歸根結底就是怎樣處理場所。在獲取場所信息之後,您已經可以決定基於場所用戶界面中顯示的語言。在大多數情況下,JSF 框架會關注帶有 <loadBundle> 標簽的語言包,它根據場所請求獲取包,而不需要額外的編碼。但是如果您需要使用 Java 代碼中的語言包內容,那麼您就需要使用場所信息來獲取包的路徑,然後自己格式化信息。列表 10 給出了范例代碼。

列表 10. 一個簡單的信息格式化范例

public class MessageFormatter {
private static final String MESSAGE_BUNDLE_NAME = "com.ibm.sample.messages";

private static String formatMessage(String msgKey, Object[] args,Locale locale) {
   ResourceBundle messageBundle = ResourceBundle.getBundle(MESSAGE_BUNDLE_NAME,locale);
   String message = messageBundle.getString(msgKey);
   if (message != null) {
     if (args == null) {
       return message;
     } else {
       return MessageFormat.format(message, args);
     }
   } else {
     return msgKey;
   }
}
}

得到請求的場所

我們可以看到文本文件中列表 10 所示的代碼,它解釋了怎樣使用 Java 方法來實現全球化。但是在實際操作時當您看到大量 getRequestLocale 存在時是很痛苦的,如列表 1 所示,叫做格式化信息之前。

提示:

查看 IBM Java 技術庫以得到關於 輕松使用線程:不共享有時是最好的變量的更多信息。

接下來,我們將會向您展示怎樣讓工作變得更加完美。

您已經知道,JSFFacesContext 是作為服務線程中的一個 ThreadLocal 變量保存的。因為所有的 Faces backend 豆是在服務線程中運行的,所以您可以在任意時刻獲取 FacesContext 對象。對於場所對象 T 也是真實的,因為它是從 FacesContext 獲取的。這樣您就可以將場所獲取方法放在工具類中,來重寫列表 10 中的代碼,如列表 11 所示。

列表 11. 獲取信息格式化器的最佳方法

public class LocaleUtils(){
public class LocaleUtils{} {
public static Locale getRequestLocale() {
   return FacesContext.getCurrentInstance().getExternalContext().getRequestLocale();
}

}
public class MessageFormatter {
private static final String MESSAGE_BUNDLE_NAME = "com.ibm.sample.messages";

private static String formatMessage(String msgKey, Object[] args) {
   Locale locale = LocaleUtils.getRequestLocale();
   ResourceBundle messageBundle = ResourceBundle.getBundle(MESSAGE_BUNDLE_NAME, locale);
   String message = messageBundle.getString(msgKey);
   if (message != null) {
     if (args == null) {
       return message;
     } else {
       return MessageFormat.format(message, args);
     }
   } else {
     return msgKey;
   }
}
}

然後 MessageFormatter.formatMessage 就成為從語言包中獲取信息的唯一方法了,它會返回基於請求的信息。現在您可以忽略場所了,因為在 LocaleUtils 的支持下它現在透明地為您工作了。

將請求場所傳遞給用戶線程

如果您想讓場所自動地工作,那麼現在您做的還不夠。

當您在處理多線程程序時,還有一個例外。在多線程程序中,用戶定義的線程並不是由服務線程初始化的,因此它們並沒有將 FacesContext 初始化為一個 ThreadLocal 變量。結果,在該線程中運行的代碼在格式化信息時,就不能訪問場所信息了。

對於以上問題通用的解決方法,是通過在構造變量期間將其傳遞給用戶線程,來讓每一個用戶都有一個場所變量。當變量在線程中准備好時,您可以使用列表 10 中所示的范例代碼,來在用戶線程中運行代碼以實現全球化。但是,這需要編輯用戶線程構造器及其調用的代碼。有一種方法可以讓它更加完美地工作。

我們知道至少有一個用戶線程(或者是它的上級線程)會在服務線程中實現。換句話說,至少有一個用戶線程的構造器在服務線程中得到訪問。因為用戶線程構造器中的代碼仍然在訪問線程的環境下運行,所以在構造用戶線程時,我們還有機會從服務線程繼承場所對象。這樣我們將可以傳遞實現每一個子線程初始化的場所對象。這樣我們就有了 LocaleUtils 的升級版本,這樣通過實施 LocaleSensitive 界面,在用戶線程成為場所敏感的線程之後,在多線程環境中它就可以更好地工作了,就像我們在 BaseThread 中所做的那樣(見於列表 12)。

列表 12. 場所敏感的基底線程

public static Locale DEFAULT_LOCALE = Locale.ENGLISH;

   public static Locale getCurrentRequestLocale() {
     Locale locale = null;
     try {
       //try to retrieve locale information from FacesContext
       locale = FacesContext.getCurrentInstance(). 
         getExternalContext().getRequestLocale();
     }
     catch(Throwable e){
       //Unable to reach FacesContext
       //Therefore call getLocale to retrieve locale variable from current thread
       Thread t = Thread.currentThread();
       if (t instanceof LocaleSensitive) {
         locale = ((LocaleSensitive) t).getLocale();
     }
     finally{
       if(locale == null){
         locale = DEFAULT_LOCALE;
       }
     }
     return locale;
   }
}

public interface LocaleSensitive {
   public Locale getLocale();
}

public class BaseThread extends Thread implements LocaleSensitive {
   protected Locale locale= null;

   public Locale getLocale() {
     return locale;
   }

   public BaseThread(String name) {
     super(name);
     //Save a copy of locale in thread instance
     this.locale=LocaleUtils.getCurrentRequestLocale();
   }
}

public class UserThread extends BaseThread{
   public void run(){
     … 
     String message = MessageFormatter.formatMessage(msgKey,msgParameters);
     … 
   }
}

首先,您需要定義一個名為 LocaleSensitive 的界面。任何一個實施該節目的類都應該提供 getLocale() 方法中的場所信息。

然後您使用 BaseThread 來實施 LocaleSensitive 線程。BaseThread 構造器通過訪問 LocaleUtils.getCurrentRequestLocale() 來得到場所對象,它從 FacesContext 中(在服務環境下),或者上級線程中得到場所信息(這就解釋了場所信息是怎樣傳遞的)。然後如果有用戶線程成為 BaseThread 的子類,該線程可以使用 MessageFormatter,而不需升級版本的 LocaleUtils 類,就像我們在服務環境下所做的那樣。

使用列表 12 中的代碼,您就可以在任何線程中傳遞和得到場所信息了,而無需擔心 FacesContext 是否可以訪問。現在多線程 JSF 網絡程序中的風險就不復存在了。

總結

IBM Rational Application Developer 中的 JavaServer Widgets 庫,提供了腳本幫助開發員處理客戶端的全球化問題,這使得全球化開發變得更加容易。在您處理多線程的網絡程序時,要十分謹慎,因為您需要掌握一定的技巧,以確保在程序的任何地方都能夠訪問場所信息。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved