前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring国际化

Spring国际化

原创
作者头像
eeaters
发布2022-02-23 18:34:49
1K0
发布2022-02-23 18:34:49
举报
文章被收录于专栏:阿杰阿杰
  • 前言
  • SpringBoot中校验基本使用
    • 地区解析
    • 测试代码
    • 异常处理
    • 响应结果
  • Java标准国际化
    • ResourceBundle
    • Debug
  • Hibernate-validator中使用
  • WebMvc的基本流程
  • 业务代码中使用国际化文案
    • 国际化生效
    • 使用国际化
    • 业务代码中使用
    • 测试

前言

国际化(i18n)是针对不同国家不同区域,同样的程序会有不同的表现形式;

在日常使用的开源框架中,都会有不同程度的国际化在里面; 刚好现在需要让程序中搞一下国际化,那么就跟踪一下如何在SpringBoot中使用国际化

使用基本就两个地方:

  • 参数校验中使用(hibernate已支持)
  • 业务代码中使用(需要简单的配置一下)

代码提交至: https://github.com/eeaters/spring-cloud-demo/tree/master/spring-boot-demo/i18n

参数校验基本使用

地区解析

为了测试方便,这里将地区的传递放在了requestParam中

代码语言:javascript
复制
@Configuration
public class I18nConfig {
    @Bean
    public LocaleResolver localeResolver() {
        return new I18nLocaleResolver();
    }
    /**
     * @see RequestContextFilter
     */
    static class I18nLocaleResolver implements LocaleResolver {
        @Override
        public Locale resolveLocale(HttpServletRequest request) {
            String language = request.getParameter("language");
            Locale locale ;
            if (StringUtils.hasText(language)) {
                String[] split = language.split("_");   //国家-地区-方言
                if (split.length == 1) {
                    locale = new Locale(split[0]);
                } else {
                    locale = new Locale(split[0], split[1]);
                }
            }else{
                locale = Locale.getDefault();
            }
            return locale;
        }
        @Override
        public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
        }
    }
}

测试代码

代码语言:javascript
复制
@SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ExtendWith(SpringExtension.class)
public class ApplicationTest {

    @Autowired
    TestRestTemplate restTemplate;
    @Test
    public void test() {
        //里面只有一个message参数
        EchoEntity echoEntity = new EchoEntity();
        ResponseEntity<String> entity = restTemplate.postForEntity("/echo", echoEntity, String.class);
        System.out.println("entity.getBody() = " + entity.getBody());

        ResponseEntity<String> entity2 = restTemplate.postForEntity("/echo?language=en", echoEntity, String.class);
        System.out.println("entity.getBody() = " + entity2.getBody());
    }
}

异常处理

如果不做任何处理;入参校验失败时错误信息不会返回,不便于查看,简单加一个异常处理

代码语言:javascript
复制
@RestControllerAdvice
public class GlobalControllerAdvice {
    @ExceptionHandler
    public ResponseEntity handleValidException(MethodArgumentNotValidException e) {
        StringBuilder sb = new StringBuilder();
        for (ObjectError error : e.getBindingResult().getAllErrors()) {
            if (error instanceof FieldError) {
                String field = ((FieldError) error).getField();
                Object rejectedValue = ((FieldError) error).getRejectedValue();
                String errorMessage = error.getDefaultMessage();
                sb.append(field)
                        .append(" : ")
                        .append(rejectedValue)
                        .append(" -> ")
                        .append(errorMessage);
            }else{
                sb.append(error.getDefaultMessage());
            }

        }
        return ResponseEntity.badRequest().body(sb.toString());
    }
}

响应结果

代码语言:javascript
复制
entity.getBody() = message : null -> 不能为空
entity.getBody() = message : null -> must not be blank

Java标准国际化

ResourceBundle

Demo中国际化实现的底层依赖于 hibernate-validator 的校验功能 而 hibernate-validator 依赖于Java的国际化 ResourceBundle

来一段ResourceBundle的实例代码:

代码语言:javascript
复制
public class ResourceBundleExample {
    @Test
    public void load() {
        //在resources下创建example.properties
        ResourceBundle bundle = ResourceBundle.getBundle("example", Locale.US);
        String name = bundle.getString("name");
        System.out.println("name = " + name);
    }
}

Idea环境下resources目录下右键会有ResourceBundle的选项 ,

创建时会提示一个basename并提示选择en和zh_CN两个Project Locale;

这个就是国际化对应的不同地域的展示文案;

ResourceBundle-Debug

上面代码中 ResourceBundle.getBundle("example", Locale.US); 这段代码可以获取到resources目录下example*.properties的配置文件,那么以这里为debug的目标

