網站國際化實現(2)—Spring MVC國際化實現及原理

 

一、背景

很多網站的用戶分佈在世界各地,因此網站需要針對不同國家的用戶展示不同語言的內容,因此就有了國際化實現的需求,大多數網站都會在網站的頭部或尾部設置語言切換鏈接,這樣就可以直接切換成相應的內容。其中有些網站是通過網站地址或參數進行區分,有些是通過設置cookie值進行進行區分。

 

二、解決思路

前面已經寫過一篇JDK的國際化支持,講解了JDK實現國際化的具體實現。那麼網站的國際化實現具體如何做呢?

 

其實網站的國際化實現與前面介紹的JDK實現思路類似,只是本地化信息的獲取需要從頁面得到而已。得到了頁面信息,再獲取對應的數據並進行格式化處理,最後渲染到頁面即可。這裏主要說明後端的處理思路,前端的處理思路其實也類似,只是實現方式有區別而已。

 

那如何從頁面獲取本地化信息呢?這個是所有處理的首要環節,常用的幾種方式有:


(1)直接根據Request.getLocale()方法得到本地化信息,實際就是從Http Request Headers裏面取“accept-language”對應的值,該值擁有瀏覽器端的語言信息;
(2)在瀏覽器端保存一個自定義名字的cookie,默認情況下指定一個值,對應的切換通過語言切換鏈接的點擊修改對應的值;
(3)在請求URL上面添加帶本地化信息的參數或者地址裏面包含本地化信息。

 

通過上面幾種方式,在web程序中就可以直接從request中得到了本地化信息,然後根據本地化信息從相應的properties文件中獲取數據(比如可以通過JDK的ResourceBundle類),得到數據後如果需要的化再對數據進行格式化處理(比如可以通過JDK的MessageFormat類),最後將處理過的數據展示到前臺即完成了整個國際化操作。

 

思路已經有了,那麼具體如何實現呢?下面以Spring MVC的實現爲例,因爲該框架做了很好的抽象和封裝,是個非常好的參考例子。

 

三、Spring MVC實現及原理

3.1 本地化信息獲取

3.1.1 概述

Spring MVC的DispatcherServlet類會在initLocaleResolver方法中查找一個locale resolver,如果沒有找到就會用默認的AcceptHeaderLocaleResolver類。locale resolver會去根據請求Request設置當前的locale信息。

除了resolver類,還可以定義攔截器去設置locale信息,比如通過請求參數去設置,具體下面細講。

 

Spring MVC相關的處理類都在org.springframework.web.servlet.i18n包下。而本地化信息的獲取可以通過RequestContext.getLocale()方法得到。另外,RequestContext.getTimeZone()方法還可以得到時區信息。

 

3.1.2 AcceptHeaderLocaleResolver

這個從名字也能看出大概來,這個類是解析request的header中的accept-language值,這個值通常包含客戶端支持的本地化信息,所以通過這個值可以獲取本地化信息。不過這個類拿不到時區信息。這個類是默認配置的,所以使用的話不用額外配置。

 

3.1.3 CookieLocaleResolver

這個類是通過cookie去存取本地化信息,客戶端可以在cookie中存儲一個指定名字的值代表本地化信息,然後這個類獲取後做相應的解析即可。具體的配置如下:

<bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver">
 <property name="cookieName" value="clientlanguage"/>
 <property name="cookieMaxAge" value="100000"/>
 <property name="cookiePath" value="/"/>
</bean>

 
這裏對幾個配置的屬性做下說明:

 

     
cookieName 默認值:classname + LOCALE cookie名字
cookieMaxAge 默認值:Servlet容器默認值 這個值爲cookie在客戶端保留的時間,如果值爲-1,則不保留;這個值會在關閉瀏覽器後無效。
cookiePath 默認值:/ 這個值設置cookie的適用路徑,如果這個值設置了,那麼就表示cookie只對當前目錄及其子目錄可見。

 

3.1.4 SessionLocaleResolver

這個類是通過request獲取本地化信息的,然後存在HttpSession中,所以本地化信息存取依賴於session的生命週期。具體配置如下:

<bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver">
</bean>
 

 

3.1.5 LocaleChangeInterceptor

這個攔截器會攔截請求中的參數,然後根據參數去調用LocaleResolver的setLocale()方法,改變當前的locale值。下面舉個例子,有這個地址http://www.sf.net/home.view?siteLanguage=nl,參數siteLanguage代表locale信息,配置攔截修改locale值:

<bean id="localeChangeInterceptor" class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
 <property name="paramName" value="siteLanguage"/></bean>
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver"/>
 

這裏配置CookieLocaleResolver是因爲LocaleChangeInterceptor需要調用LocaleResolver的setLocale()方法,這個例子裏面用到了CookieLocaleResolver,當然也可以用其他的LocaleResolver實現類。

 

3.2 數據獲取與格式化

Spring MVC的數據處理定義了一個接口MessageSource,該接口定義了數據獲取的方法。方法如下:

 

  1. String getMessage(String code, Object[] args, String defaultMessage, Locale locale);
    這裏code爲屬性文件中的key值,args是文件中需要替換的參數值,defaultMessage是找不到內容時的默認內容,locale爲本地化信息。
  2. String getMessage(String code, Object[] args, Locale locale) throws NoSuchMessageException;
    這個方法與上面的方法類似,只是沒有了默認內容,而是找不到內容時拋出異常。
  3. String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException;
    這裏參數中有個新接口MessageSourceResolvable,對前面的參數進行了封裝,locale爲本地化信息。

