Spring Boot、SpringMVC进行i18n国际化支持:使用MessageSource

项目示例仓库

[Coding]https://meostar.coding.net/public/message-source-usage/message-source-usage/git

代码分支说明

  • master分支

无配置,虽然能用,但是强烈建议使用配置。

  • code分支

通過代码配置MessageSource,对应下面第二章节 - 3.2

  • yml分支

通過yml文件配置MessageSource

一、国际化

1. 简述

国际化是什么,简单地说就是,在不修改内部代码的情况下,根据不同语言及地区显示相应的语言界面。

2. Spring官方解释

Spring中对国际化文件支持的基础接口是MessageSource
参照Spring对于MessageSource解释的官方文档:

Strategy interface for resolving messages, with support for the parameterization and internationalization of such messages.
Spring provides two out-of-the-box implementations for production:
ResourceBundleMessageSource, built on top of the standard ResourceBundle
ReloadableResourceBundleMessageSource, being able to reload message definitions without restarting the VM

意思为:

Spring提供了两种开箱即用的实现,一种是标准实现,一种是运行时可重新加载。



本文允许转载,转载本文时请加上本文链接:
https://blog.csdn.net/nthack5730/article/details/82870368
https://www.jianshu.com/p/a354d3f849ec
对于爬虫网站随意爬取以及转载不加原文链接的,本人保留追究法律责任的权力!



二、SpringBoot中使用MessageSource国际化

1. SpringBoot自动化配置国际化支持

Spring Boot已经对i18n国际化做了自动配置,自动配置类为:

org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration

使用MessageSource时只要@Autowired就行:

@Autowired
private MessageSource messageSource;

而Spring在启动的时候装备的实现类是:

org.springframework.context.support.ResourceBundleMessageSource

但是很多人会说:我用了Autowired为什么调用messageSource.getMessage(...)却返回空内容?
这是因为SpringBoot对国际化properties文件路径默认设定是在message文件夹下,找不到文件夹,所以就没有信息返回呗。
下面会进行如何自定义i18n国际化配置讲述。

2. 常见国际化支持配置参数解释

MessageSource国际化配置中有几个参数是常见的,在这里必须要说明下,因为知道遮几个参数才能理解下面的配置:

basename:默认的扫描的国际化文件名为messages,即在resources建立messages_xx.properties文件,可以通过逗号指定多个,如果不指定包名默认从classpath下寻找。
encoding:默认的编码为UTF-8,也可以改为GBK等等
cacheSeconds:加载国际化文件的缓存时间,单位为秒,默认为永久缓存。
fallbackToSystemLocale:当找不到当前语言的资源文件时,如果为true默认找当前系统的语言对应的资源文件如messages_zh_CN.properties,如果为false即加载系统默认的如messages.properties文件。

3. 自定义i18n国际化配置

很多时候我们要修改自动配置中的某些配置参数,例如message.properties文件所在的文件夹路径、properties文件的编码格式等等,可以用以下两种方式进行配置修改,选一个就好:

  1. 修改Bean,直接返回一个配置Bean
  2. 使用配置文件(比较推荐这种做法)

3.1 在application.yml配置文件中

在配置文件中加入下面的配置:(application.properties配置文件麻烦自行转换下)

spring: 
  messages:
    basename: i18n/messages
    encoding: UTF-8

3.2 用Bean进行代码配置

@Configuration下面加入Bean配置就好,下面是完整的类代码:

@Configuration
public class MessageSourceConfig {

    @Bean(name = "messageSource")
    public ResourceBundleMessageSource getMessageSource() throws Exception {
        ResourceBundleMessageSource resourceBundleMessageSource = new ResourceBundleMessageSource();
        resourceBundleMessageSource.setDefaultEncoding("UTF-8");
        resourceBundleMessageSource.setBasenames("i18n/messages");
        return resourceBundleMessageSource;
    }
}

其中:

resourceBundleMessageSource.setBasenames("i18n/messages");

指定了i18n文件的位置,这是个在i18n文件夹下的Resources Bundle文件集合,新建的时候在IDEA里面选择的是Resource Bundle类型,名字写的是messages。这个很重要,对应上方的配置文件!

注意:如果返回值不是MessageSource类型就要在@Bean上面指定name参数,否则无法使用@Autowired进行自动装入,这个是SpringIOC的东西,大家可以去查找对应的资料。

4. 使用i18n国际化

4.1 写入国际化文件

根据上面的配置,在resouces-i18n下面加入Resource Bundle类型文件:
名称为messages,然后加入以下两种类型:

zh_CN
en_US

完成之后会生成下面三个文件:

  1. 【默认】messages.properties
  2. 【英文】messages_en_US.properties
  3. 【中文】messages_zh_CN.properties

在里面写入同样的字段:

hello=xxx

其中xxx在不同文件里面写不同的语句(你能分辨出来就行)

4.2 代码中使用MessageSource

在需要用到的类中@Autowired就可以使用了:

@Autowired
private MessageSource messageSource;

然后使用:

messageSource.getMessage(code, args, defaultMessage, locale);

就可以获得对应的信息内容。

4.3 MessageSource接口中的方法

MessageSource中有三个方法,分别是:

String getMessage(String code, @Nullable Object[] args, @Nullable String defaultMessage, Locale locale);

String getMessage(String code, @Nullable Object[] args, Locale locale) throws NoSuchMessageException;