得到以下结论:

  1. 配置方式默认支持.java/.properties两种方式,在Control的类注释上提供了.xml的配置示例
  2. ResourceBundle具有层次性结构
  3. Locale分为language,country,variant(方言)
  4. 比如: en_US(方言为空), 那么ResourceBundle是先找en_US的配置,没有则找en的,还是没有找默认的 默认的也就是example.properties的配置
  5. 中文的话如下:
一次debug的截图
一次debug的截图

Hibernate-validator中使用

  1. 通过java的spi形式控制 ValidationProvider 管理校验的默认配置和ValidatorFactory
  2. 当时会用ValidatorFactory进行校验时会获取插入器MessageInterpolator
  3. 当没有特殊配置时采用默认配置生成 PlatformResourceBundleLocator,
  4. 创建一个用户级别的, basename = ValidationMessages ,这个是在hibernate-validator中默认存在的
  5. 创建一个扩展级别的, basename = ContributorValidationMessages , 这个是作为用户扩展使用的, 也就是说可以直接在项目中扩展这个前缀的ResourceBundle进行扩展, 可以替换掉默认的

WebMvc的基本流程

以Post请求, method(@ResponseBody @Valid Object obj) 为例跨越式跟踪

  1. RequestResponseBodyMethodProcessor#resolveArgument readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType()); 将请求的信息转化为实体类
  2. validateIfApplicable(binder, parameter); → binder.validate(validationHints); 开始进行校验
  3. ValidatorImpl#validate spring作为一层委托,具体校验丢给了ValidatorImpl进行校验 此时进入hibernate的世界, 此时对象实际上已经转化成功了,现在要做的就是把这个对象进行校验
  4. 层级太多,这里选择非级联非分组的调式进入 ValidatorImpl#validateConstraintsForSingleDefaultGroupElement
  5. ConstraintTree#validateConstraints 先进行校验
  6. AbstractValidationContext#addConstraintFailure校验失败进行处理, 通过 MessageInterpolator#interpolateMessage 插入器进行信息插入, 有了message(对应配置文件的key)和Locale,可以获取到配置文件中的国际化文案

对于简单的校验流程就跑通了

当使用@Min等需要返回预定好的数据时,那么就需要使用到占位符,那Min举例 : javax.validation.constraints.Min.message = must be greater than or equal to {value} 这时候在第6步插入的时候会判断是否存在 { 符号; 如果存在就使用el表达式的规则进行占位符的替换

业务代码中使用国际化文案

国际化生效

SpringBoot中需要使用国际化需要在resource目录下创建messages.properties文件;

原因: MessageSourceAutoConfiguration 是SpringBoot国际化的一个自动装配类, 生效的条件为:

  1. 当前容器上下文中没有messageSource这个bean(如果有这个Bean代表自定义了国际化的实现)
  2. 判断 spring.messages.basename 对应的文件名字是否存在,默认=messages 也就是判断 messages.properties 这个文件是否存在
代码语言:javascript
复制
@ConditionalOnMissingBean(
    name = {"messageSource"},
    search = SearchStrategy.CURRENT
)
@AutoConfigureOrder(-2147483648)
@Conditional({MessageSourceAutoConfiguration.ResourceBundleCondition.class})

配置文件中增加简单的额配置

代码语言:javascript
复制
# messages.properties中
name=eeaters
notFound = not found

# messages_zh_CN.properties中
name=\u98df\u6b7b\u5f92
notFound = \u4e0d\u5b58\u5728\u7684\u503c

使用国际化

代码语言:javascript
复制
@Component
public class MessageAdapter implements ApplicationContextAware {

    static MessageSource messageSource;

    public static String getMessage(String code) {
        return messageSource.getMessage(code, null, LocaleContextHolder.getLocale());
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        messageSource = applicationContext.getBean(MessageSource.class);
    }
}

业务代码中使用

代码语言:javascript
复制
    @GetMapping("echo")
    public ResponseEntity<String> echoGet(@RequestParam String message) {
        try {
            return ResponseEntity.ok(MessageAdapter.getMessage(message));
        } catch (NoSuchMessageException e) {
            return ResponseEntity.ok(MessageAdapter.getMessage("notFound"));
        }
    }

测试

localhost:8080/echo?language=en&message=name → eeaters

localhost:8080/echo?message=name → 食死徒

localhost:8080/echo?language=en&message=abc → not found

localhost:8080/echo?message=abc → 不存在的值

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 参数校验基本使用
    • 地区解析
      • 测试代码
        • 异常处理
          • 响应结果
          • Java标准国际化
            • ResourceBundle
              • ResourceBundle-Debug
              • Hibernate-validator中使用
              • WebMvc的基本流程
              • 业务代码中使用国际化文案
                • 国际化生效
                  • 使用国际化
                    • 业务代码中使用
                      • 测试
                      相关产品与服务
                      容器服务
                      腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                      领券
                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档