内射老阿姨1区2区3区4区_久久精品人人做人人爽电影蜜月_久久国产精品亚洲77777_99精品又大又爽又粗少妇毛片

開(kāi)發(fā)人員建議閱讀:SpringBoot架構(gòu)中的國(guó)際化支持實(shí)踐

pring Boot 主要通過(guò) Maven 或 Gradle 這樣的構(gòu)建系統(tǒng)以繼承方式添加依賴,同時(shí)繼承了 Spring 框架中的優(yōu)秀元素,減少了 Spring MVC 架構(gòu)中的復(fù)雜配置,內(nèi)置 Tomcat,Jetty 容器,使用 Java application 運(yùn)行程序,而不是傳統(tǒng)地把 WAR 包置于 Tomcat 等容器中運(yùn)行,從而簡(jiǎn)化加速開(kāi)發(fā)流程。此外,Spring Boot 學(xué)習(xí)簡(jiǎn)單、輕量級(jí)、容易擴(kuò)展?;谶@些優(yōu)秀的特點(diǎn),Spring Boot 成為了蓬勃發(fā)展的快速應(yīng)用開(kāi)發(fā)領(lǐng)域的領(lǐng)導(dǎo)者。

天全網(wǎng)站建設(shè)公司成都創(chuàng)新互聯(lián)公司,天全網(wǎng)站設(shè)計(jì)制作,有大型網(wǎng)站制作公司豐富經(jīng)驗(yàn)。已為天全上千家提供企業(yè)網(wǎng)站建設(shè)服務(wù)。企業(yè)網(wǎng)站搭建\外貿(mào)營(yíng)銷網(wǎng)站建設(shè)要多少錢(qián),請(qǐng)找那個(gè)售后服務(wù)好的天全做網(wǎng)站的公司定做!

在互聯(lián)網(wǎng)日益發(fā)展的當(dāng)今時(shí)代,一個(gè)應(yīng)用程序需要在全球范圍內(nèi)使用勢(shì)在必然。傳統(tǒng)的程序設(shè)計(jì)方法將可翻譯信息如菜單按鈕的標(biāo)簽、提示信息、幫助文檔等文字信息硬編碼在程序代碼中,但這些已經(jīng)不能很好的適應(yīng)全球化發(fā)展,而且程序的擴(kuò)展性差,維護(hù)成本高。一個(gè)能支持全球化的應(yīng)用程序,必須實(shí)現(xiàn)單一可執(zhí)行的程序,動(dòng)態(tài)地使用資源(Single Source Single Executable)。

對(duì)于一個(gè)能支持全球化的應(yīng)用程序來(lái)說(shuō),需要考慮下面三方面的設(shè)計(jì),如圖 1 所示。

圖 1. 多語(yǔ)言應(yīng)用程序模型
開(kāi)發(fā)人員建議閱讀:Spring Boot 架構(gòu)中的國(guó)際化支持實(shí)踐
開(kāi)發(fā)人員建議閱讀:Spring Boot 架構(gòu)中的國(guó)際化支持實(shí)踐

區(qū)域模型的定制化(Locale Model):Locale 模型是一個(gè)多語(yǔ)言應(yīng)用程序的基礎(chǔ),用來(lái)確定界面語(yǔ)言以及日期時(shí)間等的格式化方式,通常包括語(yǔ)言環(huán)境(Language Locale)和文化環(huán)境(Cultural Locale)。一個(gè)應(yīng)用程序的 Locale 獲取有下面幾種常見(jiàn)的方式:Locale.getDefault(); 一個(gè)應(yīng)用程序具體選擇哪種方式獲取區(qū)域信息(Locale),這需要取決于該應(yīng)用程序的用戶需求。
資源文件的外部化: 這里主要指界面語(yǔ)言,根據(jù)用戶選擇的 Locale 模型,自動(dòng)地顯示與之對(duì)應(yīng)的界面語(yǔ)言,讓客戶感覺(jué)這個(gè)產(chǎn)品是為他們而設(shè)計(jì)的。
日期時(shí)間等多元文化的支持: 包括貨幣、日歷、時(shí)間、日期、排序、界面方向性(Bi-directional) 等符合各個(gè)國(guó)家自己習(xí)慣的顯示方式。

下面主要從上述三方面分別介紹基于 Spring Boot 應(yīng)用程序如何支持多語(yǔ)言的,包括 RESTful 消息和內(nèi)容的國(guó)際化支持。

Spring Boot 中的區(qū)域模型介紹
在自定義應(yīng)用程序區(qū)域模型(Locale)之前,需要了解一下 Spring Boot 中區(qū)域解析器的原理?;?Spring 框架的應(yīng)用程序中,用戶的區(qū)域信息是通過(guò)區(qū)域解析器 LocaleResolver 來(lái)識(shí)別的,LocaleResolver 是 Spring 框架基于 Web 提供的區(qū)域解析器接口,允許通過(guò) HTTP 請(qǐng)求和響應(yīng)修改區(qū)域設(shè)置,如清單 1 所示。

