MessageSource簡介

文章目錄

 

假設我們正在開發一個支持多國語言的Web應用程序,要求系統能夠根據客戶端的系統的語言類型返回對應的界面:英文的操作系統返回英文界面,而中文的操作系統則返回中文界面——這便是典型的i18n國際化問題。對於有國際化要求的應用系統,我們不能簡單地採用硬編碼的方式編寫用戶界面信息、報錯信息等內容,而必須爲這些需要國際化的信息進行特殊處理。簡單來說,就是爲每種語言提供一套相應的資源文件,並以規範化命名的方式保存在特定的目錄中,由系統自動根據客戶端語言選擇適合的資源文件。

 

基礎知識

“國際化信息”也稱爲“本地化信息”,一般需要兩個條件纔可以確定一個特定類型的本地化信息,它們分別是“語言類型”和“國家/地區的類型”。如中文本地化信息既有中國大陸地區的中文,又有中國臺灣、中國香港地區的中文,還有新加坡地區的中文。Java通過java.util.Locale類表示一個本地化對象,它允許通過語言參數和國家/地區參數創建一個確定的本地化對象。

語言參數使用ISO標準語言代碼表示,這些代碼是由ISO-639標準定義的,每一種語言由兩個小寫字母表示。在許多網站上都可以找到這些代碼的完整列表,下面的網址是提供了標準語言代碼的信息:http://www.loc.gov/standards/iso639-2/php/English_list.php。

國家/地區參數也由標準的ISO國家/地區代碼表示,這些代碼是由ISO-3166標準定義的,每個國家/地區由兩個大寫字母表示。用戶可以從以下網址查看ISO-3166的標準代碼:
http://www.iso.ch/iso/en/prods-services/iso3166ma/02iso-3166-code-lists/list-en1.html。

表5-2給出了一些語言和國家/地區的標準代碼:
一些語言和國家/地區的標準代碼

Locale

java.util.Locale是表示語言和國家/地區信息的本地化類,它是創建國際化應用的基礎。下面給出幾個創建本地化對象的示例:

    //①帶有語言和國家/地區信息的本地化對象  
    Locale locale1 = new Locale("zh","CN");   
      
    //②只有語言信息的本地化對象  
    Locale locale2 = new Locale("zh");   
      
    //③等同於Locale("zh","CN")  
    Locale locale3 = Locale.CHINA;   
      
    //④等同於Locale("zh")  
    Locale locale4 = Locale.CHINESE;   
      
    //⑤獲取本地系統默認的本地化對象  
    Locale locale 5= Locale.getDefault();  

用戶既可以同時指定語言和國家/地區參數定義一個本地化對象①,也可以僅通過語言參數定義一個泛本地化對象②。Locale類中通過靜態常量定義了一些常用的本地化對象,③和④處就直接通過引用常量返回本地化對象。此外,用戶還可以獲取系統默認的本地化對象,如⑤所示。

在測試時,如果希望改變系統默認的本地化設置,可以在啓動JVM時通過命令參數指定:java -Duser.language=en -Duser.region=US MyTest

本地化工具類

JDK的java.util包中提供了幾個支持本地化的格式化操作工具類:NumberFormatDateFormatMessageFormat。下面,我們分別通過實例瞭解它們的用法:

代碼清單5-13 NumberFormat

    Locale locale = new Locale("zh", "CN");  
    NumberFormat currFmt = NumberFormat.getCurrencyInstance(locale);  
    double amt = 123456.78;  
    System.out.println(currFmt.format(amt));   

上面的實例通過NumberFormat按本地化的方式對貨幣金額進行格式化操作,運行實例,輸出以下信息:

¥123,456.78

代碼清單5-14 DateFormat

    Locale locale = new Locale("en", "US");  
    Date date = new Date();  
    DateFormat df = DateFormat.getDateInstance(DateFormat.MEDIUM, locale);  
    System.out.println(df.format(date));   

通過DateFormat#getDateInstance(int style,Locale locale)方法按本地化的方式對日期進行格式化操作。該方法第一個入參爲時間樣式,第二個入參爲本地化對象。運行以上代碼,輸出以下信息:

Jan 8, 2007

