資源包
在編寫應(yīng)用程序的時(shí)候,需要面對的一個(gè)問題是如何來處理與locale相關(guān)的一些信息。比如,頁面上的一些靜態(tài)文本就希望能夠以用戶習(xí)慣的語言顯示。最原始的做法是將這些信息硬編碼到程序中(可能是一大串判斷語句),但是這樣就將程序代碼和易變的locale信息捆綁在一起,以后如果需要修改locale信息或者添加其它的locale信息,你就不得不重新修改代碼。而資源包可以幫助你解決這個(gè)問題,它通過將可變的locale信息放入資源包中來達(dá)到兩者分離的目的。應(yīng)用程序可以自動(dòng)地通過當(dāng)前的locale設(shè)置到相應(yīng)的資源包中取得所要的信息。資源包的概念類似于Windows編程人員使用的資源文件(rc文件)。
一般來說,資源包需要完成兩個(gè)功能:和具體的locale進(jìn)行綁定以及讀取locale相關(guān)信息。
ResourceBundle類
你可以把資源包看作為一個(gè)由許多成員(子類)組成的大家庭,其中每個(gè)成員關(guān)聯(lián)到不同的locale對象,那它是如何完成關(guān)聯(lián)功能的呢?
資源包中的每個(gè)成員共享一個(gè)被稱作基名(base name)的名稱,然后在此基礎(chǔ)上根據(jù)一定的命名規(guī)范進(jìn)行擴(kuò)展。下面就列出了一些成員的名稱:
LabelResources
LabelResources_de
LabelResources_de_CH
LabelResources_de_CH_UNIX
可見這些子類依據(jù)這樣的命名規(guī)范:baseName_language_country_variant,其中l(wèi)anguage等幾個(gè)變量就是你在構(gòu)造Locale類時(shí)所使用的。而資源包正是通過這個(gè)符合命名規(guī)范的名稱來和locale進(jìn)行關(guān)聯(lián)的,比如LabelResource_de_CH就對應(yīng)于由德語(de)和瑞士(CH)組成的locale對象。
當(dāng)你的應(yīng)用程序需要查找特定locale對象關(guān)聯(lián)的資源包時(shí),它可以調(diào)用ResourceBundle的getBundle方法,并將locale對象作為參數(shù)傳入。
- Locale currentLocale = new Locale("de", "CH", "UNIX");
- ResourceBundle myResources =
- ResourceBundle.getBundle("LabelResources", currentLocale);
如果該locale對象匹配的資源包子類找不到,getBundle將試著查找最匹配的一個(gè)子類。具體的查找策略是這樣的:getBundle使用基名,locale對象和缺省的locale來生成一個(gè)候選資源包名稱序列。如果特定locale對象的語言代碼、國家代碼和可選變量都是空值,則基名是唯一的候選資源包名稱。否則的話,具體locale對象(language1,country1和variant1)和缺省locale(language2,country2和variant2)將產(chǎn)生如下的序列:
- baseName + "_" + language1 + "_" + country1 + "_" + variant1
- baseName + "_" + language1 + "_" + country1
- baseName + "_" + language1
- baseName + "_" + language2 + "_" + country2 + "_" + variant2
- baseName + "_" + language2 + "_" + country2
- baseName + "_" + language2
- baseName
然后,getBundle方法按照產(chǎn)生的序列依次查找匹配的資源包子類并對結(jié)果子類初始化。首先,它將尋找類名匹配候選資源包名稱的類,如果找到將創(chuàng)建該類的一個(gè)實(shí)例,我們稱之為結(jié)果資源包。否則,getBundle方法將尋找對應(yīng)的資源文件,它通過候選資源包名稱來獲得資源文件的完整路徑(將其中的“.”替換為“/”,并加上“.properties”后綴),如果找到匹配文件,getBundle方法將利用該資源文件來創(chuàng)建一個(gè)PropertyResourceBundle實(shí)例,也就是最終的結(jié)果資源包。與此同時(shí),getBundle方法會(huì)將這些資源包實(shí)例緩存起來供以后使用。
如果沒有找到結(jié)果資源包,該方法將拋出MissingResourceException異常。所以為了防止異常的拋出,一般來說都需要至少實(shí)現(xiàn)一個(gè)基名資源包子類。
注意:基名參數(shù)必須是一個(gè)完整的類名稱(比如LabelResources,resource.LabelResources等),就相當(dāng)于你引用一個(gè)類時(shí)需要指定完整的類路徑。但是,為了和以前的版本保持兼容,在使用PropertyResourceBundles時(shí)也允許使用“/”來代替“.”表示路徑。
比如你有以下這些資源類和資源文件:MyResources.class, MyResources_fr_CH.properties, MyResources_fr_CH.class, MyResources_fr.properties, MyResources_en.properties, MyResources_es_ES.class。你利用以下的locale設(shè)置來調(diào)用getBundle方法,你將會(huì)得到不同的結(jié)果資源包(假設(shè)缺省locale為Locale(“en”, “UK”)),請參考表13.4。
表13.4 locale設(shè)置與結(jié)果資源包
locale設(shè)置 結(jié)果資源包
Locale("fr", "CH") MyResources_fr_CH.class
Locale("fr", "FR") MyResources_fr.properties
Locale("de", "DE") MyResources_en.properties
Locale("en", "US") MyResources_en.properties
Locale("es", "ES") MyResources_es_ES.class
創(chuàng)建了具體的資源包子類實(shí)例以后,就需要獲得具體的信息。信息在資源包中是以鍵值對的方式存儲的,表13.5列出的是LabelResources.properties文件的內(nèi)容。
表13.5 LabelResources.properties
- # This is LabelResources.properties file
- greetings = 您好!
- farewell = 再見。
- inquiry = 您好嗎?
其中等號左邊的字符串表示主鍵,它們是唯一的。為了獲得主鍵對應(yīng)的值,你可以調(diào)用ResourceBundle類的getString方法,并將主鍵作為參數(shù)。此外,文件中以“#”號開頭的行表示注釋行。
ListResourceBundle和PropertyResourceBundle子類
抽象類ResourceBundle具有兩個(gè)子類:ListResourceBundle和PropertyResourceBundle,它們表示資源包子類兩種不同的實(shí)現(xiàn)方式。
PropertyResourceBundle是和資源文件配對使用的,一個(gè)屬性文件就是一個(gè)普通的文本文件,你只需要為不同的locale設(shè)置編寫不同名稱的資源文件。但是,在資源文件中只能包含字符串,如果需要存儲其它類型對象,你可以使用ListResourceBundle。
ListResourceBundle是將鍵值對信息保存在類中的列表中,而且你必須實(shí)現(xiàn)ListResourceBundle的具體子類。
如果ListResourceBundle和PropertyResourceBundle不能夠滿足你的需要,你可以實(shí)現(xiàn)自己的ResourceBundle子類,你的子類必須覆蓋兩個(gè)方法:handleGetObject和getKeys。
使用資源文件
使用資源包最簡單的方法就是利用資源文件,利用資源文件一般需要以下幾個(gè)步驟:
1、創(chuàng)建一個(gè)缺省的資源文件
為了防止找不到資源文件,你最好實(shí)現(xiàn)一個(gè)缺省的資源文件,該文件的名稱為資源包的基名加上.properties后綴。
2、創(chuàng)建所需的資源文件
為你準(zhǔn)備支持的locale設(shè)置編寫對應(yīng)的資源文件。
3、設(shè)置locale
你必須在程序中的某個(gè)地方提供locale的設(shè)置或者切換功能,或者將其放入配置文件中。
4、根據(jù)locale設(shè)置創(chuàng)建資源包
ResourceBundle resource =
ResourceBundle.getBundle("LabelBundle",currentLocale);
5、通過資源包獲取locale相關(guān)信息
String value = resource.getString("welcome");
注意:在使用基名的時(shí)候,特別要注意給出完整的類名(或者路徑名),比如你的應(yīng)用程序所在的類包為org.javaresearch.j2seimproved.i18n,而你的資源文件在你的應(yīng)用程序下的resource子目錄中,那你的基名就應(yīng)該是org.javaresearch.j2seimproved.i18n.resource.LabelBundleBundle而不是resource.LabelBundleBundle。
使用ListResourceBundle
使用ListResourceBundle和使用資源文件的步驟基本上一樣,只不過你需要用ListResourceBundle子類來替換相應(yīng)的資源文件。比如你的應(yīng)用程序的基名是LabelBundle,而且準(zhǔn)備支持Locale("en","US")和Locale("zh","CN"),那你需要提供以下幾個(gè)Java文件,注意類名和locale的對應(yīng)關(guān)系。
LabelBundle_en_US.java
LabelBundle_zh_CN.java
LabelBundle.java(缺省類)
代碼13.3列出的是LabelBundle_zh_CN.java的源代碼,相對于資源文件中“key = value”的寫法,在此文件中你首先利用鍵值對來初始化一個(gè)二維數(shù)組,并在getContents方法中返回該數(shù)組。
代碼13.3:LabelBundle_zh_CN.java
- package org.javaresearch.j2seimproved.i18n;import
- java.util.java/util/ListResourceBundle.java.html" target="_blank">ListResourceBundle;
- public class LabelBundle_zh_CN extends ListResourceBundle {
- public java/lang/Object.java.html" target="_blank">Object[][] getContents() {
- return contents;
- }
- private java/lang/Object.java.html" target="_blank">Object[][] contents = {
- {"title", "稱謂"},
- {"surname", "姓"},
- {"firstname", "名"},
- };
- }
創(chuàng)建完資源類以后,同樣需要設(shè)置locale以及根據(jù)locale來創(chuàng)建資源包。在通過資源包獲取具體值的時(shí)候,你不能再使用getString方法,而應(yīng)該調(diào)用getObject方法,而且由于getObject方法返回一個(gè)Object對象,你還需要進(jìn)行正確的類型轉(zhuǎn)換。其實(shí),為了你的程序通用性,我們建議在使用資源文件的時(shí)候你也應(yīng)該調(diào)用getObject方法,而不是getString方法。
關(guān)于ListResourceBundle的詳細(xì)使用,可以參考本書所附代碼中國際化一節(jié)的ListResourceBundleSample.java程序。
MessageFormat類
上面我們講到利用資源文件來分離代碼和可變的信息。但是在實(shí)際過程中,有些信息并不能夠完全事先定義好,其中可能會(huì)用到運(yùn)行時(shí)的一些結(jié)果,最典型例子的就是錯(cuò)誤提示代碼,比如提示某個(gè)輸入必須在一定范圍內(nèi)。利用上面所講的資源文件并不能夠很好地解決這個(gè)問題,所以Java中引入了MessageFormat類。
MessageFormat提供一種語言無關(guān)的方式來組裝消息,它允許你在運(yùn)行時(shí)刻用指定的參數(shù)來替換掉消息字符串中的一部分。你可以為MessageFormat定義一個(gè)模式,在其中你可以用占位符來表示變化的部分,比如你有這樣一句話:
您好,peachpi!歡迎來到Java研究組織網(wǎng)站!當(dāng)前時(shí)間是:2003-8-1 16:43:12。
其中斜體帶下劃線的部分為可變化的,你需要根據(jù)當(dāng)前時(shí)間和不同的登錄用戶來決定最終的顯示。我們用占位符來表示這些變化的部分,可以得到下面這個(gè)模式:
您好,{0}!歡迎來到Java研究組織網(wǎng)站!當(dāng)前時(shí)間是:{1,date} {1,time}。
占位符的格式為{ ArgumentIndex , FormatType , FormatStyle },詳細(xì)說明可以參考MessageFormat的API說明文檔。這里我們定義了兩個(gè)占位符,其中的數(shù)字對應(yīng)于傳入的參數(shù)數(shù)組中的索引,{0}占位符被第一個(gè)參數(shù)替換,{1}占位符被第二個(gè)參數(shù)替換,依此類推。
最多可以設(shè)置10個(gè)占位符,而且每個(gè)占位符可以重復(fù)出現(xiàn)多次,而且格式可以不同,比如{1,date}和{1,time}。而通過將這些模式定義放到不同的資源文件中,就能夠根據(jù)不同的locale設(shè)置,得到不同的模式定義,并用參數(shù)動(dòng)態(tài)替換占位符。
下面我們就以MessageFormatSample.java程序(源文件見本書所附代碼)為例,來詳細(xì)說明其中的每個(gè)步驟。
1、找出可變的部分,并據(jù)此定義模式,將模式放入不同的資源文件中。
比如針對上面的模式,定義了下面兩個(gè)資源文件:
MessagesBundle_en_US.properties
Welcome = Hi, {0}! Welcome to Java Research Organization!
MessagesBundle_zh_CN.properties
Welcome = 您好,{0}!歡迎來到Java研究組織網(wǎng)站!
2、創(chuàng)建MessageFormat對象,并設(shè)置其locale屬性。
- MessageFormat formatter = new MessageFormat("");
- formatter.setLocale(currentLocale);
3、從資源包中得到模式定義,以及設(shè)置參數(shù)。
4、利用模式定義和參數(shù)進(jìn)行格式化。
關(guān)于資源包的組織
一般來說,你是按照資源的用途來組織資源包的,比如會(huì)把所有的頁面按鈕的信息放入一個(gè)名為ButtonResources的資源包中。在實(shí)際的應(yīng)用過程中,以下幾個(gè)原則可以幫你決定如何組織資源包:
1、要易于維護(hù)。
2、最好不要將所有的信息都放入一個(gè)資源包中,因?yàn)檫@樣資源包載入內(nèi)存時(shí)將會(huì)很耗時(shí)。
3、最好將一個(gè)大的資源包分為幾個(gè)小的資源包,這樣可以在使用的時(shí)候才導(dǎo)入必須的資源,減少內(nèi)存消耗。