spring學(xué)習(xí)4-國(guó)際化

國(guó)際化

國(guó)際化也稱作i18n队魏,其來(lái)源是英文單詞 internationalization的首末字符i和n喳魏,18為中間的字符數(shù)悲柱。由于軟件發(fā)行可能面向多個(gè)國(guó)家民晒,對(duì)于不同國(guó)家的用戶芙贫,軟件顯示不同語(yǔ)言的過(guò)程就是國(guó)際化(舉個(gè)例子搂鲫,人們玩的電子游戲,通郴瞧剑可以選擇多個(gè)語(yǔ)言版本魂仍,適應(yīng)于多個(gè)國(guó)家的玩家)。通常來(lái)講拣挪,軟件中的國(guó)際化是通過(guò)配置文件來(lái)實(shí)現(xiàn)的擦酌,假設(shè)某個(gè)軟件要支撐兩種語(yǔ)言,那么就需要兩個(gè)版本的配置文件菠劝。

Java國(guó)際化

Java自身是支持國(guó)際化的赊舶,java.util.Locale用于指定當(dāng)前用戶所屬的語(yǔ)言環(huán)境等信息,java.util.ResourceBundle用于查找綁定對(duì)應(yīng)的資源文件赶诊。
Locale包含了language信息和country信息笼平,Locale創(chuàng)建默認(rèn)locale對(duì)象時(shí)使用的靜態(tài)方法:

    /**
     * This method must be called only for creating the Locale.*
     * constants due to making shortcuts.
     */
    private static Locale createConstant(String lang, String country) {
        BaseLocale base = BaseLocale.createInstance(lang, country);
        return getInstance(base, null);
    }

配置文件命名規(guī)則:
basename_language_country.properties
必須遵循以上的命名規(guī)則,java才會(huì)識(shí)別舔痪。其中出吹,basename是必須的,語(yǔ)言和國(guó)家是可選的辙喂。這里存在一個(gè)優(yōu)先級(jí)概念捶牢,如果同時(shí)提供了messages.properties和messages_zh_CN.propertes兩個(gè)配置文件鸠珠,如果提供的locale符合en_CN,那么優(yōu)先查找messages_en_CN.propertes配置文件秋麸,如果沒(méi)查找到渐排,再查找messages.properties配置文件。最后灸蟆,提示下驯耻,所有的配置文件必須放在classpath中,一般放在resource目錄下炒考。
舉個(gè)例子可缚,兩個(gè)配置文件內(nèi)容分別如下:

#messages.properties
test=hello1
#messages_en_CN.propertes
test=hello2

代碼:

//ResourceBundle.getBundle接受兩個(gè)參數(shù):basename,locale
System.out.println(ResourceBundle.getBundle("messages",new Locale("en","CN")).getString("test"));

打印結(jié)果:

hello2

以下命名的配置文件優(yōu)先級(jí)從低到高:

messages.properties
messages_en.properties
messages_en_CN.properties

通過(guò)配置不同的Locale相關(guān)的資源文件斋枢,我們可以通過(guò)key值取到相應(yīng)環(huán)境的value值帘靡,這樣便做到了國(guó)際化,這種方式是非侵入式的瓤帚,讓我們編寫(xiě)代碼時(shí)可以不需要考慮國(guó)際化的問(wèn)題描姚,只需要根據(jù)配置規(guī)則配置相關(guān)資源文件,在實(shí)際讀取資源配置時(shí)戈次,指定相應(yīng)的locale即可轩勘。這也是約定優(yōu)于配置的體現(xiàn)。

Spring國(guó)際化

spring使用MessageSource接口實(shí)現(xiàn)國(guó)際化怯邪。兩個(gè)實(shí)現(xiàn)類為:
ResourceBundleMessageSource:基于java的ResourceBundle實(shí)現(xiàn)了國(guó)際化绊寻,配置文件必須放在classpath下。
ReloadableResourceBundleMessageSource:直接使用讀取文件的方式實(shí)現(xiàn)國(guó)際化悬秉,規(guī)則跟java的相同澄步,支持動(dòng)態(tài)修改后刷新配置,避免在業(yè)務(wù)不能中斷的情況下重啟進(jìn)程搂捧。配置文件可以放在任意目錄下驮俗,指定目錄后,該類會(huì)去指定目錄中加載配置文件允跑。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.stereotype.Component;