MessageFormat在NumberFormat和DateFormat的基礎上提供了強大的佔位符字符串的格式化功能,它支持時間、貨幣、數字以及對象屬性的格式化操作。下面的實例演示了一些常見的格式化功能:

代碼清單5-15 MessageFormat

     //①信息格式化串  
    String pattern1 = "{0},你好!你於{1}在工商銀行存入{2} 元。";  
    String pattern2 = "At {1,time,short} On{1,date,long},{0} paid {2,number, currency}.";  
      
    //②用於動態替換佔位符的參數  
    Object[] params = {"John", new GregorianCalendar().getTime(),1.0E3};  
      
    //③使用默認本地化對象格式化信息  
    String msg1 = MessageFormat.format(pattern1,params);   
      
    //④使用指定的本地化對象格式化信息  
    MessageFormat mf = new MessageFormat(pattern2,Locale.US);   
    String msg2 = mf.format(params);  
    System.out.println(msg1);  
    System.out.println(msg2);  

John,你好!你於07-1-8 下午9:58在工商銀行存入1,000元。
At 9:58 PM OnJanuary 8, 2007,John paid $1,000.00.
ResourceBoundle

如果應用系統中某些信息需要支持國際化功能,則必須爲希望支持的不同本地化類型分別提供對應的資源文件,並以規範的方式進行命名。國際化資源文件的命名規範規定資源名稱採用以下的方式進行命名:

<資源名><語言代碼><國家/地區代碼>.properties

其中,語言代碼和國家/地區代碼都是可選的。<資源名>.properties命名的國際化資源文件是默認的資源文件,即某個本地化類型在系統中找不到對應的資源文件,就採用這個默認的資源文件。<資源名>_<語言代碼>.properties命名的國際化資源文件是某一語言默認的資源文件,即某個本地化類型在系統中找不到精確匹配的資源文件,將採用相應語言默認的資源文件。

舉一個例子:假設資源名爲resource,則語言爲英文,國家爲美國,則與其對應的本地化資源文件命名爲resource_en_US.properties。信息在資源文件以屬性名/值的方式表示:

本地化不同的同一資源文件,雖然屬性值各不相同,但屬性名卻是相同的,這樣應用程序就可以通過Locale對象和屬性名精確調用到某個具體的屬性值了。

讀者可能已經注意到,上面中文的本地化資源文件內容採用了特殊的編碼表示中文字符,這是因爲資源文件對文件內容有嚴格的要求:只能包含ASCII字符。所以必須將非ASCII字符的內容轉換爲Unicode代碼的表示方式。如上面中文的resource_zh_CN.properties資源文件的三個屬性值分別是“您好!”、“早上好!”和“下午好!”三個中文字符串對應的Unicode代碼串。

如果在應用開發時,直接採用Unicode代碼編輯資源文件是很不方便的,所以,通常我們直接使用正常的方式編寫資源文件,在測試或部署時再採用工具進行轉換。JDK在bin目錄下爲我們提供了一個完成此項功能的native2ascii工具,它可以將中文字符的資源文件轉換爲Unicode代碼格式的文件,命令格式如下:

native2ascii [-reverse] [-encoding 編碼] [輸入文件 [輸出文件]]

resource_zh_CN.properties包含中文字符並且以UTF-8進行編碼,假設將該資源文件放到d:\目錄下,通過下面的命令就可以將其轉換爲Unicode代碼的形式:

D:>native2ascii -encoding utf-8 d:\resource_zh_CN.properties
d:\resource_zh_CN_1.properties

由於原資源文件採用UTF-8編碼,所以必須顯式通過-encoding指定編碼格式。

通過native2ascii命令手工轉換資源文件,不但在操作上不方便,轉換後資源文件中的屬性內容由於採用了ASCII編碼,閱讀起來也不方便。很多IDE開發工具都有屬性編輯器的插件,插件會自動將資源文件內容轉換爲ASCII形式的編碼,同時以正常的方式閱讀和編輯資源文件的內容,這給開發和維護帶來了很大的便利。對於MyEclipse來說,使用MyEclipse Properties Editor編輯資源屬性文件;對於Intellij IDEA來說,無須安裝任何插件就自然支持資源屬性文件的這種編輯方式了。

ResourceBoundle

