IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 基于springboot的国际化解决方案 -> 正文阅读

[Java知识库]基于springboot的国际化解决方案

alt

本文对应源码地址: https://github.com/nieandsun/NRSC-STUDY/tree/master/i18n-study

1 引子

1.1 国际化简单概述

作为一个服务端开发人员,这里我想先站在自己的角度对国际化(internationalization,因在i和n之间共有18个字母,所以国际化也称为i18n)所要做的事做一个简单的概述:

国际化在实际项目中所要承担的职责是按照客户指定的语言让服务端返回相应语言的内容。


1.2 spring/springboot工程中国际化玩法概述

在spring/springboot的世界里,国际化的玩法是基于如下接口的: org.springframework.context.MessageSource。 在该接口里主要定义了如下三个方法:

public?interface?MessageSource?{
????//如果在国际化配置文件中找不到var1对应的message,可以给一个默认值var3
????@Nullable
????String?getMessage(String?var1,?@Nullable?Object[]?var2,?@Nullable?String?var3,?Locale?var4);
?//如果在国际化配置文件中找不到var1对应的message,抛出异常
????String?getMessage(String?var1,?@Nullable?Object[]?var2,?Locale?var3)?throws?NoSuchMessageException;
?//这个方法暂时没有做太多研究,所以本篇文章也不会涉及
????String?getMessage(MessageSourceResolvable?var1,?Locale?var2)?throws?NoSuchMessageException;
}

该接口比较重要的三个实现类如下:

  • ResourceBundleMessageSource
  • ReloadableResourceBundleMessageSource
  • StaticMessageSource

它们与MessageSource间的继承关系如下: 在这里插入图片描述 接下来将对这个三个类的玩法进行具体地介绍。

2 ResourceBundleMessageSource的玩法(默认)

首先来看一下在springboot项目中国际化最基础的玩法:

(1)搭建一个springboot项目(至少添加web依赖)。

(2)创建国际化配置文件,如下图所示: 在这里插入图片描述

  • messages.properties(默认配置)
?user.name=yoyo
  • messages_en_US.properties
user.name=yoyo-EN
user.name1=nrsc
user.name2=nrsc{0}-{1}
  • messages_zh_CN.properties
user.name1=章尔
user.name2=章尔{0}-{1}

(3)创建controller测试类

@RestController
public?class?I18nDemoController?{
????@Autowired
????private?MessageSource?messageSource;

????@GetMapping("/hello")
????public?String?hello()?{
????????String?defaultM?=?messageSource
????????????????.getMessage("user.name",?null,?LocaleContextHolder.getLocale());
????????String?message1?=?messageSource
????????????????.getMessage("user.name1",?null,?LocaleContextHolder.getLocale());
????????String?message2?=?messageSource
????????????????.getMessage("user.name2",?new?String[]{"WW",?"MM"},?LocaleContextHolder.getLocale());
????????String?message3?=?messageSource
????????????????.getMessage("user.nameXX",?null,?"defaultName",?LocaleContextHolder.getLocale());

????????return?defaultM?+?"<->"?+?message1?+?"--"?+?message2?+?"##"?+?message3;
????}
}

tips:

① 直接可以从spring容器里通过@Autowired拿到MessageSource的原因是如果没有配置该类型的bean时,spring容器会默认初始化一个该类型的bean-ResourceBundleMessageSource 放到spring容器里。答案在如下源码文件中: org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration

②LocaleContextHolder.getLocale()可以拿到本次请求header里的Accept-Language对应的语言环境 。

(4)简单测试一种场景,结果如下(其他场景留给读者): 在这里插入图片描述

3 ReloadableResourceBundleMessageSource的玩法

ReloadableResourceBundleMessageSource 相比于ResourceBundleMessageSource 而言最重要的区别在于

  • 前者可以读取.properties和.xml结尾的国际化映射文件,后者只可以读取.properties结尾的文件
  • 前者可以指定映射文件在内存中缓存的时间,后者不可以

ReloadableResourceBundleMessageSource的具体玩法如下:

(1)创建配置化文件,如下图: 在这里插入图片描述

(2)创建ReloadableResourceBundleMessageSource,将其注入到spring容器,代码如下:

@Bean("reloadableResourceBundleMessageSource")
public?MessageSource?initReloadableResourceBundleMessageSource()?{
????ReloadableResourceBundleMessageSource?messageSource?=?new?ReloadableResourceBundleMessageSource();
????//指定读取国际化配置文件的basename
????messageSource.setBasename(ResourceUtils.CLASSPATH_URL_PREFIX?+?"i18n/messages");
????//指定编码
????messageSource.setDefaultEncoding("UTF-8");
????//指定缓存时间
????messageSource.setCacheSeconds(60);
????return?messageSource;
}

