前言
Spring對國際化這一塊支持還是蠻友好的,上手也是蠻簡單,但是加載流程還是需要大家掌握的,不然會少定義一個資源文件會讓你莫名其妙的出現(xiàn)一些bug。接下來主要分享一下關(guān)于這一塊的基本知識。
MessageSource
public interface MessageSource {
@Nullable
String getMessage(String code, @Nullable Object[] args, @Nullable String defaultMessage, Locale locale);
String getMessage(String code, @Nullable Object[] args, Locale locale) throws NoSuchMessageException;
String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException;
}
頂層接口一共提供了三個獲取信息的方法:
- 提供默認值
defaultMessage參數(shù),當根據(jù)code無法從相應(yīng)的ResourceBundle中查詢出數(shù)據(jù)時,會將defaultMessage的值返回。 - 當根據(jù)
code無法從相應(yīng)的ResourceBundle中查詢出數(shù)據(jù)時,直接拋出NoSuchMessageException異常。 - 通過自定義
MessageSourceResolvable解析器去獲取信息,MessageSourceResolvable也就是封裝了code,args,defaultMessage三個參數(shù),用法上并沒有什么不同。只不過code參數(shù)為String[]數(shù)組形式,通過遍歷調(diào)用的方式去獲取信息,只要其中一個code能夠獲取到值,便直接返回。查詢不出數(shù)據(jù)時且defaultMessage為空時,直接拋出NoSuchMessageException異常。
在獲取對應(yīng)信息時,里面還有些許流程,我下面將會結(jié)合例子來進行說明,得先熟悉下該接口的主要實現(xiàn)類。

從類圖結(jié)構(gòu)中可以看出,頂層接口MessageSource下面有個抽象類AbstractMessageSource,三個基本實現(xiàn)類ResourceBundleMessageSource,ReloadableResourceBundleMessageSource,StaticMessageSource。
-
ResourceBundleMessageSource:支持對.properties的解析,解析完成后也是用map進行封裝,最后數(shù)據(jù)存儲在PropertyResourceBundle的成員變量private Map<String,Object> lookup;中。 -
ReloadableResourceBundleMessageSource:可以解析.properties和.xml文件,解析完成利用PropertiesHolder進行封裝,底層還是Properties結(jié)構(gòu)。 -
StaticMessageSource:這個相對來說最容易理解,內(nèi)部就是直接用map封裝了咱們需要的信息。
以上三個實現(xiàn)類均可以對返回信息做格式化處理。
下面我將拿ResourceBundleMessageSource這個類進行分析,先對其幾個屬性值進行分析一下:
-
alwaysUseMessageFormat:默認值為false,即默認不對返回信息做格式化處理。 -
useCodeAsDefaultMessage:默認值為false,設(shè)置成true時,當無法通過code參數(shù)返回信息時,會默認將code的值進行返回。 -
fallbackToSystemLocale:默認值為true,當根據(jù)code和locale參數(shù)無法獲取對應(yīng)的ResourceBundle時,會根據(jù)當前的環(huán)境設(shè)置獲取defaultLocale,然后獲取對應(yīng)的ResourceBundle。
注入spring容器:
可以選擇配置文件或者注解的方式進行配置到spring容器中,例如下方的注解方式:
@Bean
MessageSource messageSource() {
ResourceBundleMessageSource resourceBundleMessageSource = new ResourceBundleMessageSource();
resourceBundleMessageSource.setBasename("exception");
resourceBundleMessageSource.setDefaultEncoding("UTF-8");
return resourceBundleMessageSource;
}
而spring 容器在初始化時,會在refresh方法中調(diào)用initMessageSource方法:
protected void initMessageSource() {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (beanFactory.containsLocalBean(MESSAGE_SOURCE_BEAN_NAME)) {
//從容器中獲取name為 "messageSource",類型為MessageSource的bean
this.messageSource = beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME, MessageSource.class);
if (this.parent != null && this.messageSource instanceof HierarchicalMessageSource) {
HierarchicalMessageSource hms = (HierarchicalMessageSource) this.messageSource;
if (hms.getParentMessageSource() == null) {
hms.setParentMessageSource(getInternalParentMessageSource());
}
}
if (logger.isTraceEnabled()) {
logger.trace("Using MessageSource [" + this.messageSource + "]");
}
}
else {
DelegatingMessageSource dms = new DelegatingMessageSource();
dms.setParentMessageSource(getInternalParentMessageSource());
this.messageSource = dms;
beanFactory.registerSingleton(MESSAGE_SOURCE_BEAN_NAME, this.messageSource);
if (logger.isTraceEnabled()) {
logger.trace("No '" + MESSAGE_SOURCE_BEAN_NAME + "' bean, using [" + this.messageSource + "]");
}
}
}
上面這段代碼邏輯很清晰,先判斷當前是否有該bean的BeanDefinition,若存在,則對MessageSource進行初始化并賦值給其成員變量messageSource。如果不存在該bean的BeanDefinition,則賦值一個空的MessageSource,也就是DelegatingMessageSource,以便能夠正常的進行getMessage方法的調(diào)用。
具體使用:
有以下兩種方式可以獲取到容器中的MessageResource:
//方式一:
@Autowired
private MessageSource messageSource;
//方式二:
@Component
public class MessageResourceConfiguration implements MessageSourceAware {
private MessageSource messageSource;
@Override
public void setMessageSource(MessageSource messageSource) {
this.messageSource = messageSource;
}
}
PS:其實用ApplicationContext也可以,ApplicationContext實現(xiàn)了MessageSource接口,并且在refresh方法中也對messageSource進行了注入,不熟悉的可以回顧下上面的注入spring容器環(huán)節(jié)。
使用案例:
如下圖所示先定義好這三個配置文件(文件名需要和上面定義MessageSource的baseName屬性值一致):