如果應用程序中擁有大量的本地化資源文件,直接通過傳統的File操作資源文件顯然太過笨拙。Java爲我們提供了用於加載本地化資源文件的方便類java.util.ResourceBoundle。

ResourceBoundle爲加載及訪問資源文件提供便捷的操作,下面的語句從相對於類路徑的目錄中加載一個名爲resource的本地化資源文件:

    ResourceBundle rb = ResourceBundle.getBundle("com/baobaotao/i18n/resource", locale)  
  • 通過以下的代碼即可訪問資源文件的屬性值:
    rb.getString("greeting.common")  
  • 來看下面的實例:

代碼清單5-16 ResourceBoundle

    ResourceBundle rb1 = ResourceBundle.getBundle("com/baobaotao/i18n/resource", Locale.US);  
    ResourceBundle rb2 = ResourceBundle.getBundle("com/baobaotao/i18n/resource", Locale.CHINA);  
    System.out.println("us:"+rb1.getString("greeting.common"));  
    System.out.println("cn:"+rb2.getString("greeting.common"));  
  • rb1加載了對應美國英語本地化的resource_en_US.properties資源文件;而rb2加載了對應中國大陸中文的resource_zh_CN.properties資源文件。運行上面的代碼,將輸出以下信息:

us:How are you!
cn:您好!

加載資源文件時,如果不指定本地化對象,將使用本地系統默認的本地化對象。所以,在中文系統中,ResourceBundle.getBundle(“com/baobaotao/i18n/resource”)語句也將返回和代碼清單5-14中rb2相同的本地化資源。

ResourceBundle在加載資源時,如果指定的本地化資源文件不存在,它按以下順序嘗試加載其他的資源:本地系統默認本地化對象對應的資源→默認的資源。上面的例子中,假設我們使用ResourceBundle.getBundle(“com/baobaotao/i18n/resource”,Locale.CANADA)加載資源,由於不存在resource_en_CA.properties資源文件,它將嘗試加載resource_zh_CN.properties的資源文件,假設resource_zh_CN.properties資源文件也不存在,它將繼續嘗試加載resource.properties的資源文件,如果這些資源都不存在,將拋出java.util.MissingResourceException異常。

MessageFormat

在上面的資源文件中,屬性值都是一般的字符串,它們不能結合運行時的動態參數構造出靈活的信息,而這種需求是很常見的。要解決這個問題很簡單,只須使用帶佔位符的格式化串作爲資源文件的屬性值並結合使用MessageFormat就可以滿足要求了。

上面的例子中,我們僅向用戶提供一般性問候,下面我們對資源文件進行改造,通過格式化串讓問候語更具個性化:

greeting.common=How are you!{0},today is {1}
greeting.morning = Good morning!{0},now is {1 time short}
greeting.afternoon = Good Afternoon!{0} now is {1 date long}
  • 將該資源文件保存在fmt_resource_en_US.properties中,按照同樣的方式編寫對應的中文本地化資源文件fmt_resource_zh_CN.properties。

下面,我們聯合使用ResourceBoundle和MessageFormat得到美國英文的本地化問候語:

代碼清單5-17 資源文件格式化串處理

     //①加載本地化資源  
    ResourceBundle rb1 =   
                 ResourceBundle.getBundle("com/baobaotao/i18n/fmt_ resource",Locale.US);   
    ResourceBundle rb2 =   
                  ResourceBundle.getBundle("com/baobaotao/i18n/fmt_ resource",Locale.CHINA);  
    Object[] params = {"John", new GregorianCalendar().getTime()};  
      
      
    String str1 = new MessageFormat(rb1.getString("greeting.common"),Locale. US).format(params); ②  
    String str2 =new MessageFormat(rb2.getString("greeting.morning"),Locale. CHINA).format(params);  
    String str3 =new MessageFormat(rb2.getString("greeting.afternoon"),Locale. CHINA).format(params);  
    System.out.println(str1);  
    System.out.println(str2);  
    System.out.println(str3);  

運行以上的代碼,將輸出以下信息:

How are you!John,today is 1/9/07 4:11 PM
早上好!John,現在是下午4:11
下午好!John,現在是2007年1月9日

MessageSource

簡介