(3)指定注入的MessageSource为ReloadableResourceBundleMessageSource

@Autowired
@Qualifier("reloadableResourceBundleMessageSource")
private?MessageSource?messageSource;

(4)测试留给读者

4 StaticMessageSource的玩法

ReloadableResourceBundleMessageSourceResourceBundleMessageSource都是基于本地文件的,而StaticMessageSource就比较简单了,它的基本玩法如下:

(1)创建StaticMessageSource,同时指定国际化映射内容,然后将其放入spring容器,代码如下:

/****
?*?code和msg可以来自于数据库,或者其他任何文件系统
?*?@return
?*/

@Bean("staticMessageSource")
public?MessageSource?initStaticMessageSource()?{
????StaticMessageSource?messageSource?=?new?StaticMessageSource();

????messageSource.addMessage("user.name",?Locale.US,?"yoyo-EN");
????messageSource.addMessage("user.name1",?Locale.US,?"nrsc");
????messageSource.addMessage("user.name2",?Locale.US,?"nrsc{0}-{1}");

????messageSource.addMessage("user.name",?Locale.CHINA,?"章尔");
????messageSource.addMessage("user.name1",?Locale.CHINA,?"章尔1");
????messageSource.addMessage("user.name2",?Locale.CHINA,?"章尔{0}-{1}");
????return?messageSource;
}

(2)指定注入的MessageSource为StaticMessageSource

@Autowired
//@Qualifier("reloadableResourceBundleMessageSource")
@Qualifier("staticMessageSource")
private?MessageSource?messageSource;

(3)测试留给读者

5 DIY

5.1 WHY DIY?

首先假设你的项目里国际化实现方案上有如下两个技术需求:

  • 需要进行翻译的内容较多,要进行结构化的存储与管理(比如存放到mysql数据库)
  • 希望可以借助redis进行缓存

这时你会发现, spring/springboot提供的上面三种玩法貌似都不得行,这时我们就要考虑DIY了。

5.2 从StaticMessageSource源码找寻DIY灵感

依我的经验来看,要进行DIY最好的途径是站在源码的基础上进行模仿和改造。而上面介绍的三种玩法中,StaticMessageSource对应的玩法应该是最简单,也是最好入手的,这里简单撸一下它的源码:

public?class?StaticMessageSource?extends?AbstractMessageSource?{

?/**?Map?from?'code?+?locale'?keys?to?message?Strings.?*/
?//保存key和message的map【key的格式举例:user.name1_zh_CN】
?private?final?Map<String,?String>?messages?=?new?HashMap<>();
?//保存key和MessageFormat的map
?//当message里有占位符时(比如我上面例子里的nrsc{0}-{1}),会用到这个map
?private?final?Map<String,?MessageFormat>?cachedMessageFormats?=?new?HashMap<>();

?//给定code和语言环境(即locale)从messages这个map里取出对应语言的message
?@Override
?protected?String?resolveCodeWithoutArguments(String?code,?Locale?locale)?{
??return?this.messages.get(code?+?'_'?+?locale.toString());
?}
?//给定code和语言环境(即locale)从cachedMessageFormats这个map里
?//取出对应语言的MessageFormat,父类会联合占位符等信息,解析得到具体的message
?@Override
?@Nullable
?protected?MessageFormat?resolveCode(String?code,?Locale?locale)?{
??String?key?=?code?+?'_'?+?locale.toString();
??String?msg?=?this.messages.get(key);
??if?(msg?==?null)?{
???return?null;
??}
??//这里采用了懒加载的方式,一开始的时候cachedMessageFormats这个map是空的
??//当被调用到后会根据msg、local生成MessageFormat
??//然后将生成的MessageFormat放到cachedMessageFormats这个map里
??synchronized?(this.cachedMessageFormats)?{
???MessageFormat?messageFormat?=?this.cachedMessageFormats.get(key);
???if?(messageFormat?==?null)?{
????messageFormat?=?createMessageFormat(msg,?locale);
????this.cachedMessageFormats.put(key,?messageFormat);
???}
???return?messageFormat;
??}
?}
?
?/**
??*?Associate?the?given?message?with?the?given?code.
??*?@param?code?the?lookup?code
??*?@param?locale?the?locale?that?the?message?should?be?found?within
??*?@param?msg?the?message?associated?with?this?lookup?code
??*/

??//添加Message
?public?void?addMessage(String?code,?Locale?locale,?String?msg)?{
??Assert.notNull(code,?"Code?must?not?be?null");
??Assert.notNull(locale,?"Locale?must?not?be?null");
??Assert.notNull(msg,?"Message?must?not?be?null");
??this.messages.put(code?+?'_'?+?locale.toString(),?msg);
??if?(logger.isDebugEnabled())?{
???logger.debug("Added?message?["?+?msg?+?"]?for?code?["?+?code?+?"]?and?Locale?["?+?locale?+?"]");
??}
?}

?/**
??*?Associate?the?given?message?values?with?the?given?keys?as?codes.
??*?@param?messages?the?messages?to?register,?with?messages?codes
??*?as?keys?and?message?texts?as?values
??*?@param?locale?the?locale?that?the?messages?should?be?found?within
??*/

??//批量添加Message
?public?void?addMessages(Map<String,?String>?messages,?Locale?locale)?{
??Assert.notNull(messages,?"Messages?Map?must?not?be?null");
??messages.forEach((code,?msg)?->?addMessage(code,?locale,?msg));
?}


?@Override
?public?String?toString()?{
??return?getClass().getName()?+?":?"?+?this.messages;
?}
}