清單 1. Spring 框架中的區(qū)域解析器

public interface LocaleResolver {
    Locale resolveLocale(HttpServletRequest request);
    void setLocale(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Locale locale);
}

LocaleResolver 在 Spring 框架中有四種具體的實(shí)現(xiàn):

按 HTTP 請(qǐng)求頭部解析區(qū)域(AcceptHeaderLocaleResolver): Spring 采用的默認(rèn)區(qū)域解析器就是基于請(qǐng)求頭部,它通過(guò)檢驗(yàn) HTTP 請(qǐng)求的 accept-language 頭部來(lái)解析區(qū)域,這個(gè)頭部是由用戶的 Web 瀏覽器設(shè)定決定的。
按會(huì)話屬性解析區(qū)域(SessionLocaleResolver): 它通過(guò)檢驗(yàn)用戶會(huì)話中預(yù)置的屬性來(lái)解析區(qū)域。如果該會(huì)話屬性不存在,它會(huì)根據(jù) accept-language HTTP 頭部確定默認(rèn)區(qū)域。
按 Cookie 解析區(qū)域(CookieLocaleResolver): 如果 Cookie 存在,它會(huì)根據(jù) accept-languageHTTP 頭部確定默認(rèn)區(qū)域。
FixedLocaleResolver: 此解析器始終返回固定的默認(rèn)區(qū)域設(shè)置,通常取自 JVM 的默認(rèn)區(qū)域設(shè)置。
除了 Spring 框架中提供的四種實(shí)現(xiàn)外,還可以創(chuàng)建自定義的區(qū)域解析器。在 Spring Boot 自動(dòng)配置中可以看到清單 2 的代碼。

清單 2. 自定義 Spring 框架中的區(qū)域解析器

//向容器中加入了 LocaleResolver 對(duì)象
    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(
        prefix = "spring.mvc",
        name = {"locale"}
    )
    public LocaleResolver localeResolver() {
        if (this.mvcProperties.getLocaleResolver() == org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties.LocaleResolver.FIXED) {
            return new FixedLocaleResolver(this.mvcProperties.getLocale());
     } else {
            AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
            localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
            return localeResolver;
        }
當(dāng)我們的應(yīng)用程序需要自定義區(qū)域解析器(LocaleResovler)的時(shí)候,可以通過(guò)下面幾個(gè)步驟實(shí)現(xiàn)。

第一步:自定義區(qū)域解析器
自定義區(qū)域解析器是對(duì) Spring 中 LocaleResolver 接口的實(shí)現(xiàn),可以基于應(yīng)用程序的實(shí)際需求,取自于用戶自定義的語(yǔ)言選擇界面、用戶操作系統(tǒng)或者瀏覽器的語(yǔ)言設(shè)定。清單 3 是一個(gè)示例,首先判斷用戶請(qǐng)求中是否含有 lang 這個(gè)參數(shù),如果有,就使用這個(gè)參數(shù)所帶的區(qū)域信息;如果沒(méi)有,就取自瀏覽器請(qǐng)求頭部中的 accept-language 信息。

清單 3. 自定義區(qū)域解析器

public class CustomLocaleResolver implements LocaleResolver{

@Override
public Locale resolveLocale(HttpServletRequest request) {
        String paramLanguage = request.getParameter("lang");
        if(!StringUtils.isEmpty(paramLanguage)){
            String[] splits = paramLanguage.split("-");
            return new Locale(splits[0], splits[1]);
        }else{
            String acceptLanguage = request.getHeader("Accept-Language").split(",")[0];
            String[] splits = acceptLanguage.split("-");
            return new Locale(splits[0], splits[1]);
        }
        // 如果想使用當(dāng)前系統(tǒng)的語(yǔ)言,則使用Locale.getDefault()
   } 

@Override
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
    }
}

第二步: 將自定義的區(qū)域解析器添加到 IOC 容器中
通常添加在自定義的 config 文件中,下面的例子將自定義的 CustomLocaleResolver 通過(guò) @Bean

注添加到 IOC 容器,如清單 4 所示。

清單 4. 自定義區(qū)域解析器添加到 IOC

@Configuration
public class CustomMvcConfigure extends WebMvcConfigurationSupport {

@Bean
public LocaleResolver localeResolver(){
return new CustomLocaleResolver();
}
}

如此,在程序中就可以調(diào)用我們自定義的區(qū)域解析器。