Spring中定義了一個MessageSource接口,以用於支持信息的國際化和包含參數的信息的替換。MessageSource接口的定義如下,對應的方法說明已經在方法上註釋了。

    public interface MessageSource {
        /**
         * 解析code對應的信息進行返回,如果對應的code不能被解析則返回默認信息defaultMessage。
         *
         * @param 需要進行解析的code,對應資源文件中的一個屬性名
         * @param 需要用來替換code對應的信息中包含參數的內容,如:{0},{1,date},{2,time}
         * @param defaultMessage                                  當對應code對應的信息不存在時需要返回的默認值
         * @param locale                                          對應的Locale
         * @return
         */
        String getMessage(String code, Object[] args, String defaultMessage, Locale locale);

        /**
         * 解析code對應的信息進行返回,如果對應的code不能被解析則拋出異常NoSuchMessageException
         *
         * @param code   需要進行解析的code,對應資源文件中的一個屬性名
         * @param args   需要用來替換code對應的信息中包含參數的內容,如:{0},{1,date},{2,time}
         * @param locale 對應的Locale
         * @return
         * @throws NoSuchMessageException 如果對應的code不能被解析則拋出該異常
         */
        String getMessage(String code, Object[] args, Locale locale) throws NoSuchMessageException;

        /**
         * 通過傳遞的MessageSourceResolvable對應來解析對應的信息
         *
         * @param resolvable
         * @param locale     對應的Locale
         * @return
         * @throws NoSuchMessageException 如不能解析則拋出該異常
         */
        String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException;
    }

MessageSource分別被HierarchicalMessageSource和ApplicationContext接口擴展
MessageSource的兩個擴展接口

ApplicationContext

我們熟悉的ApplicationContext接口繼承了MessageSource接口,所以我們所有的ApplicationContext實現類都實現了MessageSource接口,也就是我們我們可以通過ApplicationContext來調用MessageSource接口方法以實現信息的國際化和替換信息中包含的參數。所有ApplicationContext實現類對MessageSource接口的實現都是在AbstractApplicationContext中實現的,其對MessageSource接口實現的源碼如下:

	@Override
	public String getMessage(String code, Object args[], String defaultMessage, Locale locale) {
		return getMessageSource().getMessage(code, args, defaultMessage, locale);
	}

	@Override
	public String getMessage(String code, Object args[], Locale locale) throws NoSuchMessageException {
		return getMessageSource().getMessage(code, args, locale);
	}

	@Override
	public String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException {
		return getMessageSource().getMessage(resolvable, locale);
	}

	/**
	 * Return the internal MessageSource used by the context.
	 * @return the internal MessageSource (never {@code null})
	 * @throws IllegalStateException if the context has not been initialized yet
	 */
	private MessageSource getMessageSource() throws IllegalStateException {
		if (this.messageSource == null) {
			throw new IllegalStateException("MessageSource not initialized - " +
					"call 'refresh' before accessing messages via the context: " + this);
		}
		return this.messageSource;
	}

從中我們可以看到AbstractApplicationContext對MessageSource的實現都是自身所持有的MessageSource類型的messageSource對象來實現的。那麼對應的messageSource又是如何初始化的呢?在其中定義了一個initMessageSource()來做對應的初始化工作,其源碼如下:

	/**
	 * Initialize the MessageSource.
	 * Use parent's if none defined in this context.
	 */
	protected void initMessageSource() {
		ConfigurableListableBeanFactory beanFactory = getBeanFactory();
		if (beanFactory.containsLocalBean(MESSAGE_SOURCE_BEAN_NAME)) {
			this.messageSource = beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME, MessageSource.class);
			// Make MessageSource aware of parent MessageSource.
			if (this.parent != null && this.messageSource instanceof HierarchicalMessageSource) {
				HierarchicalMessageSource hms = (HierarchicalMessageSource) this.messageSource;
				if (hms.getParentMessageSource() == null) {
					// Only set parent context as parent MessageSource if no parent MessageSource
					// registered already.
					hms.setParentMessageSource(getInternalParentMessageSource());
				}
			}
			if (logger.isDebugEnabled()) {
				logger.debug("Using MessageSource [" + this.messageSource + "]");
			}
		}
		else {
			// Use empty MessageSource to be able to accept getMessage calls.
			DelegatingMessageSource dms = new DelegatingMessageSource();
			dms.setParentMessageSource(getInternalParentMessageSource());
			this.messageSource = dms;
			beanFactory.registerSingleton(MESSAGE_SOURCE_BEAN_NAME, this.messageSource);
			if (logger.isDebugEnabled()) {
				logger.debug("Unable to locate MessageSource with name '" + MESSAGE_SOURCE_BEAN_NAME +
						"': using default [" + this.messageSource + "]");
			}
		}
	}

