您好,登錄后才能下訂單哦!
pring Boot 主要通過 Maven 或 Gradle 這樣的構建系統以繼承方式添加依賴,同時繼承了 Spring 框架中的優秀元素,減少了 Spring MVC 架構中的復雜配置,內置 Tomcat,Jetty 容器,使用 Java application 運行程序,而不是傳統地把 WAR 包置于 Tomcat 等容器中運行,從而簡化加速開發流程。此外,Spring Boot 學習簡單、輕量級、容易擴展。基于這些優秀的特點,Spring Boot 成為了蓬勃發展的快速應用開發領域的領導者。
在互聯網日益發展的當今時代,一個應用程序需要在全球范圍內使用勢在必然。傳統的程序設計方法將可翻譯信息如菜單按鈕的標簽、提示信息、幫助文檔等文字信息硬編碼在程序代碼中,但這些已經不能很好的適應全球化發展,而且程序的擴展性差,維護成本高。一個能支持全球化的應用程序,必須實現單一可執行的程序,動態地使用資源(Single Source Single Executable)。
對于一個能支持全球化的應用程序來說,需要考慮下面三方面的設計,如圖 1 所示。
圖 1. 多語言應用程序模型
區域模型的定制化(Locale Model):Locale 模型是一個多語言應用程序的基礎,用來確定界面語言以及日期時間等的格式化方式,通常包括語言環境(Language Locale)和文化環境(Cultural Locale)。一個應用程序的 Locale 獲取有下面幾種常見的方式:Locale.getDefault(); 一個應用程序具體選擇哪種方式獲取區域信息(Locale),這需要取決于該應用程序的用戶需求。
資源文件的外部化: 這里主要指界面語言,根據用戶選擇的 Locale 模型,自動地顯示與之對應的界面語言,讓客戶感覺這個產品是為他們而設計的。
日期時間等多元文化的支持: 包括貨幣、日歷、時間、日期、排序、界面方向性(Bi-directional) 等符合各個國家自己習慣的顯示方式。
下面主要從上述三方面分別介紹基于 Spring Boot 應用程序如何支持多語言的,包括 RESTful 消息和內容的國際化支持。
Spring Boot 中的區域模型介紹
在自定義應用程序區域模型(Locale)之前,需要了解一下 Spring Boot 中區域解析器的原理。基于 Spring 框架的應用程序中,用戶的區域信息是通過區域解析器 LocaleResolver 來識別的,LocaleResolver 是 Spring 框架基于 Web 提供的區域解析器接口,允許通過 HTTP 請求和響應修改區域設置,如清單 1 所示。
清單 1. Spring 框架中的區域解析器
public interface LocaleResolver {
Locale resolveLocale(HttpServletRequest request);
void setLocale(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Locale locale);
}
LocaleResolver 在 Spring 框架中有四種具體的實現:
按 HTTP 請求頭部解析區域(AcceptHeaderLocaleResolver): Spring 采用的默認區域解析器就是基于請求頭部,它通過檢驗 HTTP 請求的 accept-language 頭部來解析區域,這個頭部是由用戶的 Web 瀏覽器設定決定的。
按會話屬性解析區域(SessionLocaleResolver): 它通過檢驗用戶會話中預置的屬性來解析區域。如果該會話屬性不存在,它會根據 accept-language HTTP 頭部確定默認區域。
按 Cookie 解析區域(CookieLocaleResolver): 如果 Cookie 存在,它會根據 accept-languageHTTP 頭部確定默認區域。
FixedLocaleResolver: 此解析器始終返回固定的默認區域設置,通常取自 JVM 的默認區域設置。
除了 Spring 框架中提供的四種實現外,還可以創建自定義的區域解析器。在 Spring Boot 自動配置中可以看到清單 2 的代碼。
清單 2. 自定義 Spring 框架中的區域解析器
//向容器中加入了 LocaleResolver 對象
@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;
}
當我們的應用程序需要自定義區域解析器(LocaleResovler)的時候,可以通過下面幾個步驟實現。
第一步:自定義區域解析器
自定義區域解析器是對 Spring 中 LocaleResolver 接口的實現,可以基于應用程序的實際需求,取自于用戶自定義的語言選擇界面、用戶操作系統或者瀏覽器的語言設定。清單 3 是一個示例,首先判斷用戶請求中是否含有 lang 這個參數,如果有,就使用這個參數所帶的區域信息;如果沒有,就取自瀏覽器請求頭部中的 accept-language 信息。
清單 3. 自定義區域解析器
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]);
}
// 如果想使用當前系統的語言,則使用Locale.getDefault()
}
@Override
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
}
}
第二步: 將自定義的區域解析器添加到 IOC 容器中
通常添加在自定義的 config 文件中,下面的例子將自定義的 CustomLocaleResolver 通過 @Bean
注添加到 IOC 容器,如清單 4 所示。
清單 4. 自定義區域解析器添加到 IOC
@Configuration
public class CustomMvcConfigure extends WebMvcConfigurationSupport {
@Bean
public LocaleResolver localeResolver(){
return new CustomLocaleResolver();
}
}
如此,在程序中就可以調用我們自定義的區域解析器。
Thymeleaf 模板引擎對多語言的支持
Thymeleaf 是一個基于 Apache License 2.0 許可,支持 XML、XHTML、HTML5 的開源模板引擎,主要用于 Web 或者非 Web 環境中的應用開發,在有網絡和無網絡的環境下皆可運行,它既可以在瀏覽器端查看靜態頁面,也可以顯示動態頁面。這是由于它支持 HTML 原型,然后在 HTML 標簽里增加額外的屬性來達到模板+ 數據的展示方式。瀏覽器解析 HTML 時會忽略未定義的標簽屬性,所以 Thymeleaf 模板可以靜態地運行;當有數據返回到頁面時,Thymeleaf 標簽會動態地替換掉靜態內容,使頁面動態顯示。
在 Spring MVC 框架中,通常我們用 JSP 來展示 Web 前端,JSP 本質上也是模板引擎,然而 Spring Boot 官方推薦使用 Thymeleaf 模板引擎,Thymeleaf 完全可以替代 JSP 或者其他模板引擎如 Velocity、FreeMarker 等。雖然 Spring 官方推薦使用 Thymeleaf,但是并不是說 Spring Boot 不支持 JSP。
在 Spring Boot 項目中使用 Thymeleaf 模板支持多語言的步驟如下:
第一步: 封裝用戶語言環境
在我們的實驗中,設計一個簡單的登錄頁面,登錄頁面有個語言選擇下拉列表,將使用用戶選的語言來顯示登錄頁面上的標簽,如圖 2 所示。
圖 2. 資源文件組織結構
為了使我們的程序能夠使用指定的 Locale,首先需要添加 LocaleResolver Bean。在我們的示例中,使用了用戶選擇的 Locale,所以需要配置 LocaleChangeInterceptor 攔截器,主要用來檢查傳入的請求,如果請求中有 lang 的參數(可配置),如 http://localhost:8089/login?lang=zh_CN ,那么該 Interceptor 將使用 localResolver 改變當前用戶的默認 Locale。清單 5 為示例代碼,根據用戶選擇的語言顯示對應的界面。
清單 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());
}
}
第二步: 定義多語言資源文件
默認情況下,資源文件是直接放在 src/main/resource 目錄下,為了實現代碼的結構化,我們在 resource 目錄下創建 i18n 文件夾,然后在 i18n 目錄下創建資源文件(當然也可以在 i18n目錄下創建不同的子目錄,在子目錄下再創建資源文件),這種情況下,我們需要在配置文件 application.properties 中重新指定資源文件的 basename:spring.messages.basename 。資源文件名可以根據自己的項目定義,但是通常的規范是:模塊名_語言_國家 .properties ,在本實例中我們命名為如 log_zh_CN.properties ,資源文件的目錄結構如圖 3 所示。
圖 3. 資源文件組織結構
對應資源文件中的每一個 property key ,一般都是小寫字母開頭,用下劃線表示這個 key 在程序中的層級結構,并且按照字母順序排序,便于管理和查找,如清單 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 模板引擎中調用多語言資源
在 Spring Boot 架構中使用 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
根據業務需求編寫 Thymeleaf 模板 HTML 文件,默認的模板映射路徑是: src/main/resources/templates ,也可以在 application.properties 中配置自定義的模板路徑 spring.thymeleaf.prefix=classpath:/templates/myTemplates/ 。
在 Thymeleaf 模板文件中,將所有的硬編碼字符串抽取出來放到資源文件中,然后用資源文件中的鍵值 #{Property Key} 來表示界面顯示的標簽信息:
普通的標簽字符串:用 th:text= "#{login_login}" 表示;
作為賦值的字符串,如 value="Login" ,表示成 th:value="#{login_login}" ;如 placeholder="Login" ,表示成 th:placeholder="#{login_login}"
清單 9 是一個簡單登錄頁面的 Thymeleaf 模板內容(只列出了部分關鍵代碼),其中所有顯示的標簽信息都抽取到資源文件中,如 th:text="#(login_login_page)" ,從而根據用戶選擇的語言自動讀取對應的資源文件中的字符串來顯示。
清單 9. 編寫 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">簡體中文</option>
<option th:selected="${displayLang=='ja_JP'}" value="ja_JP">日本語</option>
<option th:selected="${displayLang=='zh_TW'}" value="zh_TW">繁體中文</option>
</select>
</div></div><div></div></div></div>
</body>
在實驗中遇到一個問題,界面字符串全部顯示為 ??Properties_Key_語言?? ,如圖 4 所示。
圖 4. 不能正確讀取 properties 文件
經過一步步排查,原因是在配置文件 application.properties 錯誤配置了資源文件的路徑如圖 5 所示。正確的路徑為 spring.messages.basename=i18n/login 。
圖 5. spring.messages.basename 路徑不正確
Spring Boot 中時間日期格式化
Java 8 提供了更便捷的日期時間 API 如 LocalDate、LocalTime 和 LocalDateTime,Spring Boot 架構中推薦使用 Java 8 中新的時間日期 API。LocalDate 較 Date 類的優點體現在以下幾個方面:
Date 類打印出來的日期可讀性差,通常需要使用 SimpleDateFormat 進行格式化,而 LocalDate 默認的格式為 YYYY-MM-DD 。
LocalDate 較 Date 在日期計算及格式化方面更簡單易用。
Date 類同時包括了日期和時間,而 LocalDate、LocalTime 和 LocalDateTime 分別表示日期、時間、日期和時間,使用起來非常靈活。
Date 類不支持多線程安全,LocalDate 等新的接口是線程安全的。
對于后臺 Java 程序中的時間日期格式化問題,通常會重新定制化時間日期格式化接口,比如將 Locale 參數、想要顯示的樣式參數傳入進去。清單 10 是格式化時間日期的接口示例代碼,清單 11 是具體實現的示例代碼,這里只列舉了幾種典型的格式化情況。
清單 10. 時間日期格式化接口
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. 時間日期格式化實現
@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 架構中接口與接口之間、前后端之間都使用 JSON 格式傳輸數據。對于日期格式的數據,如果采用默認方式不做處理,易讀性和可用性不是很好。如清單 12 就是默認 JSON 格式的日期。請求得到的 JSON 格式日期可讀性很差,如圖 6 所示。
清單 12. 默認的時間日期
@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. 默認格式返回的 JSON 格式日期
下面我們采用 @JsonFormat 序列化屬性值,如清單 13 所示。
清單 13. 使用 @JsonFormat 進行標注
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 格式日期
清單 13 我們只是對 JSON 格式的日期進行了格式化,但是還沒有實現多語言化。我們采用中定義的多語言格式化方法對 JSON 格式日期進行格式化,將日期參數定義為 String 類型。如清單 14 所示。
清單 14. 使用多語言格式化方法
@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;
}
}
當傳入的語言參數為 zh_CN 時,響應的日期如圖 8 所示。
圖 8. 多語言的 JSON 格式日期
![](https://s1.51cto.com/images/blog/201912/17/7d761433b542fe06d738ed20afca0f1c.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)
![](https://s1.51cto.com/images/blog/201912/17/6803c9267251f0d74eb3fe9e7a85325e.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)
Spring Boot RESTful API 多語言支持
隨著 Spring Boot 的快速發展,基于 RESTful
準的微服務接口應用也越來越廣泛,RESTful API 使用 HTTP 協議來表示創建、檢索、更新和刪除 (CRUD) 操作。下面主要介紹在 Spring Boot 框架下,如何實現服務器端 RESTful API 的多語言支持,主要涉及到返回的內容和消息。通常有以下幾方面需要考慮,如圖 9 所示。
圖 9. RESTful API 多語言支持
![](https://s1.51cto.com/images/blog/201912/17/5667612a78e149c12e22ec39ddc10d17.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)
第一步: 封裝和自定義資源文件讀取工具
現行的開發框架大部分都會提供接口讀取資源文件。涉及到資源文件讀取時,ResourceBundle 是一個機制,主要用來根據不同的用戶區域信息自動地讀取對應的資源文件,ResourceBundle 是 Java 中的資源文件讀取接口,圖 10 總結了 Java 程序中 ResourceBundle 的管理機制。
圖 10. Java 程序 Resource Bundle 管理流程
![](https://s1.51cto.com/images/blog/201912/17/ab799248216ba780e4e02adfc4a24ee4.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)
![](https://s1.51cto.com/images/blog/201912/17/dde4747c6786d3fa378a50db48af5e34.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)
Spring 定義了訪問資源文件的 MessageSource 接口,該接口有幾個重要的方法用來讀取資源文件,如表 1 所示。
表 1. MessageSource 接口說明
方法名說明String getMessage(String code, Object[] args, String defaultMessage, Locale locale)code :表示資源文件中的 Property Key 。
args :用于傳遞占位符所代表的運行期參數。
defaultMessage :當在資源找不到對應屬性名時,返回參數所指定的默認值。
locale :表示區域信息。String getMessage(String code, Object[] args, Locale locale)throws NoSuchMessageException找不到資源中對應的屬性名時,直接拋出 NoSuchMessageException 異常。String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException將屬性名、參數數組以及默認信息封裝起來,它的功能和第一個接口方法相同。
MessageSource 被 HierarchicalMessageSource 和 ApplicationContext 兩個接口繼承,如圖 11 所示。
圖 11. MessageSource 類圖
![](https://s1.51cto.com/images/blog/201912/17/6489277d25139d1fde60945cc064f4a2.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)
![](https://s1.51cto.com/images/blog/201912/17/3a0eaba17ee25ee3ccf6e4477a060fff.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)
ResourceBundleMessageSource 和 ReloadableResourceBundleMessageSource 是在 Java ResourceBundle 基礎上 HierarchicalMessageSource 的兩個具體實現類。他們與 ResourceBundle 的區別是不需要分別加載不同語言的資源文件,通過資源文件名 (baseName) 就可以加載整套的國際化資源文件;同時不再需要顯示的調用 MessageFormat 方法,使用起來更加簡單便捷;還可以讀取 XML 格式的資源文件。
ReloadableResourceBundleMessageSource 可以定時刷新資源文件,以便在應用程序不重啟的情況下感知資源文件的更新,還可以設置讀取資源文件的編碼方式。 cacheSeconds 屬性讓 ReloadableResourceBundleMessageSource 根據設定的時間刷新監測資源文件是否有更新,但是刷新周期不能太短,否則影響應用程序的性能。如果 cacheSeconds 設置 -1,表示永遠不刷新,這個時候 ReloadableResourceBundleMessageSource 和 ResourceBundleMessageSource 功能等同。
清單 15 在 ReloadableResourceBundleMessageSource 類的基礎上,自定義資源文件讀取接口,讀取自定路徑的資源文件。
清單 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);
}
第二步: 設計多語言的 API 返回狀態碼
RESTful API 都會有返回狀態碼,為了支持多語言,在設計返回狀態碼接口的時候也需要考慮多語言的支持,下面以上傳文件為例來說明如何設計反饋狀態碼以及返回接口的封裝。在設計返回狀態碼的時候,涉及到顯示的消息內容,這里用資源文件里的 Property Key 來表示,這樣在返回狀態封裝類中比較容易動態地讀取不同語言的返回消息,如清單 16 所示。
清單 16. 多語言 API 返回狀態碼
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;
}
}
第三步: 多語言 API 返回接口封裝
利用第一步中自定義的資源文件讀取工具,動態的讀取多余的返回信息,如清單 17 所示。
清單 17. 多語言 API 返回狀態碼
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);
}
}
第四步: 在控制器中調用多語言的返回碼
本步操作如清單 18 所示。
清單 18. 控制器中調用多語言返回碼
@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 是測試上傳文件的結果,在調用 API 的時候,傳遞的參數是簡體中文 (zh_CN) ,返回的狀態和信息顯示為中文信息,使用開發者熟悉的語言顯示返回信息便于讀取查看。
圖 12. MessageSource 類圖
![](https://s1.51cto.com/images/blog/201912/17/205be3ce4deb627e83ef61994fdda7fd.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)
![](https://s1.51cto.com/images/blog/201912/17/7f5b7813cbb8a31850b2c436a3085edf.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)
結束語
本文總結了在 Spring Boot 框架下,如何開發一個多語言的應用程序及 RESTful API。闡述了 Spring Boot 架構下區域模型的原理,基于已有的區域模型定制化應用程序自己的區域模型;Thymeleaf 模板引擎對多語言的支持;Spring Boot 中時間日期格式化;以及 Spring Boot RESTful API 多語言支持實踐。希望這篇文章能為正在開發國際化應用程序和微服務的您提供一定的參考。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。