聊聊國際化MessageSource

前言

Spring對國際化這一塊支持仍是蠻友好的,上手也是蠻簡單,可是加載流程仍是須要你們掌握的,否則會少定義一個資源文件會讓你莫名其妙的出現一些bug。接下來主要分享一下關於這一塊的基本知識。javascript


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;

}
複製代碼

頂層接口一共提供了三個獲取信息的方法:java

  • 提供默認值defaultMessage參數,當根據code沒法從相應的ResourceBundle中查詢出數據時,會將defaultMessage的值返回。
  • 當根據code沒法從相應的ResourceBundle中查詢出數據時,直接拋出NoSuchMessageException異常。
  • 經過自定義MessageSourceResolvable解析器去獲取信息,MessageSourceResolvable也就是封裝了code,args,defaultMessage三個參數,用法上並無什麼不一樣。只不過code參數爲String[]數組形式,經過遍歷調用的方式去獲取信息,只要其中一個code可以獲取到值,便直接返回。查詢不出數據時且defaultMessage爲空時,直接拋出NoSuchMessageException異常。

在獲取對應信息時,裏面還有些許流程,我下面將會結合例子來進行說明,得先熟悉下該接口的主要實現類。git

從類圖結構中能夠看出,頂層接口MessageSource下面有個抽象類AbstractMessageSource,三個基本實現類ResourceBundleMessageSource,ReloadableResourceBundleMessageSource,StaticMessageSourcegithub

  • ResourceBundleMessageSource:支持對.properties的解析,解析完成後也是用map進行封裝,最後數據存儲在PropertyResourceBundle的成員變量private Map lookup;中。
  • ReloadableResourceBundleMessageSource:能夠解析.properties.xml文件,解析完成利用PropertiesHolder進行封裝,底層仍是Properties結構。
  • StaticMessageSource:這個相對來講最容易理解,內部就是直接用map封裝了我們須要的信息。

以上三個實現類都可以對返回信息作格式化處理。spring

下面我將拿ResourceBundleMessageSource這個類進行分析,先對其幾個屬性值進行分析一下:數組

  • alwaysUseMessageFormat:默認值爲false,即默認不對返回信息作格式化處理。
  • useCodeAsDefaultMessage:默認值爲false,設置成true時,當沒法經過code參數返回信息時,會默認將code的值進行返回。
  • fallbackToSystemLocale:默認值爲true,當根據codelocale參數沒法獲取對應的ResourceBundle時,會根據當前的環境設置獲取defaultLocale,而後獲取對應的ResourceBundle

注入spring容器:

能夠選擇配置文件或者註解的方式進行配置到spring容器中,例以下方的註解方式:app

@Bean
MessageSource messageSource() {
    ResourceBundleMessageSource resourceBundleMessageSource = new ResourceBundleMessageSource();
    resourceBundleMessageSource.setBasename("exception");
    resourceBundleMessageSource.setDefaultEncoding("UTF-8");
    return resourceBundleMessageSource;
}
複製代碼

而spring 容器在初始化時,會在refresh方法中調用initMessageSource方法:ide

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方法的調用。測試


具體使用:

有如下兩種方式能夠獲取到容器中的MessageResource:ui

//方式一:     
@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實現了MessageSource接口,而且在refresh方法中也對messageSource進行了注入,不熟悉的能夠回顧下上面的注入spring容器環節。

使用案例:

以下圖所示先定義好這三個配置文件(文件名須要和上面定義MessageSource的baseName屬性值一致):

exception.properties咱們稱之爲基類文件,它能夠做爲exception_en.propertiesexception_zh.properties兩種配置文件的父類,聯想一下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
複製代碼

分別執行這四句代碼,出現這個四個結果:

  1. 可以從exception_en.properties中獲取」0000「對應的值:en_exception。
  2. 沒法從exception_en.properties中獲取」0001「對應的值,在其父類exception.properties中獲取對應的值:base_exception。
  3. 可以從exception_zh.properties中獲取」0000「對應的值:zh_exception。
  4. 沒法獲取Locale.JAPAN對應的ResourceBundle,但因爲fallbackToSystemLocale值默認是true,因此會根據環境設置獲取defaultLocale,而後獲取對應的ResourceBundle。當前環境經過System.getProperty("user.language")去獲取,目前是該值是zh,因此會從exception_zh.properties中獲取」0000「對應的值:zh_exception。

假設在注入bean時將fallbackToSystemLocale改爲false:

運行上面的代碼,會發現前面三個結果一致,可是最後一個結果有些許變化:

String app = messageSource.getMessage("0000"null, Locale.JAPAN);   //exception
複製代碼

因爲沒有匹配到對應國家的ResourceBundle,直接從基類文件中獲取結果。其實這裏直接將exception_zh.properties配置文件移除也會獲取相同的結果,都會從基類文件中獲取。

假設在注入bean時將useCodeAsDefaultMessage改爲true:

運行代碼:

String app = messageSource.getMessage("0003"null, Locale.CHINESE); //0003
複製代碼

返回結果爲「0003」,正是查詢時用的code參數。「0003」不存在於我們的exception_zh.propertiesexception.properties文件對應的ResourceBundle中,因此直接將code做爲默認結果進行返回了。


總結:

對上面的基本流程進行簡單的總結,能夠分爲三步:

  1. 檢測是否有對應locale的配置,有的話則從當前locale配置中讀取信息,而且能夠追蹤到基類文件中。
  2. 若沒有對應locale的配置,若是fallbackToSystemLocaletrue,則獲取默認defaultLocale的配置,而且能夠追蹤到基類文件中。
  3. 若是fallbackToSystemLocalefalse,則直接到基類文件中獲取信息。

目前只是接觸到根據不一樣的國家響應不一樣的異常狀態碼,後面會對頁面這一塊的國際化進行分享一下,SpringMVC也提供有對國際化這一塊的支持。測試案例均在github中。

相關文章
相關標籤/搜索