從上述源碼中我們可以看到如果bean容器中存在一個名爲messageSource(MESSAGE_SOURCE_BEAN_NAME常量對應的值爲messageSource)的bean,則取該bean作爲messageSource,如果對應的messageSource是一個HierarchicalMessageSource,則會在父容器存在的情況下取父容器對應的messageSource作爲當前messageSource的parentMessageSource。如果當前bean容器中不存在beanName爲messageSource的bean,則會生成一個DelegatingMessageSource來作爲當前的MessageSource。DelegatingMessageSource基本算是對MessageSource的一個空的實現,在對應父容器的messageSource存在時就使用父容器的messageSource處理,否則就不處理,具體可以參考Spring的API文檔或查看DelegatingMessageSource的源碼。

鑑於ApplicationContext實現類對MessageSource接口實現的這種機制,如果我們需要通過ApplicationContext來獲取國際化信息,那麼我們只需要在對應的ApplicationContext中定義一個MessageSource類型的bean,並且指定對應的beanName爲messageSource即可。Spring中對MessageSource提供了三個實現類,分別是ReloadableResourceBundleMessageSource、StaticMessageSource和ResourceBundleMessageSource。

HierarchicalMessageSource

HierarchicalMessageSource接口的幾個實現類
MessageSource類圖結構

HierarchicalMessageSource接口添加了兩個方法,建立父子層級的MessageSource結構,類似於前面我們所介紹的HierarchicalBeanFactory。該接口的setParentMessageSource (MessageSource parent)方法用於設置父MessageSource,而getParentMessageSource()方法用於返回父MessageSource。

HierarchicalMessageSource接口最重要的兩個實現類是ResourceBundleMessageSource和ReloadableResourceBundleMessageSource。它們基於Java的ResourceBundle基礎類實現,允許僅通過資源名加載國際化資源。
ReloadableResourceBundleMessageSource提供了定時刷新功能,允許在不重啓系統的情況下,更新資源的信息。StaticMessageSource主要用於程序測試,它允許通過編程的方式提供國際化信息。
DelegatingMessageSource是爲方便操作父MessageSource而提供的代理類。

ResourceBundleMessageSource

ResourceBundleMessageSource是基於JDK ResourceBundle的MessageSource接口實現類。它會將訪問過的ResourceBundle緩存起來,以便於下次直接從緩存中獲取進行使用。

該實現類允許用戶通過beanName指定一個資源名(包括類路徑的全限定資源名),或通過beanNames指定一組資源名。在代碼清單5-18中,我們通過JDK的基礎類完成了本地化的操作,下面我們使用ResourceBundleMessageSource來完成相同的任務。讀者可以比較兩者的使用差別,並體會Spring所提供的國際化處理功能所帶給我們的好處:

代碼清單5-18 通過ResourceBundleMessageSource配置資源

    <bean id="myResource"  
    class="org.springframework.context.support.ResourceBundleMessageSource">  
        <!--①通過基名指定資源,相對於類根路徑-->  
        <property name="basenames">    
           <list>  
              <value>com/baobaotao/i18n/fmt_resource</value>  
           </list>  
        </property>  
      </bean>   

啓動Spring容器,並通過MessageSource訪問配置的國際化資源,如代碼清單 5 19所示:

代碼清單5-19 訪問國際化消息:ResourceBundleMessageSource

    String[] configs = {"com/baobaotao/i18n/beans.xml"};  
    ApplicationContext ctx = new ClassPathXmlApplicationContext(configs);  
      
    //①獲取MessageSource的Bean  
    MessageSource ms = (MessageSource)ctx.getBean("myResource");   
    Object[] params = {"John", new GregorianCalendar(). getTime()};  
      
    //②獲取格式化的國際化信息  
    String str1 = ms.getMessage("greeting.common",params,Locale.US);  
    String str2 = ms.getMessage("greeting.morning",params,Locale.CHINA);  
    String str3 = ms.getMessage("greeting.afternoon",params,Locale.CHINA);  
    System.out.println(str1);  
    System.out.println(str2);  
    System.out.println(str3);  