String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException;

常用的是第一个(主要是其他两个会抛出NoSuchMessageException,不太方便),下面是第一个方法中的参数解释:

code:信息的键,properties中的key
args:系统运行时参数,可为空
defaultMessage:默认信息,可为空
locale:区域信息,我们可以通过java.util.Locale类下面的静态常量找到对应的值。如:简体中文就是zh_CN;英文就是en_US,详见java.util.Locale中的常量值。

测试用例:

@RunWith(SpringRunner.class)
@SpringBootTest(properties = "spring.main.web-application-type=reactive")
public class TestMessageSource {
    private static final Logger logger = LoggerFactory.getLogger(TestMessageSource.class);

    @Autowired
    private MessageSource messageSource;

    @Test
    public void testGetMessage() {
        logger.info(messageSource.getMessage("hello", null, "", null));
    }
}

测试用例中使用了nulllocale参数,这里会输出messages.propertieshello的值,这个参数会在下面4.4节中说明原因。

4.4 指定和默认的国际化信息

上面4.3中说明了MessageSource接口的三个方法,我们最常用的是第一个不抛出异常的方法。其中有一个是locale参数,这个参数的作用就是指定语言环境
java.util.Locale类中定义了很多的语言环境,如:

...
static public final Locale SIMPLIFIED_CHINESE = createConstant("zh", "CN");
static public final Locale CHINA = SIMPLIFIED_CHINESE;
static public final Locale UK = createConstant("en", "GB");
static public final Locale US = createConstant("en", "US");
static public final Locale CANADA = createConstant("en", "CA");
...

MessageSource.getMessage(...)所有方法中,都有Locale参数,指定区域信息。
当调用时,要如果指定位置为:Locale.CHINA,这时ResourceBundelMessageSource会从messages_zh_CN.properties中寻找对应的键值。
当给出Locale参数值为null,空的区域信息;或者对应的properties文件中没有找到对应的键值对,那么ResourceBundelMessageSource默认会从messages.properties中寻找键,当都找不到的时候,会返回空字符串。

三、SpringMVC使用国际化

装配到代码中

有了上面的配置,只要使用:

@Autowired
private MessageSource messageSource;

就能将MessageSource注入。

使用规范

通常,我们都会在ControllerControllerAdvice中进行使用,最好不在Service中使用,这样统一开发规范,Service可以返回properties相对应的key给上层,由Controller层统一处理。
这种做法的原因是在遇到抛异常给统一异常处理ControllerAdvice下的ExceptionHandler)时,异常处理能正确获取到对应的信息。

简化使用

上面的使用例子中,使用:

messageSource.getMessage("hello", null, "", null)

用了4个参数,这4个参数对应的都是地理位置等信息,但这类参数每次在Controller调用的时候都要放一遍,确实很不方便,因为我们只要传进的是key
我们可以考虑让包装一个类去屏蔽这些参数。
当然,下面的例子是忽略地理位置信息的,如果需要带上地理位置信息,可以考虑使用拦截器方式通过前端传回语言信息,再去对应的地方拿对应的信息。

@Component
public class MessageSourceUtil {

    @Autowired
    private MessageSource messageSource;

    public String getMessage(String code) {
        return getMessage(code, null);
    }

    public String getMessage(String code, Object[] args) {
        return getMessage(code, args, "");
    }

    public String getMessage(String code, Object[] args, String defaultMsg) {
        //这里使用比较方便的方法,不依赖request.
        Locale locale = LocaleContextHolder.getLocale();
        return messageSource.getMessage(code, args, defaultMsg, locale);
    }

}

上面使用@Component注解来指定该类是组件,这样讲就能直接在Controller层中使用:

@Autowired
private MessageSourceUtil messageSourceUtil;

然后使用:

messageSourceUtil.getMessage(String);

可以看到我们使用的是LocaleContextHolder.getLocale();来处理locale位置参数,这里指定的是服务器的位置。与客户地理位置误关。如果需要根据客户不同位置,可以使用拦截器方式,请求的时候带上位置信息(最好放在header),然后在拦截器中获取,带参数到这里。当然,上面的类中LocaleContextHolder也需要做对应的修改。

使用例子

在Controller中使用

@RestController
@RequestMapping("/")
public class BaseController {

    @Autowired
    private MessageSourceUtil messageSourceUtil;

    /**
     * Ping一下,神清气爽
     *
     * @param str
     * @return
     */
    @RequestMapping("/ping")
    public ResponseEntity ping(String str) {
        return ResponseEntity.ok(messageSourceUtil.getMessage(str));
    }
}

在全局异常处理中使用

@ControllerAdvice
public class ExceptionHandlerUtil {
    @Autowired
    private MessageSourceUtil messageSourceUtil;

    /**
     * 全局处理业务的异常
     * 通过{@link MessageUtil}直接返回Service中抛出的信息
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(Exception.class)
    public ResponseEntity handleBussinessException(Exception ex) {
        return ResponseEntity.badRequest().body(messageSourceUtil.getMessage(ex.getMessage()));
    }
}

最后:版权声明

本文允许转载,转载本文时请加上本文链接:
https://blog.csdn.net/nthack5730/article/details/82870368
https://www.jianshu.com/p/a354d3f849ec
对于爬虫网站随意爬取以及转载不加原文链接的,本人保留追究法律责任的权力!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350