import java.util.Locale;

@Component
public class MessageService {

    @Autowired
    private MessageSource messageSource;

    private Locale currentLocale = new Locale("en");

    @Bean
    public static MessageSource getMessageSource() {
        ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
        messageSource.setBasename("classpath:messages");
        messageSource.setCacheSeconds(5);
        return messageSource;
    }

    public String getMessage(String key) {
        return messageSource.getMessage(key, null, key, currentLocale);
    }
}

ReloadableResourceBundleMessageSource的優(yōu)點(diǎn)在于可以不重啟進(jìn)程動(dòng)態(tài)刷新配置王凑,以及指定資源目錄,不需要強(qiáng)制放在classpath下面聋丝,如果需要放在classpath中索烹,那么只需要在basename中的資源路徑上添加"classpath:",便可以在classpath中查找配置弱睦。messageSource.setCacheSeconds用于設(shè)置配置的過(guò)期時(shí)間百姓,單位為秒,messageSource.setCacheSeconds(5)代表每5秒配置文件就會(huì)過(guò)期况木,再重新查詢時(shí)垒拢,就會(huì)重新加載配置旬迹。
spring的默認(rèn)配置文件命名規(guī)則跟java是相同的,也都遵循約定優(yōu)于配置的思路求类。

Locale信息的獲取

一般來(lái)說(shuō)奔垦,典型的B/S架構(gòu),用戶可能在任何地點(diǎn)使用任何語(yǔ)言登陸網(wǎng)站尸疆,但是網(wǎng)站后臺(tái)是部署在固定的服務(wù)器或者是云服務(wù)上的椿猎,那么如何讓后端獲取客戶端(瀏覽器端)的locale信息,從而做到針對(duì)不同的客戶端進(jìn)行國(guó)際化呢寿弱?spring使用LocaleResolver解析用戶的http請(qǐng)求來(lái)獲取對(duì)應(yīng)的locale信息犯眠。下面的代碼是典型的controller,通過(guò)解析HttpServletRequest請(qǐng)求來(lái)獲取locale信息症革。

@Controller
public class HomeController extends BaseController {

  @RequestMapping(value = "", method = GET)
  public String homeDefault() {
    return homePageUrl();
  }

  @RequestMapping(value = "/locales", method = GET)
  public ResponseEntity<OpenLmisResponse> getLocales(HttpServletRequest request) {
    messageService.setCurrentLocale(RequestContextUtils.getLocale(request));
    return response("locales", messageService.getLocales());
  }

  @RequestMapping(value = "/changeLocale", method = PUT, headers = ACCEPT_JSON)
  public void changeLocale(HttpServletRequest request) {
    messageService.setCurrentLocale(RequestContextUtils.getLocale(request));
  }
}

RequestContextUtils.getLocale(request)實(shí)現(xiàn)如下筐咧。RequestContextUtils是spring提供的工具類〉鼐冢可以看出LocaleResolver從設(shè)計(jì)上支持多種策略嗜浮。

    /**
     * Retrieve the current locale from the given request, using the
     * LocaleResolver bound to the request by the DispatcherServlet
     * (if available), falling back to the request's accept-header Locale.
     * <p>This method serves as a straightforward alternative to the standard
     * Servlet {@link javax.servlet.http.HttpServletRequest#getLocale()} method,
     * falling back to the latter if no more specific locale has been found.
     * <p>Consider using {@link org.springframework.context.i18n.LocaleContextHolder#getLocale()}
     * which will normally be populated with the same Locale.
     * @param request current HTTP request
     * @return the current locale for the given request, either from the
     * LocaleResolver or from the plain request itself
     * @see #getLocaleResolver
     * @see org.springframework.context.i18n.LocaleContextHolder#getLocale()
     */
    public static Locale getLocale(HttpServletRequest request) {
        LocaleResolver localeResolver = getLocaleResolver(request);
        return (localeResolver != null ? localeResolver.resolveLocale(request) : request.getLocale());
    }