比較代碼清單5-19中的代碼,我們發現最主要的區別在於我們無須再分別加載不同語言、不同國家/地區的本地化資源文件,僅僅通過資源名就可以加載整套的國際化資源文件。此外,我們無須顯式使用MessageFormat操作國際化信息,僅通過MessageSource# getMessage()方法就可以完成操作了。這段代碼的運行結果與代碼清單5 17的運行結果完全一樣。

ReloadableResourceBundleMessageSource

ReloadableResourceBundleMessageSource是以ResourceBundleMessageSource結尾的,但實際上它跟ResourceBundleMessageSource沒有什麼直接的關係。ReloadableResourceBundleMessageSource也是對MessageSource的一種實現,其用法配置等和ResourceBundleMessageSource基本一致。所不同的是ReloadableResourceBundleMessageSource內部是使用PropertiesPersister來加載對應的文件,這包括properties文件和xml文件,然後使用java.util.Properties來保存對應的數據。

另外,ReloadableResourceBundleMessageSource允許我們指定非類路徑下的文件作爲對應的資源文件,而ResourceBundleMessageSource是限制了我們只能將對應的資源文件放置在類路徑下的。在指定basename時,我們還可以使用Spring支持的資源文件的前綴,如classpath等。

代碼清單5-20 通過ReloadableResourceBundleMessageSource配置資源

    <bean id="myResource"   
    lass="org.springframework.context.support. ReloadableResourceBundleMessageSource">  
       <property name="basenames">  
          <list>  
            <value>com/baobaotao/i18n/fmt_resource</value>  
          </list>  
       </property>  
       <!--① 刷新資源文件的週期,以秒爲單位-->  
       <property name="cacheSeconds" value="5"/>
       <property name="fallbackToSystemLocale" value="false"/>
       <property name="defaultEncoding" value="UTF-8"/>
     </bean>  

在上面的配置中,我們通過cacheSeconds屬性讓ReloadableResourceBundleMessageSource每5秒鐘刷新一次資源文件(在真實的應用中,刷新週期不能太短,否則頻繁的刷新將帶來性能上的負面影響,一般不建議小於30分鐘)。cacheSeconds默認值爲-1表示永不刷新,此時,該實現類的功能就蛻化爲ResourceBundleMessageSource的功能。

我們編寫一個測試類對上面配置的ReloadableResourceBundleMessageSource進行測試:

代碼清單5-21 刷新資源:ReloadableResourceBundleMessageSource

    String[] configs = {"com/baobaotao/i18n/beans.xml"};  
    ApplicationContext ctx = new ClassPathXmlApplicationContext(configs);  
      
    MessageSource ms = (MessageSource)ctx.getBean("myResource");  
    Object[] params = {"John", new GregorianCalendar().getTime()};  
      
    for (int i = 0; i < 2; i++) {  
        String str1 = ms.getMessage("greeting.common",params,Locale.US);      
        System.out.println(str1);  
        Thread.currentThread().sleep(20000); //①模擬程序應用,在此期間,我們更改資源文件   
    }  

在①處,我們讓程序睡眠20秒鐘,在這期間,我們將fmt_resource_zh_CN.properties資源文件的greeting.common鍵值調整爲:

—How are you!{0},today is {1}—

我們將看到兩次輸出的格式化信息分別對應更改前後的內容,也即本地化資源文件的調整被自動生效了:

How are you!John,today is 1/9/07 4:55 PM
—How are you!John,today is 1/9/07 4:55 PM—

容器級的國際化信息資源

在如圖5-7所示的MessageSource類圖結構中,我們發現ApplicationContext實現了MessageSource的接口。也就是說ApplicationContext的實現類本身也是一個MessageSource對象。

