Spring對國際化這一塊支持仍是蠻友好的,上手也是蠻簡單,可是加載流程仍是須要你們掌握的,否則會少定義一個資源文件會讓你莫名其妙的出現一些bug。接下來主要分享一下關於這一塊的基本知識。javascript
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
,StaticMessageSource
。github
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,當根據code
和locale
參數沒法獲取對應的ResourceBundle
時,會根據當前的環境設置獲取defaultLocale
,而後獲取對應的ResourceBundle
。能夠選擇配置文件或者註解的方式進行配置到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.properties
和exception_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
複製代碼
分別執行這四句代碼,出現這個四個結果:
exception_en.properties
中獲取」0000「對應的值:en_exception。exception_en.properties
中獲取」0001「對應的值,在其父類exception.properties
中獲取對應的值:base_exception。exception_zh.properties
中獲取」0000「對應的值:zh_exception。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.properties
和exception.properties
文件對應的ResourceBundle
中,因此直接將code
做爲默認結果進行返回了。
對上面的基本流程進行簡單的總結,能夠分爲三步:
locale
的配置,有的話則從當前locale
配置中讀取信息,而且能夠追蹤到基類文件中。locale
的配置,若是fallbackToSystemLocale
爲true
,則獲取默認defaultLocale
的配置,而且能夠追蹤到基類文件中。fallbackToSystemLocale
爲false
,則直接到基類文件中獲取信息。目前只是接觸到根據不一樣的國家響應不一樣的異常狀態碼,後面會對頁面這一塊的國際化進行分享一下,SpringMVC也提供有對國際化這一塊的支持。測試案例均在github中。