LocaleResolver包含以下四種策略實(shí)現(xiàn)羡亩。AcceptHeaderLocaleResolver摩疑,CookieLocaleResolver,F(xiàn)ixedLocaleResolver和SessionLocaleResolver畏铆。


LocaleResolver實(shí)現(xiàn).png

下面的代碼是DispatcherServlet中初始化LocaleResolver的過(guò)程雷袋,可以看到LocaleResolver實(shí)際是支持用戶進(jìn)行配置的,如果沒(méi)有配置辞居,那么使用getDefaultStrategy方法獲取默認(rèn)策略楷怒。

    /**
     * Initialize the LocaleResolver used by this class.
     * <p>If no bean is defined with the given name in the BeanFactory for this namespace,
     * we default to AcceptHeaderLocaleResolver.
     */
    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 + "]");
            }
        }
    }

查看項(xiàng)目工程代碼,發(fā)現(xiàn)項(xiàng)目中配置的是CookieLocaleResolver瓦灶。


LocaleResolver配置.png

幾種LocaleResolver的用法不同鸠删,不過(guò)從名字便可以看出一二。


accept-language.png

AcceptHeaderLocaleResolver:直接從Http請(qǐng)求的Header中通過(guò)accept-language獲取locale信息
CookieLocaleResolver:從cookie中獲取贼陶,如果獲取不到刃泡,也是通過(guò)accept-language獲取locale信息
SessionLocaleResolver:從session中獲取,如果獲取不到碉怔,也是通過(guò)accept-language獲取locale信息
FixedLocaleResolver:固定locale烘贴,基本沒(méi)啥用

至此,整個(gè)后端的國(guó)際化過(guò)程已經(jīng)比較清楚:后端通過(guò)解析客戶端(瀏覽器端)傳遞的locale信息撮胧,將locale拿出桨踪,然后再通過(guò)spring的MessageSource類傳入locale信息,取出相應(yīng)的配置文件芹啥,解析出相應(yīng)配置锻离,從而便完成了國(guó)際化铺峭。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市汽纠,隨后出現(xiàn)的幾起案子逛薇,更是在濱河造成了極大的恐慌,老刑警劉巖疏虫,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件永罚,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡卧秘,警方通過(guò)查閱死者的電腦和手機(jī)呢袱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)翅敌,“玉大人羞福,你說(shuō)我怎么就攤上這事◎卿蹋” “怎么了治专?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)遭顶。 經(jīng)常有香客問(wèn)我张峰,道長(zhǎng),這世上最難降的妖魔是什么棒旗? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任喘批,我火速辦了婚禮,結(jié)果婚禮上铣揉,老公的妹妹穿的比我還像新娘饶深。我一直安慰自己,他們只是感情好逛拱,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開(kāi)白布敌厘。 她就那樣靜靜地躺著,像睡著了一般朽合。 火紅的嫁衣襯著肌膚如雪俱两。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,046評(píng)論 1 285
  • 那天旁舰,我揣著相機(jī)與錄音锋华,去河邊找鬼。 笑死箭窜,一個(gè)胖子當(dāng)著我的面吹牛毯焕,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼纳猫,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼婆咸!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起芜辕,我...
    開(kāi)封第一講書(shū)人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤尚骄,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后侵续,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體倔丈,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年状蜗,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了需五。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡轧坎,死狀恐怖宏邮,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情缸血,我是刑警寧澤蜜氨,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站捎泻,受9級(jí)特大地震影響飒炎,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜族扰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一厌丑、第九天 我趴在偏房一處隱蔽的房頂上張望定欧。 院中可真熱鬧渔呵,春花似錦、人聲如沸砍鸠。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)爷辱。三九已至录豺,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間饭弓,已是汗流浹背双饥。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留弟断,地道東北人咏花。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親昏翰。 傳聞我的和親對(duì)象是個(gè)殘疾皇子苍匆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345