將ApplicationContext和MessageSource整合起來,乍一看挺讓人費解的,Spring這樣設計的意圖究竟是什麼呢?原來Spring認爲:在一般情況下,國際化信息資源應該是容器級。我們一般不會將MessageSource作爲一個Bean注入到其他的Bean中,相反MessageSource作爲容器的基礎設施向容器中所有的Bean開放。只要我們考察一下國際化信息的實際消費場所就更能理解Spring這一設計的用意了。國際化信息一般在系統輸出信息時使用,如Spring MVC的頁面標籤,控制器Controller等,不同的模塊都可能通過這些組件訪問國際化信息,因此Spring就將國際化消息作爲容器的公共基礎設施對所有組件開放。

既然一般情況下我們不會直接通過引用MessageSource Bean使用國際信息,那如何聲明容器級的國際化信息呢?我們其實在5.1.1節講解Spring容器的內部工作機制時已經埋下了伏筆:在介紹容器啓動過程時,我們通過代碼清單5-1對Spring容器啓動時的步驟進行剖析,④處的initMessageSource()方法所執行的工作就是初始化容器中的國際化信息資源:它根據反射機制從BeanDefinitionRegistry中找出名稱爲“messageSource”且類型爲org.springframework.context.MessageSource的Bean,將這個Bean定義的信息資源加載爲容器級的國際化信息資源。請看下面的配置:

    <!--①註冊資源Bean,其Bean名稱只能爲messageSource -->  
    <bean id="messageSource"   
          class="org.springframework.context.support.ResourceBundleMessageSource">  
      <property name="basenames">  
         <list>  
           <value>com/baobaotao/i18n/fmt_resource</value>  
         </list>  
      </property>  
    </bean>  

下面,我們通過ApplicationContext直接訪問國際化信息,如代碼清單5 23所示:

代碼清單5-23 通過ApplicationContext訪問國際化信息

    String[] configs = {"com/baobaotao/i18n/beans.xml"};  
    ApplicationContext ctx = new ClassPathXmlApplicationContext(configs);  
    //①直接通過容器訪問國際化信息  
    Object[] params = {"John", new GregorianCalendar().getTime()};  
       
    String str1 = ctx.getMessage("greeting.common",params,Locale.US);  
    String str2 = ctx.getMessage("greeting.morning",params,Locale.CHINA);     
    System.out.println(str1);  
    System.out.println(str2);  

運行以上代碼,輸出以下信息:

How are you!John,today is 1/9/07 5:24 PM
早上好!John,現在是下午5:24

假設MessageSource Bean名字沒有命名爲“messageSource”,以上代碼將拋出NoSuchMessageException異常。

注入MessageSource

除了直接使用ApplicationContext對象來獲取對應code的國際化信息外,我們還可以給對應的bean直接注入一個MessageSource對象以直接通過對應的MessageSource對象來獲取對應code的國際化信息。給bean注入MessageSource主要有兩種方式,一種是直接注入,一種是間接的通過實現MessageSourceAware接口進行注入。

  • 直接注入

直接注入就可以跟普通bean注入一樣進行注入,可以使用註解標註進行注入,也可以使用XML配置進行注入。以下是一個使用XML方式通過set方法進行注入的示例。

<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
	<property name="basename" value="message"/> 
</bean> 
<bean id="hello" class="com.app.Hello">
	<property name="messageSource" ref="messageSource"/>
</bean>

對應Hello的定義如下:

public class Hello { 

	private MessageSource messageSource; 
	
	public void doSomething() { 
		String appName = this.messageSource.getMessage("appName", null, null); 
		System.out.println(appName); 
	} 
	
	public void setMessageSource(MessageSource messageSource) { 
		this.messageSource = messageSource; 
	} 
}
  • 實現MessageSourceAware接口

當一個bean實現了MessageSourceAware接口時,ApplicationContext在實例化對應的bean後會將自己作爲MessageSource回調MessageSourceAware實現類的setMessageSource()方法以實現MessageSource的注入。如下代碼中Hello類就實現了MessageSourceAware接口。

public class Hello implements MessageSourceAware {

	private MessageSource messageSource;
	
	public void doSomething() {
		String appName = this.messageSource.getMessage("appName", null, null);
		System.out.println(appName);
	}
	
	public void setMessageSource(MessageSource messageSource) {
		this.messageSource = messageSource;
	}

}
發佈了40 篇原創文章 · 獲贊 78 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章