Thymeleaf 模板引擎對(duì)多語(yǔ)言的支持
Thymeleaf 是一個(gè)基于 Apache License 2.0 許可,支持 XML、XHTML、HTML5 的開(kāi)源模板引擎,主要用于 Web 或者非 Web 環(huán)境中的應(yīng)用開(kāi)發(fā),在有網(wǎng)絡(luò)和無(wú)網(wǎng)絡(luò)的環(huán)境下皆可運(yùn)行,它既可以在瀏覽器端查看靜態(tài)頁(yè)面,也可以顯示動(dòng)態(tài)頁(yè)面。這是由于它支持 HTML 原型,然后在 HTML 標(biāo)簽里增加額外的屬性來(lái)達(dá)到模板+ 數(shù)據(jù)的展示方式。瀏覽器解析 HTML 時(shí)會(huì)忽略未定義的標(biāo)簽屬性,所以 Thymeleaf 模板可以靜態(tài)地運(yùn)行;當(dāng)有數(shù)據(jù)返回到頁(yè)面時(shí),Thymeleaf 標(biāo)簽會(huì)動(dòng)態(tài)地替換掉靜態(tài)內(nèi)容,使頁(yè)面動(dòng)態(tài)顯示。

在 Spring MVC 框架中,通常我們用 JSP 來(lái)展示 Web 前端,JSP 本質(zhì)上也是模板引擎,然而 Spring Boot 官方推薦使用 Thymeleaf 模板引擎,Thymeleaf 完全可以替代 JSP 或者其他模板引擎如 Velocity、FreeMarker 等。雖然 Spring 官方推薦使用 Thymeleaf,但是并不是說(shuō) Spring Boot 不支持 JSP。

在 Spring Boot 項(xiàng)目中使用 Thymeleaf 模板支持多語(yǔ)言的步驟如下:

第一步: 封裝用戶語(yǔ)言環(huán)境
在我們的實(shí)驗(yàn)中,設(shè)計(jì)一個(gè)簡(jiǎn)單的登錄頁(yè)面,登錄頁(yè)面有個(gè)語(yǔ)言選擇下拉列表,將使用用戶選的語(yǔ)言來(lái)顯示登錄頁(yè)面上的標(biāo)簽,如圖 2 所示。

圖 2. 資源文件組織結(jié)構(gòu)
開(kāi)發(fā)人員建議閱讀:Spring Boot 架構(gòu)中的國(guó)際化支持實(shí)踐

開(kāi)發(fā)人員建議閱讀:Spring Boot 架構(gòu)中的國(guó)際化支持實(shí)踐

為了使我們的程序能夠使用指定的 Locale,首先需要添加 LocaleResolver Bean。在我們的示例中,使用了用戶選擇的 Locale,所以需要配置 LocaleChangeInterceptor 攔截器,主要用來(lái)檢查傳入的請(qǐng)求,如果請(qǐng)求中有 lang 的參數(shù)(可配置),如 http://localhost:8089/login?lang=zh_CN ,那么該 Interceptor 將使用 localResolver 改變當(dāng)前用戶的默認(rèn) Locale。清單 5 為示例代碼,根據(jù)用戶選擇的語(yǔ)言顯示對(duì)應(yīng)的界面。

清單 5. 自定義 LocaleResolver

@Configuration
@ComponentScan(basePackages = "com.example.config")
public class MvcConfig implements WebMvcConfigurer {
     @Bean
        public LocaleResolver localeResolver() {
            SessionLocaleResolver slr = new SessionLocaleResolver();
            slr.setDefaultLocale(Locale.US);
            return slr;
        }

        @Bean
        public LocaleChangeInterceptor localeChangeInterceptor() {
            LocaleChangeInterceptor lci = new LocaleChangeInterceptor();
            lci.setParamName("lang");
            return lci;
        }

        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(localeChangeInterceptor());
        }
}

第二步: 定義多語(yǔ)言資源文件
默認(rèn)情況下,資源文件是直接放在 src/main/resource 目錄下,為了實(shí)現(xiàn)代碼的結(jié)構(gòu)化,我們?cè)?resource 目錄下創(chuàng)建 i18n 文件夾,然后在 i18n 目錄下創(chuàng)建資源文件(當(dāng)然也可以在 i18n目錄下創(chuàng)建不同的子目錄,在子目錄下再創(chuàng)建資源文件),這種情況下,我們需要在配置文件 application.properties 中重新指定資源文件的 basename:spring.messages.basename 。資源文件名可以根據(jù)自己的項(xiàng)目定義,但是通常的規(guī)范是:模塊名_語(yǔ)言_國(guó)家 .properties ,在本實(shí)例中我們命名為如 log_zh_CN.properties ,資源文件的目錄結(jié)構(gòu)如圖 3 所示。

圖 3. 資源文件組織結(jié)構(gòu)
開(kāi)發(fā)人員建議閱讀:Spring Boot 架構(gòu)中的國(guó)際化支持實(shí)踐

對(duì)應(yīng)資源文件中的每一個(gè) property key ,一般都是小寫(xiě)字母開(kāi)頭,用下劃線表示這個(gè) key 在程序中的層級(jí)結(jié)構(gòu),并且按照字母順序排序,便于管理和查找,如清單 6 所示。

清單 6. 英文資源文件示例

login_home=Home
login_login=Log In
login_login_page=Login page
login_languge_selector=Language Selector
login_please_select_language_to_display=Please select the language to display
login_password=Password
login_username=Username

