如何利用Spring Boot框架开发一个全球化的应用程序

Spring Boot 架构中的国际化支持实践—— Spring Boot 全球化解决方案 背景Spring Boot 主要通过 Maven 或 Gradle 这样的构建系统以继承方式添加依赖,同时继承了 Spring 框架中的优秀元素,减少了 Spring MVC 架构中的复... 阅读详情

Spring Boot 主要通过 Maven 或 Gradle 这样的构建系统以继承方式添加依赖,同时继承了 Spring 框架中的优秀元素,减少了 Spring MVC 架构中的复杂配置,内置 Tomcat,Jetty 容器,使用 Java application 运行程序,而不是传统地把 WAR 包置于 Tomcat 等容器中运行,从而简化加速开发流程。此外,Spring Boot 学习简单、轻量级、容易扩展。基于这些优秀的特点,Spring Boot 成为了蓬勃发展的快速应用开发领域的领导者。

在互联网日益发展的当今时代,一个应用程序需要在全球范围内使用势在必然。传统的程序设计方法将可翻译信息如菜单按钮的标签、提示信息、帮助文档等文字信息硬编码在程序代码中,但这些已经不能很好的适应全球化发展,而且程序的扩展性差,维护成本高。一个能支持全球化的应用程序,必须实现单一可执行的程序,动态地使用资源(Single Source Single Executable)。

对于一个能支持全球化的应用程序来说,需要考虑下面三方面的设计,如图 1 所示。

图 1. 多语言应用程序模型

