一君珠、背景
很多網(wǎng)站的用戶分布在世界各地慷吊,因此網(wǎng)站需要針對(duì)不同國(guó)家的用戶展示不同語(yǔ)言的內(nèi)容馍惹,因此就有了國(guó)際化實(shí)現(xiàn)的需求瑞眼,大多數(shù)網(wǎng)站都會(huì)在網(wǎng)站的頭部或尾部設(shè)置語(yǔ)言切換鏈接龙宏,這樣就可以直接切換成相應(yīng)的內(nèi)容。其中有些網(wǎng)站是通過(guò)網(wǎng)站地址或參數(shù)進(jìn)行區(qū)分伤疙,有些是通過(guò)設(shè)置cookie值進(jìn)行進(jìn)行區(qū)分银酗。
二、解決思路
前面已經(jīng)寫(xiě)過(guò)一篇JDK的國(guó)際化支持徒像,講解了JDK實(shí)現(xiàn)國(guó)際化的具體實(shí)現(xiàn)黍特。那么網(wǎng)站的國(guó)際化實(shí)現(xiàn)具體如何做呢?
其實(shí)網(wǎng)站的國(guó)際化實(shí)現(xiàn)與前面介紹的JDK實(shí)現(xiàn)思路類似锯蛀,只是本地化信息的獲取需要從頁(yè)面得到而已灭衷。得到了頁(yè)面信息,再獲取對(duì)應(yīng)的數(shù)據(jù)并進(jìn)行格式化處理旁涤,最后渲染到頁(yè)面即可翔曲。這里主要說(shuō)明后端的處理思路,前端的處理思路其實(shí)也類似拭抬,只是實(shí)現(xiàn)方式有區(qū)別而已部默。
那如何從頁(yè)面獲取本地化信息呢?這個(gè)是所有處理的首要環(huán)節(jié)造虎,常用的幾種方式有:
(1)直接根據(jù)Request.getLocale()方法得到本地化信息傅蹂,實(shí)際就是從Http Request Headers里面取“accept-language”對(duì)應(yīng)的值,該值擁有瀏覽器端的語(yǔ)言信息算凿;
(2)在瀏覽器端保存一個(gè)自定義名字的cookie份蝴,默認(rèn)情況下指定一個(gè)值,對(duì)應(yīng)的切換通過(guò)語(yǔ)言切換鏈接的點(diǎn)擊修改對(duì)應(yīng)的值氓轰;
(3)在請(qǐng)求URL上面添加帶本地化信息的參數(shù)或者地址里面包含本地化信息婚夫。
通過(guò)上面幾種方式,在web程序中就可以直接從request中得到了本地化信息署鸡,然后根據(jù)本地化信息從相應(yīng)的properties文件中獲取數(shù)據(jù)(比如可以通過(guò)JDK的ResourceBundle類)案糙,得到數(shù)據(jù)后如果需要的化再對(duì)數(shù)據(jù)進(jìn)行格式化處理(比如可以通過(guò)JDK的MessageFormat類)限嫌,最后將處理過(guò)的數(shù)據(jù)展示到前臺(tái)即完成了整個(gè)國(guó)際化操作。
思路已經(jīng)有了时捌,那么具體如何實(shí)現(xiàn)呢怒医?下面以Spring MVC的實(shí)現(xiàn)為例,因?yàn)樵摽蚣茏隽撕芎玫某橄蠛头庋b奢讨,是個(gè)非常好的參考例子稚叹。
三、Spring MVC實(shí)現(xiàn)及原理
3.1 本地化信息獲取
3.1.1 概述
Spring MVC的DispatcherServlet類會(huì)在initLocaleResolver方法中查找一個(gè)locale resolver拿诸,如果沒(méi)有找到就會(huì)用默認(rèn)的AcceptHeaderLocaleResolver類扒袖。locale resolver會(huì)去根據(jù)請(qǐng)求Request設(shè)置當(dāng)前的locale信息。
除了resolver類亩码,還可以定義攔截器去設(shè)置locale信息季率,比如通過(guò)請(qǐng)求參數(shù)去設(shè)置,具體下面細(xì)講描沟。
Spring MVC相關(guān)的處理類都在org.springframework.web.servlet.i18n包下蚀同。而本地化信息的獲取可以通過(guò)RequestContext.getLocale()方法得到。另外啊掏,RequestContext.getTimeZone()方法還可以得到時(shí)區(qū)信息。
3.1.2 AcceptHeaderLocaleResolver
這個(gè)從名字也能看出大概來(lái)衰猛,這個(gè)類是解析request的header中的accept-language值迟蜜,這個(gè)值通常包含客戶端支持的本地化信息,所以通過(guò)這個(gè)值可以獲取本地化信息啡省。不過(guò)這個(gè)類拿不到時(shí)區(qū)信息娜睛。這個(gè)類是默認(rèn)配置的,所以使用的話不用額外配置卦睹。
3.1.3 CookieLocaleResolver
這個(gè)類是通過(guò)cookie去存取本地化信息畦戒,客戶端可以在cookie中存儲(chǔ)一個(gè)指定名字的值代表本地化信息,然后這個(gè)類獲取后做相應(yīng)的解析即可结序。具體的配置如下:
<code><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></code>
這里對(duì)幾個(gè)配置的屬性做下說(shuō)明:
屬性 | 默認(rèn)值 | 說(shuō)明 |
---|---|---|
cookieName | classname + LOCALE | cookie名字 |
cookieMaxAge | Servlet容器默認(rèn)值 | 這個(gè)值為cookie在客戶端保留的時(shí)間障斋,如果值為-1,則不保留徐鹤;這個(gè)值會(huì)在關(guān)閉瀏覽器后無(wú)效垃环。 |
cookiePath | / | 這個(gè)值設(shè)置cookie的適用路徑,如果這個(gè)值設(shè)置了返敬,那么就表示cookie只對(duì)當(dāng)前目錄及其子目錄可見(jiàn)劲赠。 |
3.1.4 SessionLocaleResolver
這個(gè)類是通過(guò)request獲取本地化信息的估蹄,然后存在HttpSession中,所以本地化信息存取依賴于session的生命周期。具體配置如下:
<code><bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver">
</bean></code>
3.1.5 LocaleChangeInterceptor
這個(gè)攔截器會(huì)攔截請(qǐng)求中的參數(shù),然后根據(jù)參數(shù)去調(diào)用LocaleResolver的setLocale()方法,改變當(dāng)前的locale值当犯。下面舉個(gè)例子宏榕,有這個(gè)地址http://www.sf.net/home.view?siteLanguage=nl,參數(shù)siteLanguage代表locale信息,配置攔截修改locale值:
<code><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"/></code>
這里配置CookieLocaleResolver是因?yàn)長(zhǎng)ocaleChangeInterceptor需要調(diào)用LocaleResolver的setLocale()方法卜壕,這個(gè)例子里面用到了CookieLocaleResolver,當(dāng)然也可以用其他的LocaleResolver實(shí)現(xiàn)類。
3.2 數(shù)據(jù)獲取與格式化
Spring MVC的數(shù)據(jù)處理定義了一個(gè)接口MessageSource,該接口定義了數(shù)據(jù)獲取的方法。方法如下:
- String getMessage(String code, Object[] args, String defaultMessage, Locale locale);
這里code為屬性文件中的key值,args是文件中需要替換的參數(shù)值,defaultMessage是找不到內(nèi)容時(shí)的默認(rèn)內(nèi)容,locale為本地化信息访诱。 - String getMessage(String code, Object[] args, Locale locale) throws NoSuchMessageException;
這個(gè)方法與上面的方法類似哲泊,只是沒(méi)有了默認(rèn)內(nèi)容催蝗,而是找不到內(nèi)容時(shí)拋出異常切威。 - String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException;
這里參數(shù)中有個(gè)新接口MessageSourceResolvable,對(duì)前面的參數(shù)進(jìn)行了封裝丙号,locale為本地化信息先朦。
對(duì)于MessageSource接口缰冤,Spring MVC的ApplicationContext和HierarchicalMessageSource都有繼承,ApplicationContext在加載的時(shí)候喳魏,它會(huì)先去上下文里面查找bean名為messageSource的實(shí)現(xiàn)棉浸,找到后上面MessageSource方法的調(diào)用就用這個(gè)實(shí)現(xiàn)類; 如果找不到就會(huì)找包含MessageSource bean的類去使用刺彩; 再找不到就用DelegatingMessageSource去執(zhí)行方法調(diào)用了迷郑。
MessageSource常見(jiàn)的實(shí)現(xiàn)主要有如下幾個(gè):
- ResourceBundleMessageSource類:這個(gè)類實(shí)際是依賴的JDK的ResourceBundle類獲取數(shù)據(jù)、MessageFormat去做格式化创倔。
- ReloadableResourceBundleMessageSource類:這個(gè)與上面的比較就多了可重新加載嗡害,即可以在不重新啟動(dòng)應(yīng)用的情況下重新讀取新的內(nèi)容。具體實(shí)現(xiàn)方式也有區(qū)別三幻,這個(gè)類是通過(guò)Spring的PropertiesPersister策略加載就漾,依賴的是JDK的Properties類讀取內(nèi)容。
- StaticMessageSource類:這個(gè)類提供了簡(jiǎn)單的實(shí)現(xiàn)念搬,內(nèi)容是需要先配置好的抑堡。使用比較少,適合在內(nèi)容較少較簡(jiǎn)單情況下使用朗徊。
下面以最常用的ResourceBundleMessageSource類做個(gè)簡(jiǎn)單示例:
<code><bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames">
<list>
<value>format</value>
<value>exceptions</value>
</list>
</property>
</bean></code>
format與exceptions為文件基礎(chǔ)名首妖,對(duì)應(yīng)內(nèi)容配置在具體locale相應(yīng)的文件名中即可。詳細(xì)示例見(jiàn)下面部分爷恳。
3.3 小結(jié)與示例
上面兩節(jié)主要講了本地化信息獲取有缆,數(shù)據(jù)獲取與格式化兩部分,這兩部分其實(shí)也是整個(gè)國(guó)際化過(guò)程最核心的兩個(gè)部分温亲,至于請(qǐng)求的匹配與接收棚壁,返回結(jié)果的頁(yè)面渲染這個(gè)就不展開(kāi)講,與國(guó)際化不直接相關(guān)栈虚,屬于Spring MVC的基礎(chǔ)內(nèi)容袖外。
這里對(duì)整個(gè)Spring MVC的國(guó)際化過(guò)程做個(gè)大概的梳理,整個(gè)過(guò)程大概是這樣:接收請(qǐng)求——>LocaleResolver獲取/設(shè)置locale信息——>MessageSource獲取數(shù)據(jù)并格式化——>內(nèi)容展示到頁(yè)面魂务。
講了半天曼验,還是有點(diǎn)抽象,下面直接來(lái)個(gè)詳細(xì)示例:
<code>@Controller
public class I18nController {
@Autowired
private MessageSource messageSource;
@RequestMapping("i18n")
public String i18n(Model model){
//獲取本地化信息粘姜,從LocaleContext中得到
Locale locale = LocaleContextHolder.getLocale();
//初始化參數(shù)鬓照,這里簡(jiǎn)便演示,真實(shí)參數(shù)可能是從數(shù)據(jù)庫(kù)查詢處理的孤紧。這里的參數(shù)是與i18n目錄下的配置文件需要替換的內(nèi)容對(duì)應(yīng)的
Object [] objArr = new Object[4];
objArr[0] = new Date();
objArr[1] = messageSource.getMessage("goods", null, locale);//這個(gè)具體商品從配置中讀取
objArr[2] = "taobao";
objArr[3] = new BigDecimal("39.20");
//獲取格式化后的內(nèi)容
String content = messageSource.getMessage("template", objArr, locale);
model.addAttribute("content", content);
return "/i18n/show";
}
}
</code>
LocaleResolver配置豺裆,這里以Cookie為例:
<code><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></code>
MessageSource配置,這里以ResourceBundleMessageSource為例:
<code> <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames">
<list>
<value>/i18n/message</value>
</list>
</property>
</bean></code>
Properties配置号显,這里統(tǒng)一放在/i18n目錄下留储,message名字開(kāi)頭:
更詳細(xì)的代碼可以查看我的Github項(xiàng)目翼抠。
四、拓展介紹
4.1 LocaleResolver對(duì)應(yīng)Bean是如何初始化的获讳?
初始化工作是在DispatcherServlet類初始化時(shí)調(diào)用initLocaleResolver方法執(zhí)行的阴颖。
<code>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 + "]");
}
}
}</code>
從代碼里面可以看到處理化過(guò)程分為兩步:(1)先從當(dāng)前上下文環(huán)境中取名字為localeResolver的bean; (2)如果找不到就根據(jù)默認(rèn)策略去取LocaleResolver這個(gè)Class名字的bean丐膝,即執(zhí)行g(shù)etDefaultStrategy方法量愧,該方法實(shí)際是取DispatcherServlet.properties文件中的org.springframework.web.servlet.LocaleResolver對(duì)應(yīng)的值,即默認(rèn)的AcceptHeaderLocaleResolver類帅矗,再創(chuàng)建對(duì)應(yīng)的bean偎肃。
所以如果上下文中自定義了LocaleResolver就用自定義的,沒(méi)有定義會(huì)用默認(rèn)的AcceptHeaderLocaleResolver類浑此。這種寫(xiě)法在寫(xiě)公共邏輯且提供多種策略時(shí)很實(shí)用累颂。