第三步:在 Thymeleaf 模板引擎中調(diào)用多語(yǔ)言資源
在 Spring Boot 架構(gòu)中使用 Thymeleaf 模板,首先需要在 pom.xml 中引入 Thymeleaf 模板依賴 pring-boot-starter-thymeleaf ,如清單 7 所示。

清單 7. pom.xml 引入依賴

<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-thymeleaf</artifactId>
 </dependency>

此外,還需要在 application.properties 中配置 Thymeleaf 視圖解析器,如清單 8 所示。

清單 8. 配置 Thymeleaf

#============ thymeleaf ====================================
spring.thymeleaf.mode=HTML
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.servlet.content-type=text/html
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.cache=false

根據(jù)業(yè)務(wù)需求編寫(xiě) Thymeleaf 模板 HTML 文件,默認(rèn)的模板映射路徑是: src/main/resources/templates ,也可以在 application.properties 中配置自定義的模板路徑 spring.thymeleaf.prefix=classpath:/templates/myTemplates/ 。

在 Thymeleaf 模板文件中,將所有的硬編碼字符串抽取出來(lái)放到資源文件中,然后用資源文件中的鍵值 #{Property Key} 來(lái)表示界面顯示的標(biāo)簽信息:

普通的標(biāo)簽字符串:用 th:text= "#{login_login}" 表示;
作為賦值的字符串,如 value="Login" ,表示成 th:value="#{login_login}" ;如 placeholder="Login" ,表示成 th:placeholder="#{login_login}"
清單 9 是一個(gè)簡(jiǎn)單登錄頁(yè)面的 Thymeleaf 模板內(nèi)容(只列出了部分關(guān)鍵代碼),其中所有顯示的標(biāo)簽信息都抽取到資源文件中,如 th:text="#(login_login_page)" ,從而根據(jù)用戶選擇的語(yǔ)言自動(dòng)讀取對(duì)應(yīng)的資源文件中的字符串來(lái)顯示。

清單 9. 編寫(xiě) Thymeleaf 模板文件

<body>
<div layout:fragment="content" th:remove="tag">
 <div class="row">
    <div class="col-md-6 col-md-offset-3">
       <h2 th:text="#{login_login_page}"></h2>
          <form th:action="@{/login}" method="post">
          <div class="form-group">
          <label for="username" th:text="#{login_username}"></label>:
          <input type="text" id="username" name="username" class="form-control" autofocus="autofocus" th:placeholder="#{login_username}">
          </div><div class="form-group">
           <label for="password" th:text="#{login_password}"></label>:
           <input type="password" id="password" name="password"
               class="form-control" th:placeholder="#{login_password}">
           </div> <div class="form-group">
           <div class="row">
              <div class="col-sm-6 col-sm-offset-3">
              <input type="submit" name="login-submit" id="login-submit"
               class="form-control btn btn-info" th:value="#{login_login}">
            </div></div></div>
           </form></div></div>

        <div class="row"><div class="col-md-6 col-md-offset-3">
          <div class="form-group">
           <label th:text="#{login_languge_selector}"></label>
            <select class="form-control" id ="locales">
   <option  value="" th:text="#{login_please_select_language_to_display}"></option>
   <option th:selected="${displayLang=='en_US'}" value="en_US">English</option>
     <option th:selected="${displayLang=='zh_CN'}" value="zh_CN">簡(jiǎn)體中文</option>
     <option th:selected="${displayLang=='ja_JP'}" value="ja_JP">日本語(yǔ)</option>
     <option th:selected="${displayLang=='zh_TW'}" value="zh_TW">繁體中文</option>
  </select>
    </div></div><div></div></div></div>
</body>

在實(shí)驗(yàn)中遇到一個(gè)問(wèn)題,界面字符串全部顯示為 ??Properties_Key_語(yǔ)言?? ,如圖 4 所示。

圖 4. 不能正確讀取 properties 文件
開(kāi)發(fā)人員建議閱讀:Spring Boot 架構(gòu)中的國(guó)際化支持實(shí)踐

經(jīng)過(guò)一步步排查,原因是在配置文件 application.properties 錯(cuò)誤配置了資源文件的路徑如圖 5 所示。正確的路徑為 spring.messages.basename=i18n/login 。
圖 5. spring.messages.basename 路徑不正確
開(kāi)發(fā)人員建議閱讀:Spring Boot 架構(gòu)中的國(guó)際化支持實(shí)踐

Spring Boot 中時(shí)間日期格式化
Java 8 提供了更便捷的日期時(shí)間 API 如 LocalDate、LocalTime 和 LocalDateTime,Spring Boot 架構(gòu)中推薦使用 Java 8 中新的時(shí)間日期 API。LocalDate 較 Date 類的優(yōu)點(diǎn)體現(xiàn)在以下幾個(gè)方面:

Date 類打印出來(lái)的日期可讀性差,通常需要使用 SimpleDateFormat 進(jìn)行格式化,而 LocalDate 默認(rèn)的格式為 YYYY-MM-DD 。
LocalDate 較 Date 在日期計(jì)算及格式化方面更簡(jiǎn)單易用。
Date 類同時(shí)包括了日期和時(shí)間,而 LocalDate、LocalTime 和 LocalDateTime 分別表示日期、時(shí)間、日期和時(shí)間,使用起來(lái)非常靈活。
Date 類不支持多線程安全,LocalDate 等新的接口是線程安全的。
對(duì)于后臺(tái) Java 程序中的時(shí)間日期格式化問(wèn)題,通常會(huì)重新定制化時(shí)間日期格式化接口,比如將 Locale 參數(shù)、想要顯示的樣式參數(shù)傳入進(jìn)去。清單 10 是格式化時(shí)間日期的接口示例代碼,清單 11 是具體實(shí)現(xiàn)的示例代碼,這里只列舉了幾種典型的格式化情況。

清單 10. 時(shí)間日期格式化接口

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Locale;
public interface I18nFormatter {
    String formaFullDateTime(LocalDateTime date, Locale locale);
    String formatFullDate(LocalDate originalDate, Locale locale);

    String formatMediumDateTime(LocalDateTime date, Locale locale);
    String formatMediumDate(LocalDate originalDate, Locale locale);
    String formatMediumDateShortTime(LocalDateTime date, Locale locale);
    String formatMediumTime(LocalTime originalDate, Locale locale);

    String formatShortDateTime(LocalDateTime originalDate, Locale locale);
    String formatShortDate(LocalDate originalDate, Locale locale);
    String formatShortTime(LocalTime originalDate, Locale locale);
    String formatShortDateMediumTime(LocalDateTime originalDate, Locale locale);

清單 11. 時(shí)間日期格式化實(shí)現(xiàn)

@Service("I18nFormatter")
public class I18nFormatterImpl implements I18nFormatter { 

@Override
public String formatFullDate(LocalDate originalDate, Locale locale) {
    DateTimeFormatter dateFormat =DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL).withLocale(locale);
    return dateFormat.format(originalDate);
}

@Override
public String formatMediumDateTime(LocalDateTime date, Locale locale) {
    DateTimeFormatter dateFormat = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.MEDIUM).withLocale(locale);
    return dateFormat.format(date);
}

@Override
public String formatShortTime(LocalTime originalDate, Locale locale) {
    DateTimeFormatter dateFormat = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT).withLocale(locale);
    return dateFormat.format(originalDate);
}
}

在 Spring Boot 架構(gòu)中接口與接口之間、前后端之間都使用 JSON 格式傳輸數(shù)據(jù)。對(duì)于日期格式的數(shù)據(jù),如果采用默認(rèn)方式不做處理,易讀性和可用性不是很好。如清單 12 就是默認(rèn) JSON 格式的日期。請(qǐng)求得到的 JSON 格式日期可讀性很差,如圖 6 所示。

清單 12. 默認(rèn)的時(shí)間日期

@RestController
public class LocalDateTimeController {

    @GetMapping("/time")
    public DateTime timeMapping() {
        return new DateTime();
    }

public class DateTime {

    protected LocalDate localDate;
    protected LocalDateTime localDateTime;
    protected LocalTime localTime;

    public DateTime() {
        localDate = LocalDate.now();
        localDateTime = LocalDateTime.now();
        localTime = LocalTime.now();
    }

    public LocalDate getLocalDate() {
        return localDate;
    }

    public LocalDateTime getLocalDateTime() {
        return localDateTime;
    }

    public LocalTime getLocalTime() {
        return localTime;
    }
}

圖 6. 默認(rèn)格式返回的 JSON 格式日期
開(kāi)發(fā)人員建議閱讀:Spring Boot 架構(gòu)中的國(guó)際化支持實(shí)踐

開(kāi)發(fā)人員建議閱讀:Spring Boot 架構(gòu)中的國(guó)際化支持實(shí)踐

下面我們采用 @JsonFormat 序列化屬性值,如清單 13 所示。

清單 13. 使用 @JsonFormat 進(jìn)行標(biāo)注

public class JsonDateTime extends DateTime {

    @Override
    @JsonFormat(pattern="yyyy-MM-dd")
    public LocalDate getLocalDate() {
        return super.localDate;
    }

    @Override
    @JsonFormat(pattern="yyyy-MM-dd HH:mm")
    public LocalDateTime getLocalDateTime() {
        return super.localDateTime;
    }

    @Override
    @JsonFormat(pattern="HH:mm")
    public LocalTime getLocalTime() {
        return localTime;
    }

圖 7. @JsonFormat 格式化后的 Json 格式日期
開(kāi)發(fā)人員建議閱讀:Spring Boot 架構(gòu)中的國(guó)際化支持實(shí)踐

開(kāi)發(fā)人員建議閱讀:Spring Boot 架構(gòu)中的國(guó)際化支持實(shí)踐

清單 13 我們只是對(duì) JSON 格式的日期進(jìn)行了格式化,但是還沒(méi)有實(shí)現(xiàn)多語(yǔ)言化。我們采用中定義的多語(yǔ)言格式化方法對(duì) JSON 格式日期進(jìn)行格式化,將日期參數(shù)定義為 String 類型。如清單 14 所示。

清單 14. 使用多語(yǔ)言格式化方法

@RestController
public class LocalDateTimeController{