如何利用Spring Boot框架开发一个全球化的应用程序

 

  • 区域模型的定制化(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 的代码。

//向容器中加入了 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. 资源文件组织结构

如何利用Spring Boot框架开发一个全球化的应用程序

 

为了使我们的程序能够使用指定的 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,在本实例中我们命名为如 logzhCN.properties,资源文件的目录结构如图 3 所示。

图 3. 资源文件组织结构

如何利用Spring Boot框架开发一个全球化的应用程序

 

对应资源文件中的每一个 property key,一般都是小写字母开头,用下划线表示这个 key 在程序中的层级结构,并且按照字母顺序排序,便于管理和查找,如清单 6 所示。

清单 6. 英文资源文件示例

login_home=Home
login_login=Log Inlogin_login_page=Login pagelogin_languge_selector=Language Selectorlogin_please_select_language_to_display=Please select the language to display
login_password=Passwordlogin_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=HTMLspring.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="#{loginlogin}";如 placeholder="Login",表示成 th:placeholder="#{loginlogin}"清单 9 是一个简单登录页面的 Thymeleaf 模板内容(只列出了部分关键代码),其中所有显示的标签信息都抽取到资源文件中,如 th:text="#(loginloginpage)",从而根据用户选择的语言自动读取对应的资源文件中的字符串来显示。

清单 9. 编写 Thymeleaf 模板文件

<body>
<div layout:fragment="content" th:remove="tag">
 <div class="row">
    <div class="col-md-6 col-md-offset-3">
       <h1 th:text="#{login_login_page}"></h1>
          <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>

 

如何利用Spring Boot框架开发一个全球化的应用程序

 

经过一步步排查,原因是在配置文件 application.properties 错误配置了资源文件的路径如图 5 所示。正确的路径为 spring.messages.basename=i18n/login。

图 5. spring.messages.basename 路径不正确

如何利用Spring Boot框架开发一个全球化的应用程序

 

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 格式日期

如何利用Spring Boot框架开发一个全球化的应用程序

 

下面我们采用 @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 格式日期

如何利用Spring Boot框架开发一个全球化的应用程序

 

清单 13 我们只是对 JSON 格式的日期进行了格式化,但是还没有实现多语言化。我们采用清单 10 中定义的多语言格式化方法对 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 格式日期

如何利用Spring Boot框架开发一个全球化的应用程序

 

Spring Boot RESTful API 多语言支持

随着 Spring Boot 的快速发展,基于 RESTful

准的微服务接口应用也越来越广泛,RESTful API 使用 HTTP 协议来表示创建、检索、更新和删除 (CRUD) 操作。下面主要介绍在 Spring Boot 框架下,如何实现服务器端 RESTful API 的多语言支持,主要涉及到返回的内容和消息。通常有以下几方面需要考虑,如图 9 所示。

图 9. RESTful API 多语言支持

如何利用Spring Boot框架开发一个全球化的应用程序

 

第一步: 封装和自定义资源文件读取工具

现行的开发框架大部分都会提供接口读取资源文件。涉及到资源文件读取时,ResourceBundle 是一个机制,主要用来根据不同的用户区域信息自动地读取对应的资源文件,ResourceBundle 是 Java 中的资源文件读取接口,图 10 总结了 Java 程序中 ResourceBundle 的管理机制。

图 10. Java 程序 Resource Bundle 管理流程

如何利用Spring Boot框架开发一个全球化的应用程序

 

MessageSource 被 HierarchicalMessageSource 和 ApplicationContext 两个接口继承,如图 11 所示。

图 11. MessageSource 类图

如何利用Spring Boot框架开发一个全球化的应用程序

 

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 返回状态码

Spring 定义了访问资源文件的 MessageSource 接口,该接口有几个重要的方法用来读取资源文件,如表 1 所示。

表 1. MessageSource 接口说明

如何利用Spring Boot框架开发一个全球化的应用程序

 

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")
public class UploadFilesController {
     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 类图

如何利用Spring Boot框架开发一个全球化的应用程序

 

结束语

本文总结了在 Spring Boot 框架下,如何开发一个多语言的应用程序及 RESTful API。阐述了 Spring Boot 架构下区域模型的原理,基于已有的区域模型定制化应用程序自己的区域模型;Thymeleaf 模板引擎对多语言的支持;Spring Boot 中时间日期格式化;以及 Spring Boot RESTful API 多语言支持实践。希望这篇文章能为正在开发国际化应用程序和微服务的您提供一定的参考。

解决若依Vue3前后端分离---路由切换时显示白屏 解决基于Vue3若依前后端分离项目中出现的路由正常切换但是就是不显示数据的问题,也就是不发起网络请求的问题。 阅读详情

相关推荐

十大排序算法详解(一)冒泡排序、选择排序、插入排序、快速排序、希尔排序

冒泡排序、选择排序、插入排序、快速排序、希尔排序

HK的博客 18万+

spring boot 插件化开发(一)

最近做了个有趣的事,今晚不想写代码,来记录一下,如果需要的人看见也算是功德一件。 github地址:https://github.com/zhengdaone/spring-plugin-cli 可以先拉下来看看效果。 需求描述:产品被越来越多的用户使用,需求的差异也愈来愈大,为了适应不同用户的不同需求,将产品进行插件化升级。 目标:将项目拆分为主程序和插件,主程序感知不到插件的存在,插件...

ZhengDa_LY的博客 1万+

如何在iPhone上本地运行SmolVLM?5亿参数模型实战指南(附Swift代码)

本文详细介绍了如何在iPhone上本地部署和运行轻量高效的SmolVLM多模态模型。通过实战指南,分享了使用Swift和MLX框架进行环境配置、模型加载、内存优化以及实现实时图像分析与文本生成的完整流程,帮助开发者在移动端高效运行AI视觉语言模型。

echo99的专栏 654

SpringCloud微服务架构前后端分离项目实践

一款 Java 语言基于 SpringCloud、Vue、ElementUI、MySQL等框架精心打造的一款前后端分离框架,致力于实现模块化、组件化、可插拔的前后端分离架构敏捷开发框架,可用于快速搭建前后端分离后台管理系统,本着简化开发、提升开发效率的初衷,目前框架已集成了完整的RBAC权限架构和常规基础模块,前端Vue端支持多主题切换,可以根据自己喜欢的风格选择想一个的主题的个性化呈现的需

weixin_50196917的博客 5686

矩阵LED和矩阵按键

先说矩阵LED,如图: 下面以行几、列几的表达方法说明 三极管可以当做开关使用 由图可知,如果想让一个LED亮,那么它对应的行和列的三极管都要导通。如果这个矩阵每次只亮一个LED,那么正常写程序没问题。 如果我们想让LED1和LED6亮,其他灯不亮,则需要开启行1、行2、列1、列2,随之带来的问题就是LED2和LED5也会跟着亮起来。 解决方法类似于扫描,因为单片机运行速度很快,可以在很短的时间内开关数次而肉眼看不到。所以我们可以每次只亮一列(排)灯,亮单位时间后关掉同时开启另外一列(排),如此四次一循环

qq_46615634的博客 5793

FPGA开发者必看:如何用SATA3.0主机控制器IP实现高速存储扩展(附Xilinx实测数据)

本文为FPGA开发者提供了基于SATA3.0主机控制器IP核的高速存储方案设计与优化指南。通过详细解析IP核架构、Xilinx FPGA平台集成实战及性能优化技巧,帮助开发者实现接近理论极限的540MB/s数据传输速度。文章还包含常见问题解决方案和高级应用场景拓展,是硬件工程师提升存储性能的必备参考。

weixin_30632089的博客 114

SpringBoot对比传统开发以及自身的优缺点

SpringBoot是伴随着Spring4.0诞生的,继承了Spring的优点,一经推出,引起了巨大的反向;目前SpringBoot的版本为2.1.0,需要Java7及Spring Framework4.1.5以上的支持。如果想在Java6下使用它,需要一些额外的设置。Spring Boot的优点:使用Java或Groovy开发基于Spring的应用程序非常容易。它减少了大量的开...

辉哥的博客 1万+

傻白入门芯片设计,芯片键合(Die Bonding)(四)

傻白入门芯片设计,芯片键合(Die Bonding)(四) 引线键合、倒装芯片键合、管脚贴合技术

qq_46675545的博客 3万+

介绍一款基于SpringBoot开发OA开源产品

办公自动化(OA)是面向组织的日常运作和管理,员工及管理者使用频率最高的应用系统,极大提高公司的办公效率。1.项目介绍 oasys是一个OA办公自动化系统,使用Maven进行项目管理,基...

Java笔记虾 1629

Xilinx ZYNQ 7000学习笔记二(FSBL代码分析启动-汇编代码)

Xilinx ZYNQ 7000学习笔记

u011116085的博客 4246

LMI线性矩阵不等式之非线性变量处理

​ 如果一个LMI线性矩阵不等式中的某项存在两个矩阵变量相乘,那么称该类情形为非线性矩阵不等式,而要直接求解时很困难的,而且LMI工具箱也无法求解非线性矩阵,所以需要通过变量替换法进行处理。 ​...

Mr.邹的博客 4550

用Spring Boot写自己的第一个HelloWorld程序,超详细

1、所用工具有关版本如下 jdk 1.8 maven 3.3.6 idea 2018 2、创建spring boot项目 1、创建maven 项目 然后点击Nest继续填写 然后finish 2、在pom.xml中添加依赖 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/200

weixin_45757712的博客 535

【异常】Openclaw Gateway进程异常停止问题分析与解决方案

本文分析了Gateway服务异常停止的问题,提出了三种解决方案。核心问题是缺乏进程守护和自动恢复机制,导致服务崩溃后无法自行重启。推荐方案是使用PM2进程管理工具(方案1),提供进程监控、自动重启和开机自启功能。替代方案包括自定义守护脚本(方案2)和supervisord管理(方案3)。这些方案都能有效解决当前服务脆弱性问题,显著提升服务可用性,其中PM2方案成熟度高、配置简单,是首选解决方案。实施后可将服务从人工干预转变为自动恢复,确保业务连续性。

奶爸的编程之路,也就一周冷个三天 457

如何利用Spring Boot框架开发一个全球化的应用程序(1)

2021年的金三银四一眨眼就到了,对于很多人来说是跳槽的好机会,大厂面试远没有我们想的那么困难,摆好心态,做好准备,你也可以的。另外,面试中遇到不会的问题不妨尝试讲讲自己的思路,因为有些问题不是考察我们的编程能力,而是逻辑思维表达能力;最后平时要进行自我分析与评价,做好职业规划,不断摸索,提高自己的编程能力和抽象思维能力。BAT面试经验实战系列:Spring全家桶+Redis等其他相关的电子书:源码+调优面试真题:《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》

a1901149的博客 384

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

springboot 强制显示一种语言_如何利用Spring Boot框架开发一个全球化的应用程序...

Spring Boot 主要通过 Maven 或 Gradle 这样的构建系统以继承方式添加依赖,同时继承了 Spring 框架中的优秀元素,减少了 Spring MVC 架构中的复杂配置,内置 Tomcat,Jetty 容器,使用 Java application 运行程序,而不是传统地把 WAR 包置于 Tomcat 等容器中运行,从而简化加速开发流程。此外,Spring Boot 学习简单、...

weixin_42205158的博客 185

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

springboot 强制显示一种语言_Spring Boot 架构中的国际化支持实践—— Spring Boot 全球化解决方案...

背景Spring Boot 主要通过 Maven 或 Gradle 这样的构建系统以继承方式添加依赖,同时继承了 Spring 框架中的优秀元素,减少了 Spring MVC 架构中的复杂配置,内置 Tomcat,Jetty 容器,使用 Java application 运行程序,而不是传统地把 WAR 包置于 Tomcat 等容器中运行,从而简化加速开发流程。此外,Spring Boo...

weixin_28679635的博客 474

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

计算机毕业设计Springboot基于区块链的脐橙溯源系统 基于Spring Boot框架的区块链脐橙溯源平台设计与实现 利用Spring Boot构建区块链驱动的脐橙溯源管理系统

计算机毕业设计Springboot基于区块链的脐橙溯源系统26a8q5q7(配套有源码 程序 mysql数据库 论文)本套源码可以先看具体功能演示视频领取,文末有联xi 可分享在全球化的农产品市场中,食品安全和溯源性成为了消费者、生产者以及监管机构最为关注的问题之一。脐橙作为广受欢迎的水果,其生产、加工和销售过程中的信息透明度至关重要。传统的溯源方式往往依赖于纸质记录或中心化数据库,不仅容易被篡改,还难以实现全链条的有效追踪。

路可程序设计 2253

计算机毕设Springboot基于web的英语学习网站 基于Spring Boot框架的Web英语学习平台设计与实现 面向Web的英语学习系统开发:基于Spring Boot技术栈

计算机毕业设计Springboot基于web的英语学习网站ync88v78 (配套有源码 程序 mysql数据库 论文)本套源码可以先看具体功能演示视频领取,文末有联xi 可分享随着全球化的加速,英语作为国际通用语言的重要性日益凸显。无论是学术交流、职业发展,还是日常社交,掌握英语都已成为一项必备技能。然而,传统的英语学习方式往往受到时间和空间的限制,难以满足现代人快节奏生活下的学习需求。

django毕设 1905

bankeralgorithm.jar中没有主清单属性_Spring Boot 架构中的国际化支持实践—— Spring Boot 全球化解决方案...

背景Spring Boot 主要通过 Maven 或 Gradle 这样的构建系统以继承方式添加依赖,同时继承了 Spring 框架中的优秀元素,减少了 Spring MVC 架构中的复杂配置,内置 Tomcat,Jetty 容器,使用 Java application 运行程序,而不是传统地把 WAR 包置于 Tomcat 等容器中运行,从而简化加速开发流程。此外,Spring Boo...

weixin_39615741的博客 96

Spring Boot 国际化(i18n)实现指南

国际化是指设计和开发应用程序时,使其能够适应不同语言、地区和文化习惯的过程。通过国际化,应用可以根据用户的语言偏好动态切换文本内容,包括界面标签、提示信息、错误消息等,同时还能处理不同地区的日期、时间、数字、货币等格式差异,为全球用户提供一致且友好的体验。例如,一款面向全球用户的电商应用,需要根据用户所在地区显示不同语言的商品描述、价格格式以及促销信息。通过上述步骤,我们可以在 Spring Boot 项目中轻松实现国际化功能,为全球用户提供多语言支持。从创建资源文件、配置。

2302_79926174的博客 1345

基于Spring Boot框架的文学名著分享系统的设计与实现

springboot为基础的文学名著分享信息化管理体系。首先,以需求为依据,根据需求分析结果进行了系统的设计,并将其划分为管理员和用户二种角色和多个主要模块:用户、书籍分类、书籍信息、图书分享等。使用目前市场主流的技术springboot框架进行项目构建,基于B/S架构模式,使用Java开发语言和MySQL数据库对系统进行高内聚低耦合的设计,最终完成了文学名著分享系统的实现。 基于springboot框架的文学名著分享系统为当前传统管理模式提供了一个高效、便捷、信息化的解决方案,这为后期文学名著分享系

m0_50936939的博客 1052

Java 基于Spring Boot+Vue的民俗文化系统

在全球化的今天,文化的交流与碰撞日益频繁,不同民族、不同地区的文化相互融合,形成了多元化的文化格局。然而,这种文化交流的同时,也带来了对本土文化的冲击和侵蚀。民俗文化,作为一种独特的文化形态,它承载着丰富的历史信息、地域特色和民族记忆,是国家和民族历史沉淀的重要组成部分,为了更好的宣传我国的民俗文化我们通过Spring Boot框架开发了本次的民俗文化系统。 本民俗文化系统是根据当前数字化时代对民俗文化管理的要求而开发的,在系统语言选择上我们使用的Java语言,数据库是小巧灵活的MySQL数据库,本系统的开

QQ860234001的专栏 818

计算机毕业设计springboot国外鞋服代购平台 基于Spring Boot的海外鞋服代购平台设计与开发 Spring Boot框架下海外鞋服代购系统构建

在当今全球化的时代,海外鞋服代购业务蓬勃发展,成为许多人追求时尚潮流的重要途径。然而,随着代购业务的日益复杂,传统的管理模式已难以满足高效、便捷的管理需求。基于此,我们设计并开发了一款基于Spring Boot的海外鞋服代购平台,旨在为代购业务提供一个高效、安全、便捷的管理解决方案。 该平台采用Java技术和Spring Boot框架构建,连接MySQL数据库,具备强大的信息传输和数据处理能力。系统主要包含以下功能: 用户管理:涵盖用户注册、登录、信息查看与修改、密码修改等基础功能。 商品信息管理:管理员和

毕业设计-小慧师姐 944
上一篇: Java面试中最常问的JVM问题大全,附答案
下一篇: 百万JAVA程序员最关注的问题:今年的“金九银十”还在吗?
java架构师小芷
博客等级 码龄6年 216粉丝 519原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值