从上面的源码来看,其实非常简单。

5.3 DO DIY -- 借助redis进行缓存

这里给出一个简单的借助redis进行缓存的DIY方案 (1)向redis里存储数据

@Autowired
private?RedisTemplate<String,?Object>?redisTemplate;

@Test
public?void?initData()?{
????List<MessageInfo>?messageInfos?=?Arrays.asList(
????????????new?MessageInfo("user.name",?Locale.US.toString(),?"yoyo-EN"),
????????????new?MessageInfo("user.name1",?Locale.US.toString(),?"nrsc"),
????????????new?MessageInfo("user.name2",?Locale.US.toString(),?"nrsc{0}-{1}"),
????????????new?MessageInfo("user.name",?Locale.CHINA.toString(),?"章尔"),
????????????new?MessageInfo("user.name1",?Locale.CHINA.toString(),?"章尔1"),
????????????new?MessageInfo("user.name2",?Locale.CHINA.toString(),?"章尔{0}-{1}")
????);
????redisTemplate.opsForValue().set("userInfo",?messageInfos);
}

(2)仿照StaticMessageSource自定义MessageSource

@Component("myMessageSource")
public?class?MyMessageSource?extends?AbstractMessageSource?{

????private?final?Map<String,?MessageFormat>?cachedMessageFormats?=?new?HashMap<>();

????@Autowired
????private?RedisTemplate<String,?Object>?redisTemplate;

????@Override
????protected?String?resolveCodeWithoutArguments(String?code,?Locale?locale)?{
????????Map<String,?String>?map?=?getMessagesMap();
????????return?map.get(code?+?'_'?+?locale.toString());
????}

????private?Map<String,?String>?getMessagesMap()?{
????????Object?userInfoList?=?redisTemplate.opsForValue().get("userInfo");
????????List<MessageInfo>?messageInfoList?=?(List<MessageInfo>)?userInfoList;
????????Map<String,?String>?map?=?new?HashMap<>();
????????for?(MessageInfo?messageInfo?:?messageInfoList)?{
????????????String?key?=?messageInfo.getCode()?+?'_'?+?messageInfo.getLocale();
????????????map.computeIfAbsent(key,?k?->?messageInfo.getMessage());
????????}
????????return?map;
????}

????@Override
????@Nullable
????protected?MessageFormat?resolveCode(String?code,?Locale?locale)?{
????????String?key?=?code?+?'_'?+?locale.toString();
????????String?msg?=?getMessagesMap().get(key);
????????if?(msg?==?null)?{
????????????return?null;
????????}
????????synchronized?(this.cachedMessageFormats)?{
????????????MessageFormat?messageFormat?=?this.cachedMessageFormats.get(key);
????????????if?(messageFormat?==?null)?{
????????????????messageFormat?=?createMessageFormat(msg,?locale);
????????????????this.cachedMessageFormats.put(key,?messageFormat);
????????????}
????????????return?messageFormat;
????????}
????}
}

(3)指定注入的MessageSource为我自定义的MyMessageSource

@Autowired
//@Qualifier("reloadableResourceBundleMessageSource")
//@Qualifier("staticMessageSource")
@Qualifier("myMessageSource")
private?MessageSource?messageSource;

(4)测试留给读者

alt
alt

本文由 mdnice 多平台发布

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-09-15 01:51:23  更:2022-09-15 01:52:25 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 12:48:50-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码