    @GetMapping("/g11nDateTime")
    public G11nDateTime g11nDateTimeMapping(@RequestParam(value = "language") String language) {
        Locale locale = new Locale("en", "US");
        if(!StringUtils.isEmpty(language)){
                String[] splits = language.split("_");
                locale = new Locale(splits[0], splits[1]);
        }
        return new G11nDateTime(locale);

    }

    public class G11nDateTime {

     protected String localDate;
     protected String localDateTime;
     protected String localTime;

     public G11nDateTime(Locale locale) {
        I18nFormatterImpl formatter = new I18nFormatterImpl();

        localDate = formatter.formatFullDate(LocalDate.now(), locale);
        localDateTime = formatter.formatMediumDateShortTime(LocalDateTime.now(), locale);
        localTime = formatter.formatShortTime(LocalTime.now(), locale);
    }

     public String getLocalDate() {
        return localDate;
    }

    public String getLocalDateTime() {
        return localDateTime;
    }

    public String getLocalTime() {
        return localTime;
    }
}

當(dāng)傳入的語(yǔ)言參數(shù)為 zh_CN 時(shí),響應(yīng)的日期如圖 8 所示。

圖 8. 多語(yǔ)言的 JSON 格式日期

![](/upload/otherpic63/7d761433b542fe06d738ed20afca0f1c.png)

![](/upload/otherpic63/6803c9267251f0d74eb3fe9e7a85325e.png)

Spring Boot RESTful API 多語(yǔ)言支持
隨著 Spring Boot 的快速發(fā)展,基于 RESTful

準(zhǔn)的微服務(wù)接口應(yīng)用也越來(lái)越廣泛,RESTful API 使用 HTTP 協(xié)議來(lái)表示創(chuàng)建、檢索、更新和刪除 (CRUD) 操作。下面主要介紹在 Spring Boot 框架下,如何實(shí)現(xiàn)服務(wù)器端 RESTful API 的多語(yǔ)言支持,主要涉及到返回的內(nèi)容和消息。通常有以下幾方面需要考慮,如圖 9 所示。

圖 9. RESTful API 多語(yǔ)言支持
![](/upload/otherpic63/5667612a78e149c12e22ec39ddc10d17.png)

第一步: 封裝和自定義資源文件讀取工具
現(xiàn)行的開(kāi)發(fā)框架大部分都會(huì)提供接口讀取資源文件。涉及到資源文件讀取時(shí),ResourceBundle 是一個(gè)機(jī)制,主要用來(lái)根據(jù)不同的用戶區(qū)域信息自動(dòng)地讀取對(duì)應(yīng)的資源文件,ResourceBundle 是 Java 中的資源文件讀取接口,圖 10 總結(jié)了 Java 程序中 ResourceBundle 的管理機(jī)制。

圖 10. Java 程序 Resource Bundle 管理流程
![](/upload/otherpic63/ab799248216ba780e4e02adfc4a24ee4.png)

![](/upload/otherpic63/dde4747c6786d3fa378a50db48af5e34.png)

Spring 定義了訪問(wèn)資源文件的 MessageSource 接口,該接口有幾個(gè)重要的方法用來(lái)讀取資源文件,如表 1 所示。

表 1. MessageSource 接口說(shuō)明
方法名說(shuō)明String getMessage(String code, Object[] args, String defaultMessage, Locale locale)code :表示資源文件中的 Property Key 。 
args :用于傳遞占位符所代表的運(yùn)行期參數(shù)。 
defaultMessage :當(dāng)在資源找不到對(duì)應(yīng)屬性名時(shí),返回參數(shù)所指定的默認(rèn)值。 
locale :表示區(qū)域信息。String getMessage(String code, Object[] args, Locale locale)throws NoSuchMessageException找不到資源中對(duì)應(yīng)的屬性名時(shí),直接拋出 NoSuchMessageException 異常。String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException將屬性名、參數(shù)數(shù)組以及默認(rèn)信息封裝起來(lái),它的功能和第一個(gè)接口方法相同。

MessageSource 被 HierarchicalMessageSource 和 ApplicationContext 兩個(gè)接口繼承,如圖 11 所示。

圖 11. MessageSource 類圖
![](/upload/otherpic63/6489277d25139d1fde60945cc064f4a2.png)

![](/upload/otherpic63/3a0eaba17ee25ee3ccf6e4477a060fff.png)

ResourceBundleMessageSource 和 ReloadableResourceBundleMessageSource 是在 Java ResourceBundle 基礎(chǔ)上 HierarchicalMessageSource 的兩個(gè)具體實(shí)現(xiàn)類。他們與 ResourceBundle 的區(qū)別是不需要分別加載不同語(yǔ)言的資源文件,通過(guò)資源文件名 (baseName) 就可以加載整套的國(guó)際化資源文件;同時(shí)不再需要顯示的調(diào)用 MessageFormat 方法,使用起來(lái)更加簡(jiǎn)單便捷;還可以讀取 XML 格式的資源文件。

ReloadableResourceBundleMessageSource 可以定時(shí)刷新資源文件,以便在應(yīng)用程序不重啟的情況下感知資源文件的更新,還可以設(shè)置讀取資源文件的編碼方式。 cacheSeconds 屬性讓 ReloadableResourceBundleMessageSource 根據(jù)設(shè)定的時(shí)間刷新監(jiān)測(cè)資源文件是否有更新,但是刷新周期不能太短,否則影響應(yīng)用程序的性能。如果 cacheSeconds 設(shè)置 -1,表示永遠(yuǎn)不刷新,這個(gè)時(shí)候 ReloadableResourceBundleMessageSource 和 ResourceBundleMessageSource 功能等同。

清單 15 在 ReloadableResourceBundleMessageSource 類的基礎(chǔ)上,自定義資源文件讀取接口,讀取自定路徑的資源文件。

清單 15. 自定義資源文件讀取接口

public class CustomizeMessageResource {

private final static Logger logger = LoggerFactory.getLogger(CustomizeMessageResource.class);
private static MessageSourceAccessor accessor;
private static final String PATH_PARENT = "classpath:i18n/";
private static final String SUFFIX = ".properties";

public CustomizeMessageResource() {}

private void initMessageSourceAccessor() throws IOException{
    logger.info("init initMessageSourceAccessor...");

    ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
    Resource resource = resourcePatternResolver.getResource(PATH_PARENT + "message" + SUFFIX);
    String fileName = resource.getURL().toString();
    int lastIndex = fileName.lastIndexOf(".");
    String baseName = fileName.substring(0,lastIndex);

    ReloadableResourceBundleMessageSource reloadableResourceBundleMessageSource = new ReloadableResourceBundleMessageSource();

reloadableResourceBundleMessageSource.setBasename(baseName);
reloadableResourceBundleMessageSource.setCacheSeconds(1800);
reloadableResourceBundleMessageSource.setDefaultEncoding("UTF-8");
accessor = new MessageSourceAccessor(reloadableResourceBundleMessageSource);
}

public String getMessage(String key, String lang) throws IOException {
    initMessageSourceAccessor();
    Locale locale = new Locale("en", "US");
     if (!lang.isEmpty()) {
           locale = new Locale(lang.split("_")[0], lang.split("_")[1]);
     }
     return accessor.getMessage(key, null, "No such Property key", locale);
  }

public String getMessage(String key, String lang, Object... parameters) throws IOException {
    initMessageSourceAccessor();
    Locale locale = new Locale("en", "US");
    if (!lang.isEmpty()) {
        locale = new Locale(lang.split("_")[0], lang.split("_")[1]);
    }
    return accessor.getMessage(key, parameters, "No such Property key", locale);
}

第二步: 設(shè)計(jì)多語(yǔ)言的 API 返回狀態(tài)碼
RESTful API 都會(huì)有返回狀態(tài)碼,為了支持多語(yǔ)言,在設(shè)計(jì)返回狀態(tài)碼接口的時(shí)候也需要考慮多語(yǔ)言的支持,下面以上傳文件為例來(lái)說(shuō)明如何設(shè)計(jì)反饋狀態(tài)碼以及返回接口的封裝。在設(shè)計(jì)返回狀態(tài)碼的時(shí)候,涉及到顯示的消息內(nèi)容,這里用資源文件里的 Property Key 來(lái)表示,這樣在返回狀態(tài)封裝類中比較容易動(dòng)態(tài)地讀取不同語(yǔ)言的返回消息,如清單 16 所示。

清單 16. 多語(yǔ)言 API 返回狀態(tài)碼

public enum G11nUploadCode {

OK(200, "OK", "api_upload_response_ok"),
ERROR(400, "WRONG REQUEST", "api_upload_response_wrong_request"),
CREATED(201, "CREATED", "api_upload_response_create"),
UNAUTHORIZED(401, "UNAUTHORIZED", "api_upload_response_unauthorized"),
FORBIDDEN(403, "FORBIDDEN", "api_upload_response_forbidden"),
NOT_FOUND(404, "NOT FOUND", "api_upload_response_not_found");

private int code;
private String status;
private String propertyKey;

private G11nUploadCode(int code, String status, String propertyKey) {
    this.code = code;
    this.status= status;
    this.propertyKey = propertyKey;
}

public void seCode(int code) {
    this.code = code;
}

public int getCode() {
    return this.code;
}

public String getStatus() {
    return this.status;
}

public void seStatus(String status) {
    this.status = status;
}

public void setPropertyKey(String propertyKey) {
    this.propertyKey = propertyKey;
}

public String getPropertyKey() {
    return this.propertyKey;
}

}


第三步: 多語(yǔ)言 API 返回接口封裝
利用第一步中自定義的資源文件讀取工具,動(dòng)態(tài)的讀取多余的返回信息,如清單 17 所示。

清單 17. 多語(yǔ)言 API 返回狀態(tài)碼

public class G11nUploadResult implements Serializable {

private static final long serialVersionUID = 1L;
private int code;
private String status;
private Object data;

public void setCode(int code) {
this.code = code;
}

public int getCode() {
return this.code;
}

public void setStatus(String status) {
this.status = status;
}

public String getStatus() {
return this.status;
}

public void setData(Object data) {
this.data = data;
}

public Object getData() {
return this.data;
}

public G11nUploadResult() {}

public G11nUploadResult(int code, String status, Object data) {
this.code = code;
this.status = status;
this.data = data;
}

public G11nUploadResult(G11nUploadCode responseCodeI18n, String language) throws IOException{
CustomizeMessageResource customizeMessageResource = new CustomizeMessageResource();

this.code = responseCodeI18n.getCode();
this.status = responseCodeI18n.getStatus();
System.out.println("Status: " + this.status);
this.data = customizeMessageResource.getMessage(responseCodeI18n.getPropertyKey(), language);

}
}


第四步: 在控制器中調(diào)用多語(yǔ)言的返回碼
本步操作如清單 18 所示。

清單 18. 控制器中調(diào)用多語(yǔ)言返回碼

@RestController@Api(value="uploadFiles")
br/>@Api(value="uploadFiles")

private final Logger logger = LoggerFactory.getLogger(UploadFilesController.class);

private static String UPLOADED_FOLDER = "/users/tester/upload/";

@PostMapping("/uploadfiles")
public G11nUploadResult uploadFile(@RequestParam("file") MultipartFile uploadfile) throws IOException {
logger.debug("Single file uploa!");

G11nUploadResult result = new G11nUploadResult();
    CustomizeMessageResource customizeMessageResource = new CustomizeMessageResource();

   if (uploadfile.isEmpty()) {
    return new G11nUploadResult(G11nUploadCode.ERROR, "zh_CN");     
}

try {
    saveUploadedFiles(Arrays.asList(uploadfile));
} catch (IOException e) {
    return new G11nUploadResult(G11nUploadCode.NOT_FOUND, "zh_CN");
}
logger.info("Successfully uploaded - " + uploadfile.getOriginalFilename());
result.setStatus("OK");
result.setData(customizeMessageResource.getMessage("success_upload", "zh_CN", uploadfile.getOriginalFilename()));
return result;
}

}


圖 12 是測(cè)試上傳文件的結(jié)果,在調(diào)用 API 的時(shí)候,傳遞的參數(shù)是簡(jiǎn)體中文 (zh_CN) ,返回的狀態(tài)和信息顯示為中文信息,使用開(kāi)發(fā)者熟悉的語(yǔ)言顯示返回信息便于讀取查看。

圖 12. MessageSource 類圖
![](/upload/otherpic63/205be3ce4deb627e83ef61994fdda7fd.png)

![](/upload/otherpic63/7f5b7813cbb8a31850b2c436a3085edf.png)

結(jié)束語(yǔ)
本文總結(jié)了在 Spring Boot 框架下,如何開(kāi)發(fā)一個(gè)多語(yǔ)言的應(yīng)用程序及 RESTful API。闡述了 Spring Boot 架構(gòu)下區(qū)域模型的原理,基于已有的區(qū)域模型定制化應(yīng)用程序自己的區(qū)域模型;Thymeleaf 模板引擎對(duì)多語(yǔ)言的支持;Spring Boot 中時(shí)間日期格式化;以及 Spring Boot RESTful API 多語(yǔ)言支持實(shí)踐。希望這篇文章能為正在開(kāi)發(fā)國(guó)際化應(yīng)用程序和微服務(wù)的您提供一定的參考。

當(dāng)前名稱:開(kāi)發(fā)人員建議閱讀:SpringBoot架構(gòu)中的國(guó)際化支持實(shí)踐
URL分享:http://m.rwnh.cn/article24/jipsje.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站排名、ChatGPT、手機(jī)網(wǎng)站建設(shè)商城網(wǎng)站、自適應(yīng)網(wǎng)站、網(wǎng)站制作

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

搜索引擎優(yōu)化
桑植县| 锡林郭勒盟| 九龙县| 德庆县| 靖边县| 改则县| 巍山| 双柏县| 东兴市| 双柏县| 沂水县| 崇信县| 会同县| 英吉沙县| 屏东市| 巍山| 广西| 平罗县| 神农架林区| 大埔县| 施甸县| 通江县| 道真| 吉安县| 南昌市| 嵊泗县| 长寿区| 青岛市| 舟山市| 察雅县| 桑植县| 万荣县| 涪陵区| 定襄县| 漳平市| 邯郸市| 天柱县| 蓬安县| 精河县| 同江市| 南开区|