國際化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; }
熟悉的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處理,否則就不處理。
鑒於ApplicationContext實現類對MessageSource接口實現的這種機制,如果需要通過ApplicationContext來獲取國際化信息,那么只需要在對應的ApplicationContext中定義一個MessageSource類型的bean,並且指定對應的beanName為messageSource即可。Spring中對MessageSource提供了三個實現類,分別是ReloadableResourceBundleMessageSource、StaticMessageSource和ResourceBundleMessageSource。
ResourceBundleMessageSource
ResourceBundleMessageSource是基於JDK ResourceBundle的MessageSource接口實現類。會將訪問過的ResourceBundle緩存起來,以便於下次直接從緩存中獲取進行使用。
示例
如下在bean容器中定義一個messageSource和一個名為hello的bean,並指定messageSource的basename為message,即根資源文件應為類路徑下的message.properties,其它的是需要帶Locale后綴的,如中國大陸是message_zh_CN.properties。
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basename" value="message"/> </bean> <bean id="hello" class="com.app.Hello"/>
其中類Hello的代碼如下所示,可以看到其通過實現ApplicationContextAware接口注入了當前的ApplicationContext對象,然后在其doSomething()方法中,我們通過注入的ApplicationContext對象以code為“appName”獲取從對應的國際化信息,這里在中文環境下默認就會從message_zh_CN.properties文件中獲取。
public class Hello implements ApplicationContextAware { private ApplicationContext context; public void doSomething() { String appName = context.getMessage("appName", null, null); System.out.println(appName); } public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.context = applicationContext; } }
接下來我們在類路徑下分別建立message_zh_CN.properties文件和message.properties文件。其內容分別對應如下。
1、message.properties文件內容
appName=test
2、message_zh_CN.properties文件內容
appName=測試
接下來我們運行如下測試代碼,將看到控制台將輸出“測試”,因為在獲取對應信息時我們沒有指定Locale,所以默認會取當前的Locale,即zh_CN,所以對應的信息將從message_zh_CN.properties文件中獲取。
@ContextConfiguration({"classpath:/applicationContext.xml"}) @RunWith(SpringJUnit4ClassRunner.class) public class ApplicationContextTest { @Autowired private ApplicationContext context; @Test public void test() { Hello hello = context.getBean("hello", Hello.class); hello.doSomething(); } }
指定多個basename
在上述示例中,我們通過setBasename()指定ResourceBundleMessageSource的一個對應資源文件的基名稱。但有時候我們可能會存在多種分類的資源文件,它們對應不同的基名稱,如view.properties、remind.properties等,對於這種情況我們就可以通過ResourceBundleMessageSource的setBasenames()方法來指定多個basename。
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basenames"> <array> <value>remind</value> <value>view</value> </array> </property> </bean>
defaultEncoding
可以通過setDefaultEncoding()來指定將用來加載對應資源文件時使用的編碼,默認為空,表示將使用默認的編碼進行獲取。
fallbackToSystemLocale
默認情況下,當指定Locale不存在某code對應的屬性時,默認將嘗試從系統對應的Locale中解析對應的code,只有都不能解析時,才會使用基文件進行解析,如果還不能解析則將拋出異常。打個比方針對基名稱“message”我們有兩個屬性文件, message.properties和message_zh_CN.properties,其中前者定義了appName=test,且定義了hello=hello,而后者只定義了appName=測試,那么當我們通過如下代碼獲取對應code對應的信息時,輸出將如代碼中注釋所示,即輸出兩個“測試”,一個“hello”
System.out.println(this.messageSource.getMessage("appName", null, null));//測試 System.out.println(this.messageSource.getMessage("appName", null, Locale.ENGLISH));//測試 System.out.println(this.messageSource.getMessage("hello", null, Locale.CHINA));//hello
第一個輸出因為我們沒有指定Locale,則默認會使用當前系統的Locale,即中文環境,此時將從message_zh_CN.properties中獲取對應code對應的信息,所以輸出為“測試”。第二個是因為我們沒有定義Locale.ENGLISH對應的本地資源文件,默認將使用當前系統的Locale,即Locale.CHINA,所以輸出為“測試”。第三個是因為Locale.CHINA對應的資源文件message_zh_CN.properties文件中不存在對應code的屬性,所以將從基文件message.properties中獲取,即輸出為“hello”。
前面已經提到了當指定Locale不能解析指定的code時,默認將使用系統當前的Locale來解析對應的code,這是通過ResourceBundleMessageSource的fallbackToSystemLocale屬性來定義的,默認為true,我們可以通過對應的set方法來設置其值為false,以使在指定的Locale不能解析指定的code時將使用基文件來解析對應的code。
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basename" value="message"/> <property name="fallbackToSystemLocale" value="false"/> </bean>
當我們指定fallbackToSystemLocale為false后,再運行上述測試代碼時對應結果將如下。其中第一個跟第三個的輸出結果將不變,第二個將變為“test”,因為此時使用Locale.ENGLISH不能解析appName時將使用基文件message.properties來解析。
System.out.println(this.messageSource.getMessage("appName", null, null));//測試 System.out.println(this.messageSource.getMessage("appName", null, Locale.ENGLISH));//test System.out.println(this.messageSource.getMessage("hello", null, Locale.CHINA));//hello
ReloadableResourceBundleMessageSource
ReloadableResourceBundleMessageSource是以ResourceBundleMessageSource結尾的,但實際上它跟ResourceBundleMessageSource沒有什么直接的關系。 ReloadableResourceBundleMessageSource也是對MessageSource的一種實現,其用法配置等和ResourceBundleMessageSource基本一致。所不同的是ReloadableResourceBundleMessageSource內部是使用PropertiesPersister來加載對應的文件,這包括properties文件和xml文件,然后使用java.util.Properties來保存對應的數據。
另外,ReloadableResourceBundleMessageSource允許我們指定非類路徑下的文件作為對應的資源文件,而ResourceBundleMessageSource是限制我們只能將對應的資源文件放置在類路徑下的。在指定basename時,還可以使用Spring支持的資源文件的前綴,如classpath等。
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource"> <property name="basename" value="classpath:message"/> <property name="fallbackToSystemLocale" value="false"/> <property name="defaultEncoding" value="UTF-8"/> </bean>
cacheSeconds
ReloadableResourceBundleMessageSource也是支持緩存對應的資源文件的,默認的緩存時間為永久,即獲取一次資源文件后就將其緩存起來,以后再也不重新去獲取該文件。這個可以通過setCacheSeconds()方法來指定對應的緩存時間,單位為秒。前面我們已經說過ResourceBundleMessageSource也是會緩存對應的資源文件的,而且其也可以通過setCacheSeconds()方法指定對應的緩存時間,但是即使指定了也不會生效,其不會對緩存過的文件重新加載。
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource"> <property name="basename" value="classpath:message"/> <property name="cacheSeconds" value="1200"/> </bean>
concurrentRefresh
ReloadableResourceBundleMessageSource使用concurrentRefresh屬性來控制是否允許並發刷新,默認為true。其表示當線程A正在刷新緩存的資源文件F時,線程B也准備刷新緩存的資源文件F,那么線程A將繼續執行刷新緩存的資源文件F的動作,而線程B將直接獲取到原來緩存的資源文件F,當然這里也可以是取的線程A剛剛刷新的那個資源文件F。如果我們設定concurrentRefresh為false,那么先獲取到對應鎖的線程A將先刷新緩存中的資源文件F,然后在其釋放對應的鎖后,線程B將獲取到對應的鎖並再一次刷新緩存中的資源文件F。
注入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; } }
參考
原文:https://blog.csdn.net/elim168/article/details/77891450