敞开生长之旅!这是我参与「日新计划 2 月更文应战」的第 4 天,点击检查活动概况
经过Apollo和nacos的才能进行世界化热更新
1.apollo的主动改写
Apollo(阿波罗)是一款牢靠的分布式装备管理中心,有了它,咱们能够用来做许多事情:装备的热更新,装备监听,灰度发布,微服务的多环境装备阻隔等。项目中引进apollo-client
,就能够做到已上功用。
maven依赖:
<dependency>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo-client</artifactId>
<version>1.8.0</version>
</dependency>
经过注解@ApolloConfigChangeListener
界说监听逻辑,能够指定监听的namespace,能够指定感兴趣的key。就像是事情机制一样,当一个事情感兴趣的事情过来的时候,咱们能够监听并处理一些特别的操作。
界说一个装备类而且注入到spring容器中就收效了。
public class ApolloListenerConfig {
@ApolloConfigChangeListener({"test-i18n_zh_CN"})
public void propertiesChanged(ConfigChangeEvent changeEvent) {
log.info("changeEvent namespace {}", changeEvent.getNamespace());
Set<String> set = changeEvent.changedKeys();
set.forEach(key -> {
ConfigChange change = changeEvent.getChange(key);
String newValue = change.getNewValue();
if (change.getChangeType() == PropertyChangeType.DELETED) {
properties.remove(key);
} else if (change.getChangeType() == PropertyChangeType.MODIFIED) {
properties.setProperty(key, newValue);
} else {
properties.put(key, newValue);
}
});
}
}
返回值: changeEvent namespace test-i18n_zh_CN,测验成功,后续咱们会运用这个监听机制来完成本地世界化装备的改写。其中对于每一个key都能够有增删改的事情,以方便咱们进行特别的处理。
2.凭借apollo完成言语装备的界面化
接下来便是凭借apollo的装备界面来完成咱们的言语装备的界面化。咱们会在项目对应的apollo装备中新增一个新的namespace:test-i18n_zh_CN
,它会对应咱们本地的世界化装备文件test-i18n_zh_CN.properties
。
在项目中resource目录下创立test-i18n_zh_CN.properties
,其中内容为:
welcome=你好,世界!
咱们能够随意的运用apollo的装备界面进行装备的增删改,而且还能够进行回滚,前史审计,环境阻隔,然后保障装备的动态远程装备,阻隔性,可审计。
至此,主要是把apollo的一些界面操作做了一些铺垫,咱们在界面的操作,客户端会准实时的感知到,而且根据上文提到的监听,咱们能按需进行一些操作。
上一篇文章《从源码看Spring的i18n高雅的世界化实战》,咱们对spring的I18n的组件MessageSource进行了源码分析,并对比了他的三个完成。接下来,咱们谈谈怎样怎么结合apollo的才能做到言语装备的动态改写。
3.Apollo&i18n的动态改写
3.1 纯编码内存计划
《从源码看Spring的i18n高雅的世界化实战》一文也提到过StaticMessageSource
能够经过编码的方式来自界说装备源。从这一点出发,咱们能够凭借apollo去维护内存中的装备信息。
首要,自界说MineApolloStaticMessageSource
,界说言语装备的初始化。
@Component
@Slf4j
public class MineApolloStaticMessageSource extends StaticMessageSource implements InitializingBean {
private List<String> namespaces= Arrays.asList("test-i18n_zh_CN","test-i18n_en_UK");
@Override
public void afterPropertiesSet() throws Exception {
for (String namespace : namespaces) {
final Config config = ConfigService.getConfig(namespace);
final Set<String> keys = config.getPropertyNames();
for (String key : keys) {
final String value = config.getProperty(key, "");
if (StringUtils.isNotBlank(value)){
final String[] locate = namespace.split("_");
addMessage(key,new Locale(locate[1],locate[2]),value);
}
}
}
}
}
然后,界说监听装备更新,改写内存中的世界化装备。
@ApolloConfigChangeListener({"test-i18n_zh_CN","test-i18n_en_UK"})
public void propertiesChanged(ConfigChangeEvent changeEvent) {
try {
final String namespace = changeEvent.getNamespace();
Set<String> set = changeEvent.changedKeys();
set.forEach(key -> {
ConfigChange change = changeEvent.getChange(key);
String newValue = change.getNewValue();
final String[] locate = namespace.split("_");
if (change.getChangeType() == PropertyChangeType.DELETED) {
messageSource.addMessage(key,new Locale(locate[1],locate[2]),"");
}else {
messageSource.addMessage(key,new Locale(locate[1],locate[2]),newValue);
}
log.info(" namespace {} key {} newValue {}",namespace,key,newValue);
});
} catch (Exception e) {
log.warn("", e);
}
}
apollo装备界面中咱们界说了俩个namespace,分别是”test-i18n_zh_CN
“,”test-i18n_en_UK
“,而且用于测验现已添加了一些key。
项目启动之后,咱们做个简略的测验。
Locale zhLocale=new Locale("zh","CN");
Locale enLocale=new Locale("en","UK");
final String zhMessage = apolloStaticMessageSource.getMessage(code, null, zhLocale);
final String enMessage = apolloStaticMessageSource.getMessage(code, null, enLocale);
log.info("坐落我国,音讯为 {}",zhMessage);
log.info("in UK,message {}",enMessage);
检查日志,能够看到俩条输出:
坐落我国,音讯为 你好,世界!!
in UK,message hello world!
3.2 结合静态装备文件计划
MessageResource还有一个完成ReloadableResourceBundleMessageSource
,经过监听本地文件然后能够在指定时间内改写世界言语的装备。在这里咱们也来完成一下这个计划。
首要,先创立ReloadableResourceBundleMessageSource
实例,并注入到spring容器中,咱们能够界说言语装备的存储目录,缓存的改写检查距离,设置言语编码等。
@Bean
public ReloadableResourceBundleMessageSource i18nMessageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("classpath:i18n/message");
messageSource.setCacheSeconds(120);
messageSource.setUseCodeAsDefaultMessage(true);
messageSource.setDefaultEncoding("utf-8");
log.info("*** i18n message source loaded ***");
return messageSource;
}
在resource目录下,有俩个装备文件,
“
test-i18n_zh_CN.properties
““
test-i18n_en_UK.properties
“
apollo装备界面中咱们界说了俩个namespace,分别是”test-i18n_zh_CN
“,”test-i18n_en_UK
“,和上文图片共同。
最大的不同是对于监听事情的处理需求定制化,根据ReloadableResourceBundleMessageSource
的改写静态文件机制来完成世界化言语装备的改写。
@ApolloConfigChangeListener({"test-i18n_zh_CN","test-i18n_en_UK"})
public void propertiesChanged2(ConfigChangeEvent changeEvent) {
try {
String resourceName = changeEvent.getNamespace() + ".properties";
log.info("resourceName:{}", resourceName);
final File file = new File(getClass().getProtectionDomain().getCodeSource().getLocation().getPath() + "/" + resourceName);
Properties properties = new Properties();
properties.load(new InputStreamReader(new FileInputStream(file), Charset.forName("UTF-8")));
Set<String> set = changeEvent.changedKeys();
set.forEach(key -> {
ConfigChange change = changeEvent.getChange(key);
String newValue = change.getNewValue();
if (change.getChangeType() == PropertyChangeType.DELETED) {
properties.remove(key);
} else if (change.getChangeType() == PropertyChangeType.MODIFIED) {
properties.setProperty(key, newValue);
} else {
properties.put(key, newValue);
}
});
OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8);
properties.store(writer, "properties reload");
} catch (Exception e) {
log.warn("properties reload error", e);
}
}
项目启动之后,咱们做个简略的测验。
Locale zhLocale=new Locale("zh","CN");
Locale enLocale=new Locale("en","UK");
final String zhMessage = apolloStaticMessageSource.getMessage(code, null, zhLocale);
final String enMessage = apolloStaticMessageSource.getMessage(code, null, enLocale);
log.info("坐落我国,音讯为 {}",zhMessage);
log.info("in UK,message {}",enMessage);
修改apollo的装备,能够发现message和apollo共同,动态装备收效。检查日志,能够看到俩条输出:
坐落我国,音讯为 three.body in UK,message Dark Forest Rule
一起,咱们也用到了ReloadableResourceBundleMessageSource
定时缓存改写机制,在apollo更新之后,120秒之后,才会感知新的装备,读取新的message。
3.3 优化点初始改写静态文件
在测验的过程中,发现这种方式下存在一个小问题,便是apollo不触发改写,本地的静态言语装备和apollo是不共同的,就算咱们服务发布上线了,也只有本地文件中的默认值,这样就出问题了。所以咱们需求在项目启动时去初始更新apollo中的装备到本地文件:
@Override
public void afterPropertiesSet() throws Exception {
for (String namespace : namespaces) {
final Config config = ConfigService.getConfig(namespace);
final Set<String> keys = config.getPropertyNames();
Properties properties = new Properties();
String resourceName = namespace + ".properties";
log.info("resourceName:{}", resourceName);
final File file = new File(getClass().getProtectionDomain().getCodeSource().getLocation().getPath() + "/" + resourceName);
for (String key : keys) {
final String value = config.getProperty(key, "");
if (StringUtils.isNotBlank(value)){
properties.setProperty(key,value);
}
}
OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8);
properties.store(writer, "properties reload");
}
}
经过测验,在项目启动之后,本地文件的内容会被apollo改写,一起定时缓存改写机制也ok,这姿态就能够根据以上的计划进行世界化动态装备了。
4.根据Nacos的热更新
根据ReloadableResourceBundleMessageSource
+nacos
相同能够做到上文的效果,原理都是想通的,咱们凭借nacos的装备界面装备多个言语装备,其实实质来说,他们就映射本地言语装备的properties。
然后便是改造ReloadableResourceBundleMessageSource
的办法,改造读取properties的办法,只需求将从本地文件读取变成改成从nacos中读取即可。
final Properties props = newProperties();
final String dataId = filename + NacosConstants.PROPERTIES_SUFFIX;
logger.info("Loading properties for " + dataId);
final String config = nacosConfigManager.getConfigService().getConfig(dataId, nacosGroup, 3000);
还有便是改造refreshProperties
改写的流程,咱们经过nacos的监听机制,获取到改写事情之后,就能够直接改写本地缓存中的properties,实质上和上文一样,更新的是缓存中的properties。
ConfigService configService = nacosConfigManager.getConfigService();
configService.addListener("fileName.properties", "group", new Listener() {
@Override
public Executor getExecutor() {
return executor;
}
@Override
public void receiveConfigInfo(String configInfo) {
//改写缓存的properties,省略
}
});
已上,便是根据nacos的改写计划。此计划省略了本地的静态文件,依赖于nacos的装备拉取。
其实,apollo也能够这姿态的思路去完成。就只论装备中心来说,比较于nacos,apollo的环境阻隔性,审计才能,权限操控更胜一筹。nacos想要有这些细粒度的功用就得上付费版的MSE套件了。
世界化计划的完成有许多,还需求前端的配合,比方Vue也有世界化计划,正在用于出产环境的计划需求好好推敲,适合自己业务的计划才是好的计划。