1. 程式人生 > >SpringBoot國際化配置分析

SpringBoot國際化配置分析

DispatcherServlet使得開發人員可以通過客戶端的Locale來自動轉換訊息進行國際化;在整個國際化的過程中,主要分成兩步,一是解析客戶端的Locale,一是查詢國際化的訊息;

1. 解析Locale

解析Locale是通過LocaleResolver來完成的。

當有請求時,DispatcherServlet查詢LocaleResolver型別的Bean,如果找到,則使用它獲取並設定Locale。在這之後,通過RequestContext.getLocale方法則可以獲取到Locale。

Spring預設在i18n包中提供了幾種實現,如通過Cookie、Header、Session等方式來獲取Locale的實現等。

1.1 定義所使用的LocaleResolver:

Spring Boot中可以通過Bean註解來定義所使用的LocaleResolver

@Configuration
public class AdditionalBeanConfig {
    @Bean
    public LocaleResolver getLocalResolver() {
        return new CookieLocaleResolver();
    }
}

預設情況下,SpringBoot中是沒有包含LocaleResolver物件的。

1.2 獲取Locale

如果需要在應用中獲取當前客戶端所使在的Locale,在Controller中通過以下方式查詢Locale:

@RestController
@RequestMapping("/test")
public class TestController {
    @GetMapping("/locale")
    public String locale(HttpServletRequest request) {
        // 第一種方式,通過構建RequestContext來查詢Locale
        RequestContext requestContext = new RequestContext(request);
        System.out.println(requestContext.getLocale().toLanguageTag());

        // 第二種方式, 通過使用LocaleContextHolder來獲取Locale
System.out.println(LocaleContextHolder.getLocale().toLanguageTag()); // 第二種方式,通過RequestContextUtils來獲取Locale return RequestContextUtils.getLocale(request).toLanguageTag(); } }

列印及返回的結果為:zh-CN

RequestContext:用於儲存請求相關的一些資訊,如當前請求的Web上下文、Theme、Locale等; RequestContextUtils: 使用RequestContext的一個輔助類;提供靜態方法來使用RequestContext,這樣就不需要手動建立RequestContext物件;

2. 查詢訊息

查詢訊息一般通過MessageSource介面進行,它可以根據Code查詢訊息,同時支援國際化及引數傳遞。

2.1 MessageSource介面

當不配置任何的MessageSource時,Spring預設使用DelegatingMessageSource,如以下控制器:

@GetMapping("/message")
public String message() {
    return messageSource.toString();
}

請求返回後是:[email protected]471e

這個時候,我們使用MessageSource來獲取Message:

@GetMapping("/message")
public String message(HttpServletRequest request) {
    Locale locale = RequestContextUtils.getLocale(request);
    return messageSource.getMessage("test", null, locale);
}

此時會報錯,錯誤資訊:

There was an unexpected error (type=Internal Server Error, status=500).
No message found under code 'test' for locale 'zh_CN'.

經過跟蹤,這個異常在DelegatingMessageSource的getMessage方法中處理:

@Override
public String getMessage(String code, @Nullable Object[] args, Locale locale) throws NoSuchMessageException {
    if (this.parentMessageSource != null) {
        return this.parentMessageSource.getMessage(code, args, locale);
    }
    else {
        throw new NoSuchMessageException(code, locale);
    }
}

由於parentMessageSource為空,直接丟擲異常,即訪問介面時看到的訊息。

2.2 指定MessageSource

MessageSource包含有以下幾個實現類:

  • StaticMessageSource: 基於程式註冊的方式來管理資源;一般用於測試;
  • ResourceBundleMessageSource: 基於JDK中的ResourceBundle實現;
  • ReloadableResourceBundleMessageSource:包含執行時動態載入機制的實現;

2.2.1 StaticMessageSource

先使用StaticMessageSource來進行實驗:

@Bean
public MessageSource messageSource() {
    StaticMessageSource messageSource =  new StaticMessageSource();
    messageSource.addMessage("test", Locale.CHINA, "測試訊息");

    return messageSource;
}

仍舊使用之前的Controller,這時不會報錯直接會返回“測試訊息”這個串了。

2.2.2 ResourceBundleMessageSource

上面已經說過,StaticMessageSource這種方式一般用於測試當中,在生產中一般使用ResourceBundleMessageSource。

我們將上面使用staticMessageSource的地方進行替換:

@Bean
public MessageSource messageSource() {
    ResourceBundleMessageSource messageSource =  new ResourceBundleMessageSource();

    // 通過setBasenames來設定國際化檔案
    messageSource.setBasenames("messages");

    // 也可以指定多個,如:
    //        messageSource.setBasenames("messages", "errors");

    return messageSource;
}

它會去Classpath下的目錄中查詢名稱為mesages開頭的國際化檔案並進行載入。

在resources目錄下新增messages.properties及messages_zh_cn.properties兩個檔案,並在message_zh_cn.properties中新增test=測試訊息 的內容。

執行前面的Controller,可以看到獲取到的訊息。也可以僅在messages.properties中新增;它會先去對應Locale的檔案中查詢,如果未找到則去預設的messages.properties中查詢;如果仍舊未找到則丟擲異常。

有時候resources目錄東西過多需要分子目錄,可以在定義MessageSource的時候進行指定,如下面在resources下增加子目錄i18n:

 @Bean
public MessageSource messageSource() {
    ResourceBundleMessageSource messageSource =  new ResourceBundleMessageSource();

    // 通過setBasenames來設定國際化檔案
    messageSource.setBasenames("i18n.messages");

    // 也可以指定多個,如:
    //        messageSource.setBasenames("messages", "errors");

    return messageSource;
}

3. 總結

綜上,在專案國際化過程中,需要建立LocaleResolver及MessageSource物件並注入到容器中,然後定義相關的國際化屬性檔案;在檔案中定義好相關資訊即可在需要使用的地方通過Autowired注入MessageSource物件並使用它來解析國際化訊息了。

注意到在每個使用的地方都要傳入Locale到MessageSource的方法中,實際上,通過使用LocaleContextHolder可以進行進一步的簡化:

@Component
public class MessageSourceProxy {
    @Autowired
    private MessageSource messageSource;

    public String getMessage(String code, Object[] params) {
        return messageSource.getMessage(code, params, LocaleContextHolder.getLocale());
    }
}

然後改造前面的Controller:

@Autowired
private MessageSourceProxy messageSourceProxy;

@GetMapping("/message")
public String message(HttpServletRequest request) {
    //        Locale locale = RequestContextUtils.getLocale(request);
    //        return messageSource.getMessage("test", null, locale);

    return messageSourceProxy.getMessage("test", null);
}

這樣簡化後,使用者甚至都不用手動去獲取Locale了。

本文主要分析國際化的配置過程是怎樣完成的,關於MessageSource物件的使用,可以參考具體的API。