exception.properties我們稱之為基類文件,它可以作為exception_en.properties和exception_zh.properties兩種配置文件的父類,聯(lián)想一下spring的子父容器的概率,相信不難理解。
1.String app = messageSource.getMessage("0000", null, Locale.ENGLISH); //en_exception
2.String app = messageSource.getMessage("0001", null, Locale.ENGLISH); //base_exception
3.String app = messageSource.getMessage("0000", null, Locale.CHINESE); //zh_exception
4.String app = messageSource.getMessage("0000", null, Locale.JAPAN); //zh_exception
分別執(zhí)行這四句代碼,出現(xiàn)這個四個結(jié)果:
- 能夠從
exception_en.properties中獲取”0000“對應(yīng)的值:en_exception。 - 無法從
exception_en.properties中獲取”0001“對應(yīng)的值,在其父類exception.properties中獲取對應(yīng)的值:base_exception。 - 能夠從
exception_zh.properties中獲取”0000“對應(yīng)的值:zh_exception。 - 無法獲取
Locale.JAPAN對應(yīng)的ResourceBundle,但由于fallbackToSystemLocale值默認是true,所以會根據(jù)環(huán)境設(shè)置獲取defaultLocale,然后獲取對應(yīng)的ResourceBundle。當前環(huán)境通過System.getProperty("user.language")去獲取,目前是該值是zh,所以會從exception_zh.properties中獲取”0000“對應(yīng)的值:zh_exception。
假設(shè)在注入bean時將fallbackToSystemLocale改成false:

運行上面的代碼,會發(fā)現(xiàn)前面三個結(jié)果一致,但是最后一個結(jié)果有些許變化:
String app = messageSource.getMessage("0000", null, Locale.JAPAN); //exception
由于沒有匹配到對應(yīng)國家的ResourceBundle,直接從基類文件中獲取結(jié)果。其實這里直接將exception_zh.properties配置文件移除也會獲取相同的結(jié)果,都會從基類文件中獲取。
假設(shè)在注入bean時將useCodeAsDefaultMessage改成true:

運行代碼:
String app = messageSource.getMessage("0003", null, Locale.CHINESE); //0003
返回結(jié)果為“0003”,正是查詢時用的code參數(shù)。“0003”不存在于咱們的exception_zh.properties和exception.properties文件對應(yīng)的ResourceBundle中,所以直接將code作為默認結(jié)果進行返回了。
總結(jié):
對上面的基本流程進行簡單的總結(jié),可以分為三步:
- 檢測是否有對應(yīng)
locale的配置,有的話則從當前locale配置中讀取信息,并且可以追蹤到基類文件中。 - 若沒有對應(yīng)
locale的配置,如果fallbackToSystemLocale為true,則獲取默認defaultLocale的配置,并且可以追蹤到基類文件中。 - 如果
fallbackToSystemLocale為false,則直接到基類文件中獲取信息。
目前只是接觸到根據(jù)不同的國家響應(yīng)不同的異常狀態(tài)碼,后面會對頁面這一塊的國際化進行分享一下,SpringMVC也提供有對國際化這一塊的支持。測試案例均在github中。