download:体系课大数据工程师2022版2.0升级版含源码ppt
编程猿自学it java python go c
基于springboot的国际化解决方案
1底漆
1.1国际化概述
作为一名服务器端开发人员,这里我想从自己的角度简单概述一下国际化(国际化也叫i18n,因为I和N之间有18个字母)是做什么的:
在实际的国际化项目中,责任是让服务器根据客户指定的语言,返回相应语言的内容。
1.2 spring/spring boot项目中国际化游戏性概述
在spring/springboot的世界中,国际游戏基于以下界面:
org . spring framework . context . message source .
该接口主要定义了以下三种方法:
公共接口消息源{
//如果国际化配置文件中找不到var1对应的消息,可以给它一个默认值var3。
@Nullable
String getMessage(String var1,@Nullable Object[] var2,@Nullable String var3,Locale var 4);
//如果在国际化配置文件中找不到var1对应的消息,抛出异常
String getMessage(String var1,@Nullable Object[] var2,Locale var3)抛出NoSuchMessageException
//这个方法暂时没有做太多的研究,所以本文也不会涉及。
string getMessage(MessageSourceResolvable var 1,Locale var2)抛出NoSuchMessageException
}
复制代码
该接口的三个重要实现类如下:
ResourceBundleMessageSource
ReloadableResourceBundleMessageSource
静态消息源
2如何播放ResourceBundleMessage源(默认)
首先我们来看看springboot项目中国际化最基本的玩法:
(1)构建springboot项目(至少添加web依赖)。
Messages.properties(默认配置)
用户名=溜溜球
复制代码
messages_en_US .属性
user.name=yoyo-EN
user.name1=nrsc
user.name2=nrsc{0}-{1}
复制代码
消息_zh_CN.properties
用户名1=张耳
User.name2=张耳{0}-{1}
复制代码
(3)创建一个控制器测试类
@RestController
公共类i18d民主控制器{
@自动连线
私有MessageSource messageSource
@GetMapping("/hello ")
公共字符串hello() {
String defaultM = messageSource
。getMessage("user.name ",null,locale context holder . getlocale());
字符串message1 =消息源
。getMessage("user.name1 ",null,locale context holder . getlocale());
字符串message2 = messageSource
。getMessage("user.name2 ",new String[]{"WW "," MM"},locale context holder . getlocale());
字符串message3 = messageSource
。getMessage("user.nameXX ",null," defaultName ",locale context holder . getlocale());
返回default m+" "+message 1+"-"+message 2+" # # "+message 3;
}
}
复制代码
小贴士:
①之所以可以通过@Autowired直接从spring容器中获取MessageSource,是因为如果没有配置这种类型的bean,spring容器会默认初始化一个这种类型的bean——ResourceBundleMessage Source,并放入spring容器中。答案在下面的源文件中:
org . spring framework . boot . auto configure . context . messagesource auto configuration .
②LocaleContextHolder.getLocale()可以获取该请求头中Accept-Language对应的语言环境。
3如何播放ReloadableResourceBundleMessage源码
ReloadableResourceBundleMessageSource和ResourceBundleMessageSource之间最重要的区别在于
前者可以读取位于。属性和。xml,而后者只能读取。属性。
前者可以指定映射文件缓存在内存中的时间,而后者不能。
ReloadableResourceBundleMessage源码具体玩法如下:
(2)创建ReloadableResourceBundleMessage源,并将其注入到spring容器中。代码如下:
@ Bean(" reloadableResourceBundleMessageSource ")
public message source initReloadableResourceBundleMessageSource(){
ReloadableResourceBundleMessageSource message source = new ReloadableResourceBundleMessageSource();
//指定读取国际化配置文件的基本名称。
message source . set basename(resource utils。class path URL PREFIX+" i18n/messages ");
//指定代码
message source . setdefaultencoding(" UTF-8 ");
//指定缓存时间
message source . setcacheseconds(60);
返回messageSource
}
复制代码
(3)将注入的MessageSource指定为ReloadableResourceBundleMessage Source。
@自动连线
@ Qualifier(" reloadableResourceBundleMessageSource ")
私有MessageSource messageSource
复制代码
(4)测试留给读者。
4如何播放staticmessagesource
ReloadableResourceBundleMessageSource和ResourceBundleMessageSource基于本地文件,而StaticMessageSource相对简单。它的基本玩法如下:
(1)创建一个StaticMessageSource,同时指定国际化映射内容,然后放入spring容器。代码如下:
/**
*代码和消息可以来自数据库或任何其他文件系统。
- @返回
*/
@Bean("staticMessageSource ")
public message source initStaticMessageSource(){
static message source message source = new static message source();
message source . add message(" user . name ",区域设置。美国,“yoyo-EN”);
message source . add message(" user . name 1 ",区域设置。美国,“nrsc”);
message source . add message(" user . name 2 ",区域设置。美国,“nrsc { 0 }-{ 1 }”;
message source . add message(" user . name ",locale.china,"张耳");
message source . add message(" user . name 1 ",locale.china," Zhang er 1 ");
message source . add message(" user . name 2 ",locale.china,"张耳{ 0 }-{ 1 } ");
返回messageSource
}
复制代码
(2)将注入的消息源指定为StaticMessageSource。
@自动连线
//@限定符(" reloadableResourceBundleMessageSource ")
@ Qualifier(" static message source ")
私有MessageSource messageSource
复制代码
(3)测试留给读者。
5 DIY
5.1为什么要DIY?
首先,假设你的项目国际化实施方案中有以下两个技术需求:
需要翻译的内容很多,需要结构化的存储和管理(比如存储在mysql数据库中)。
希望你能用redis做缓存。
这个时候你会发现spring/springboot提供的以上三款游戏好像都不行。这个时候,我们就不得不考虑DIY了。
5.2从StaticMessageSource源代码中寻找DIY灵感
根据我的经验,做DIY最好的方法就是模仿和改造源代码。上面介绍的三种游戏中,StaticMessageSource对应的游戏应该是最简单的,也是最好入手的。下面简单看一下它的源代码:
公共类StaticMessageSource扩展了AbstractMessageSource {
/*从“代码+区域设置”键映射到消息字符串。/
//保存密钥和消息的映射[密钥格式示例:user.name1_zh_CN]
私有最终映射消息= new HashMap();
//保存键和消息格式的映射
//当消息中有占位符时,将使用此映射(如上面示例中的nrsc{0}-{1})
private final Map cachedMessageFormats = new HashMap();
//给定代码和地区(即地区),从消息图中取出相应语言的消息。
@覆盖
受保护的字符串resolveCodeWithoutArguments(字符串代码,区域设置区域设置){
返回this . messages . get(code+' _ '+locale . tostring());
}
//给定的代码和区域设置(即区域设置)来自cachedMessageFormats映射
//取出对应语言的MessageFormat,父类会结合占位符等信息,解析得到具体的消息。
@覆盖
@Nullable
受保护的消息格式resolveCode(字符串代码,区域设置){
string key = code+' _ '+locale . tostring();
string msg = this . messages . get(key);
if (msg == null) {
返回null
}
//这里采用的是懒加载方式。首先,cachedMessageFormats的映射是空的。
//调用时,根据消息和本地生成消息格式
//然后将生成的MessageFormat放入cachedMessageFormats的映射中。
synchronized(this . cachedmessageformats){
message format message format = this . cachedmessageformats . get(key);
if (messageFormat == null) {
message format = create message format(msg,locale);
this . cachedmessageformats . put(key,message format);
}
返回消息格式;
}
}
/**
*将给定的消息与给定的代码相关联。
- @param code查找代码
- @param locale应该在其中找到消息的区域设置
- @param msg与此查找代码相关联的消息
*/
//添加消息
public void addMessage(字符串代码、区域设置、字符串消息){
Assert.notNull(code,“代码不得为空”);
Assert.notNull(区域设置,“区域设置不得为空”);
Assert.notNull(msg,“消息不得为空”);
this . messages . put(code+' _ '+locale . tostring()、msg);
if (logger.isDebugEnabled()) {
logger.debug("为代码[" + code + "]和区域设置[" + locale + "]")添加了消息["+msg+"]";
}
}
/**
*将给定的消息值与给定的密钥关联为代码。
- @param messages要注册的消息,带有消息代码
*作为键,消息文本作为值 - @param locale应该在其中找到消息的区域设置
*/
//批量添加消息
public void addMessages(地图消息,区域设置){
Assert.notNull(messages,“消息映射不能为空”);
messages.forEach((code,msg) -> addMessage(code,locale,msg));
}
@覆盖
公共字符串toString() {
返回getClass()。getName()+":"+this . messages;
}
}
复制代码
从上面的源代码来看,其实很简单。
5.3用redis做DIY缓存
下面是一个简单的用redis进行缓存的DIY方案。
(1)在redis中存储数据
@自动连线
私有RedisTemplate redisTemplate
@测试
public void initData() {
List messageInfos = Arrays.asList(
新的MessageInfo("user.name ",区域设置。US.toString()," yoyo-EN "),
新的MessageInfo("user.name1 ",区域设置。US.toString()," nrsc "),
新的MessageInfo("user.name2 ",区域设置。US.toString()," nrsc{0}-{1} ",
消息信息("用户名",locale.china.tostring(),"张耳"),
消息信息(" user.name1 ",locale.china.tostring()," Zhanger1 "),
消息信息(" user.name2 ",locale.china.tostring(),"张耳{0}-{1} ")
);
redisTemplate.opsForValue()。set("userInfo ",message infos);
}
复制代码
(2)模仿静态消息源定制消息源
@Component("myMessageSource ")
公共类MyMessageSource扩展了AbstractMessageSource {
private final Map cachedMessageFormats = new HashMap();
@自动连线
私有RedisTemplate redisTemplate
@覆盖
受保护的字符串resolveCodeWithoutArguments(字符串代码,区域设置区域设置){
map map = getMessagesMap();
返回map . get(code+' _ '+locale . tostring());
}
私有地图getMessagesMap() {
对象userInfoList = redis template . ops for value()。get(" userInfo ");
List message infolist =(List)userInfoList;
map map = new HashMap();
for(消息信息消息信息:消息信息列表){
string key = message info . get code()+' _ '+message info . get locale();
map . computeifaxine(key,k-> message info . getmessage());
}
返回地图;
}
@覆盖
@Nullable
受保护的消息格式resolveCode(字符串代码,区域设置){
string key = code+' _ '+locale . tostring();
String msg = getMessagesMap()。get(键);
if (msg == null) {
返回null
}
synchronized(this . cachedmessageformats){
message format message format = this . cachedmessageformats . get(key);
if (messageFormat == null) {
message format = create message format(msg,locale);
this . cachedmessageformats . put(key,message format);
}
返回消息格式;
}
}
}
复制代码
(3)将注入的MessageSource指定为我的自定义MyMessageSource。
@自动连线
//@限定符(" reloadableResourceBundleMessageSource ")
//@限定符(" staticMessageSource ")
@限定符(" myMessageSource ")
私有MessageSource messageSource
复制代码
(4)测试留给读者。