SpringBoot国际化配置分析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/icarusliu/article/details/82732089

DispatcherServlet使得开发人员可以通过客户端的Locale来自动转换消息进行国际化;在整个国际化的过程中,主要分成两步,一是解析客户端的Locale,一是查找国际化的消息;

1. 解析Locale

解析Locale是通过LocaleResolver来完成的。

当有请求时,DispatcherServlet查找LocaleResolver类型的Bean,如果找到,则使用它获取并设置Locale。在这之后,通过RequestContext.getLocale方法则可以获取到Locale。

Spring默认在i18n包中提供了几种实现,如通过Cookie、Header、Session等方式来获取Locale的实现等。

1.1 定义所使用的LocaleResolver:

Spring Boot中可以通过Bean注解来定义所使用的LocaleResolver

@Configuration
public class AdditionalBeanConfig {
    @Bean
    public LocaleResolver getLocalResolver() {
        return new CookieLocaleResolver();
    }
}

默认情况下,SpringBoot中是没有包含LocaleResolver对象的。

1.2 获取Locale

如果需要在应用中获取当前客户端所使在的Locale,在Controller中通过以下方式查找Locale:

@RestController
@RequestMapping("/test")
public class TestController {
    @GetMapping("/locale")
    public String locale(HttpServletRequest request) {
        // 第一种方式,通过构建RequestContext来查找Locale
        RequestContext requestContext = new RequestContext(request);
        System.out.println(requestContext.getLocale().toLanguageTag());

        // 第二种方式, 通过使用LocaleContextHolder来获取Locale
        System.out.println(LocaleContextHolder.getLocale().toLanguageTag());

        // 第二种方式,通过RequestContextUtils来获取Locale
        return RequestContextUtils.getLocale(request).toLanguageTag();
    }
}

打印及返回的结果为:zh-CN

RequestContext:用于存储请求相关的一些信息,如当前请求的Web上下文、Theme、Locale等;
RequestContextUtils: 使用RequestContext的一个辅助类;提供静态方法来使用RequestContext,这样就不需要手动创建RequestContext对象;

2. 查找消息

查找消息一般通过MessageSource接口进行,它可以根据Code查找消息,同时支持国际化及参数传递。

2.1 MessageSource接口

当不配置任何的MessageSource时,Spring默认使用DelegatingMessageSource,如以下控制器:

@GetMapping("/message")
public String message() {
    return messageSource.toString();
}

请求返回后是:org.springframework.context.support.DelegatingMessageSource@76b6471e

这个时候,我们使用MessageSource来获取Message:

@GetMapping("/message")
public String message(HttpServletRequest request) {
    Locale locale = RequestContextUtils.getLocale(request);
    return messageSource.getMessage("test", null, locale);
}

此时会报错,错误信息:

There was an unexpected error (type=Internal Server Error, status=500).
No message found under code 'test' for locale 'zh_CN'.

经过跟踪,这个异常在DelegatingMessageSource的getMessage方法中处理:

@Override
public String getMessage(String code, @Nullable Object[] args, Locale locale) throws NoSuchMessageException {
    if (this.parentMessageSource != null) {
        return this.parentMessageSource.getMessage(code, args, locale);
    }
    else {
        throw new NoSuchMessageException(code, locale);
    }
}

由于parentMessageSource为空,直接抛出异常,即访问接口时看到的消息。

2.2 指定MessageSource

MessageSource包含有以下几个实现类:

  • StaticMessageSource: 基于程序注册的方式来管理资源;一般用于测试;
  • ResourceBundleMessageSource: 基于JDK中的ResourceBundle实现;
  • ReloadableResourceBundleMessageSource:包含运行时动态加载机制的实现;

2.2.1 StaticMessageSource

先使用StaticMessageSource来进行实验:

@Bean
public MessageSource messageSource() {
    StaticMessageSource messageSource =  new StaticMessageSource();
    messageSource.addMessage("test", Locale.CHINA, "测试消息");

    return messageSource;
}

仍旧使用之前的Controller,这时不会报错直接会返回“测试消息”这个串了。

2.2.2 ResourceBundleMessageSource

上面已经说过,StaticMessageSource这种方式一般用于测试当中,在生产中一般使用ResourceBundleMessageSource。

我们将上面使用staticMessageSource的地方进行替换:

@Bean
public MessageSource messageSource() {
    ResourceBundleMessageSource messageSource =  new ResourceBundleMessageSource();

    // 通过setBasenames来设置国际化文件
    messageSource.setBasenames("messages");

    // 也可以指定多个,如:
    //        messageSource.setBasenames("messages", "errors");

    return messageSource;
}

它会去Classpath下的目录中查找名称为mesages开头的国际化文件并进行加载。

在resources目录下添加messages.properties及messages_zh_cn.properties两个文件,并在message_zh_cn.properties中添加test=测试消息 的内容。

执行前面的Controller,可以看到获取到的消息。也可以仅在messages.properties中添加;它会先去对应Locale的文件中查找,如果未找到则去默认的messages.properties中查找;如果仍旧未找到则抛出异常。

有时候resources目录东西过多需要分子目录,可以在定义MessageSource的时候进行指定,如下面在resources下增加子目录i18n:

 @Bean
public MessageSource messageSource() {
    ResourceBundleMessageSource messageSource =  new ResourceBundleMessageSource();

    // 通过setBasenames来设置国际化文件
    messageSource.setBasenames("i18n.messages");

    // 也可以指定多个,如:
    //        messageSource.setBasenames("messages", "errors");

    return messageSource;
}

3. 总结

综上,在项目国际化过程中,需要创建LocaleResolver及MessageSource对象并注入到容器中,然后定义相关的国际化属性文件;在文件中定义好相关信息即可在需要使用的地方通过Autowired注入MessageSource对象并使用它来解析国际化消息了。

注意到在每个使用的地方都要传入Locale到MessageSource的方法中,实际上,通过使用LocaleContextHolder可以进行进一步的简化:

@Component
public class MessageSourceProxy {
    @Autowired
    private MessageSource messageSource;

    public String getMessage(String code, Object[] params) {
        return messageSource.getMessage(code, params, LocaleContextHolder.getLocale());
    }
}

然后改造前面的Controller:

@Autowired
private MessageSourceProxy messageSourceProxy;

@GetMapping("/message")
public String message(HttpServletRequest request) {
    //        Locale locale = RequestContextUtils.getLocale(request);
    //        return messageSource.getMessage("test", null, locale);

    return messageSourceProxy.getMessage("test", null);
}

这样简化后,使用者甚至都不用手动去获取Locale了。

本文主要分析国际化的配置过程是怎样完成的,关于MessageSource对象的使用,可以参考具体的API。

猜你喜欢

转载自blog.csdn.net/icarusliu/article/details/82732089