對於MessageSource接口,Spring MVC的ApplicationContext和HierarchicalMessageSource都有繼承,ApplicationContext在加載的時候,它會先去上下文裏面查找bean名爲messageSource的實現,找到後上面MessageSource方法的調用就用這個實現類; 如果找不到就會找包含MessageSource bean的類去使用; 再找不到就用DelegatingMessageSource去執行方法調用了。

 

MessageSource常見的實現主要有如下幾個:

 

  1. ResourceBundleMessageSource類:這個類實際是依賴的JDK的ResourceBundle類獲取數據、MessageFormat去做格式化。
  2. ReloadableResourceBundleMessageSource類:這個與上面的比較就多了可重新加載,即可以在不重新啓動應用的情況下重新讀取新的內容。具體實現方式也有區別,這個類是通過Spring的PropertiesPersister策略加載,依賴的是JDK的Properties類讀取內容。
  3. StaticMessageSource類:這個類提供了簡單的實現,內容是需要先配置好的。使用比較少,適合在內容較少較簡單情況下使用。

下面以最常用的ResourceBundleMessageSource類做個簡單示例:

<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
 <property name="basenames">
 <list>
 <value>format</value>
 <value>exceptions</value>
 </list>
 </property>
 </bean>

 
format與exceptions爲文件基礎名,對應內容配置在具體locale相應的文件名中即可。詳細示例見下面部分。

 

3.3 小結與示例

上面兩節主要講了本地化信息獲取,數據獲取與格式化兩部分,這兩部分其實也是整個國際化過程最核心的兩個部分,至於請求的匹配與接收,返回結果的頁面渲染這個就不展開講,與國際化不直接相關,屬於Spring MVC的基礎內容。

 

這裏對整個Spring MVC的國際化過程做個大概的梳理,整個過程大概是這樣:接收請求——>LocaleResolver獲取/設置locale信息——>MessageSource獲取數據並格式化——>內容展示到頁面。

 

講了半天,還是有點抽象,下面直接來個詳細示例:

@Controller
public class I18nController {
 @Autowired
 private MessageSource messageSource;
 @RequestMapping("i18n")
 public String i18n(Model model){
 //獲取本地化信息,從LocaleContext中得到
 Locale locale = LocaleContextHolder.getLocale();
 //初始化參數,這裏簡便演示,真實參數可能是從數據庫查詢處理的。這裏的參數是與i18n目錄下的配置文件需要替換的內容對應的
 Object [] objArr = new Object[4];
 objArr[0] = new Date();
 objArr[1] = messageSource.getMessage("goods", null, locale);//這個具體商品從配置中讀取
 objArr[2] = "taobao";
 objArr[3] = new BigDecimal("39.20");
 //獲取格式化後的內容
 String content = messageSource.getMessage("template", objArr, locale);
 model.addAttribute("content", content);
 return "/i18n/show";
 }
}

 

LocaleResolver配置,這裏以Cookie爲例:

<bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver">
 <property name="cookieName" value="clientlanguage"/>
 <property name="cookieMaxAge" value="100000"/>
 <property name="cookiePath" value="/"/>
</bean>

 

MessageSource配置,這裏以ResourceBundleMessageSource爲例:

<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
 <property name="basenames">
 <list>
 <value>/i18n/message</value>
 </list>
 </property>
 </bean>

 

Properties配置,這裏統一放在/i18n目錄下,message名字開頭:


文件配置.png

 

 

更詳細的代碼可以查看我的Github項目

 

四、拓展介紹

4.1 LocaleResolver對應Bean是如何初始化的?

初始化工作是在DispatcherServlet類初始化時調用initLocaleResolver方法執行的。

private void initLocaleResolver(ApplicationContext context) {
 try {
 this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
 if (logger.isDebugEnabled()) {
 logger.debug("Using LocaleResolver [" + this.localeResolver + "]");
 }
 }
 catch (NoSuchBeanDefinitionException ex) {
 // We need to use the default.
 this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
 if (logger.isDebugEnabled()) {
 logger.debug("Unable to locate LocaleResolver with name '" + LOCALE_RESOLVER_BEAN_NAME +
 "': using default [" + this.localeResolver + "]");
 }
 }
 }

 
從代碼裏面可以看到處理化過程分爲兩步:

(1)先從當前上下文環境中取名字爲localeResolver的bean;

(2)如果找不到就根據默認策略去取LocaleResolver這個Class名字的bean,即執行getDefaultStrategy方法,該方法實際是取DispatcherServlet.properties文件中的org.springframework.web.servlet.LocaleResolver對應的值,即默認的AcceptHeaderLocaleResolver類,再創建對應的bean。


所以如果上下文中自定義了LocaleResolver就用自定義的,沒有定義會用默認的AcceptHeaderLocaleResolver類。這種寫法在寫公共邏輯且提供多種策略時很實用。

 

五、參考資料

  1. Spring MVC using locales官方教程
  2. Spring MVC using MessageSource官方教程
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章