三招通过Apollo和nacos的能力进行国际化热更新

news2025/4/8 16:05:24

通过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

image-20230214170837520

在项目中resource目录下创建test-i18n_zh_CN.properties,其中内容为:

welcome=你好,世界!

我们可以随意的使用apollo的配置界面进行配置的增删改,并且还可以进行回滚,历史审计,环境隔离,从而保障配置的动态远程配置,隔离性,可审计。

image-20230214171656555

至此,主要是把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。

image-20230215142624724

项目启动之后,我们做个简单的测试。

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。

image-20230215183919868

然后就是改造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也有国际化方案,正在用于生产环境的方案需要好好推敲,适合自己业务的方案才是好的方案。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/385916.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ESP32遥控器软硬件设计

一. 前言 做智能车 或者 四轴飞控怎么能少得了遥控器呢&#xff01;在这里给大家分享一个简单的基于ESP32遥控器的设计&#xff0c;包括软硬件以及3D外壳。 二. 硬件设计 1. 功能介绍 遥控器嘛&#xff0c;通信方式是最重要的&#xff0c;本设计支持 WIFI、蓝牙 和 2.4G&…

raft协议的一些理解

raft是工程上使用较为广泛的强一致性、去中心化、高可用的分布式协议。(类似的还有ZAB,GOSSIP) 大概理解一下,强一致性就是任意时刻客户端在不同机器读到的数据一致.注意跟最终一致性区别,比如数据库mysql的主从复制,主库写了再同步到从库,这个过程是异步的,主库写好就返回客户…

Python高性能编程

一、进程池和线程池 1.串行 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import time import requests url_lists [ http://www.baidu.com, http://fanyi.baidu.com, http://map.baidu.com, http://music.baidu.com/, http://tieba.baid…

项目实战典型案例13——学情页面逻辑问题

学情页面逻辑问题一&#xff1a;背景介绍二&#xff1a;学情页面逻辑问题分析逻辑问题缓存滥用的问题三&#xff1a;LocalStorage基础知识数据结构特性应用场景localStorage常用方法四&#xff1a;总结一&#xff1a;背景介绍 本篇博客是对项目开发中出现的学情页面逻辑问题进…

buu [INSHack2017]rsa16m 1

题目描述&#xff1a; 打开的 rsa_16m 文件 &#xff1a; &#xff08;在此我只想说神人才找得到 c 的位置&#xff09; &#xff0c;这位置是真的难找啊 题目分析&#xff1a; 首先打开 description.md 文件&#xff0c;得到&#xff1a; 翻译下来&#xff1a; 当您需要真正…

青岛诺凯达机械盛装亮相2023济南生物发酵展,3月与您相约

BIO CHINA生物发酵展&#xff0c;作为生物发酵产业一年一度行业盛会&#xff0c;由中国生物发酵产业协会主办&#xff0c;上海信世展览服务有限公司承办&#xff0c;2023第10届国际生物发酵展&#xff08;济南&#xff09;于2023年3月30-4月1日在山东国际会展中心&#xff08;济…

王道C语言督学营OJ练习全解【24考研最新版】

前言 本篇博客是在博主参加王道408专业课前置课程-----C语言督学营的学习笔记&#xff0c;包含了从第一节课到最后一节课的所有OJ习题题解&#xff0c;文章中每一题都给出了详尽的代码&#xff0c;并在每一题的关键部位加上了注释&#xff0c;记录下来的目的是方便自己以后进行…

ElasticSearch从0到1——基础知识

1.ES是什么&#xff1f; 是一个开源的高扩展的分布式全文检索引擎&#xff0c;它可以近乎实时的存储、检索数据&#xff1b;本身扩展性很好&#xff0c;可以扩展到上百台服务器&#xff0c;处理PB级别的数据使用Java开发并使用Lucene作为其核心来实现所有索引和搜索的功能&…

【ElasticSearch8.X】学习笔记(二)

【ElasticSearch8.X】学习笔记四、基础操作4.1、索引操作4.1.1、创建索引4.1.2、查询指定索引4.1.3、查询所有索引4.1.4、 删除索引4.2、文档操作4.2.1、创建文档4.2.2、查询文档4.2.3、修改文档4.2.4、删除文档4.2.5、查询所有文档4.3、数据搜索4.3.1、匹配查询文档4.3.2、匹配…

LeetCode题目笔记——1487. 保证文件名唯一

文章目录题目描述题目链接题目难度——中等方法一&#xff1a;哈希表代码/Python代码/C总结题目描述 给你一个长度为 n 的字符串数组 names 。你将会在文件系统中创建 n 个文件夹&#xff1a;在第 i 分钟&#xff0c;新建名为 names[i] 的文件夹。 由于两个文件 不能 共享相同…

JUC并发编程之JMM_synchronized_volatile

目录 JUC并发编程之JMM_synchronized_volatile 什么是JMM模型&#xff1f; JMM和JVM的区别 JMM不同于JVM内存区域模型 主内存 工作内存 Java内存模型与硬件内存架构的关系 JMM存在的必要性 数据同步八大原子操作 同步规则分析 并发编程的可见性&#xff0c;原子性与有序…

【代码编辑器记录一】vue项目中如何实现代码高亮效果+输入

文章目录1-1 代码高亮显示但不可以实现编辑输入vue-highlightjs1-1-1 vue31-1-2 vue21-2 编辑输入高亮代码&#xff0c;进行格式规范code-mirror1-2-1 展示1-2-2 基本配置1-2-3 使用1-1 代码高亮显示但不可以实现编辑输入vue-highlightjs 1-1-1 vue3 安装依赖 npm install -…

企业电子招投标采购系统之系统的首页设计

​​ 功能模块&#xff1a; 待办消息&#xff0c;招标公告&#xff0c;中标公告&#xff0c;信息发布 描述&#xff1a; 全过程数字化采购管理&#xff0c;打造从供应商管理到采购招投标、采购合同、采购执行的全过程数字化管理。通供应商门户具备内外协同的能力&#xff0c;为…

【知识点】OkHttp 原理 8 连问

前言OkHttp可以说是Android开发中最常见的网络请求框架,OkHttp使用方便&#xff0c;扩展性强&#xff0c;功能强大&#xff0c;OKHttp源码与原理也是面试中的常客但是OKHttp的源码内容比较多&#xff0c;想要学习它的源码往往千头万绪&#xff0c;一时抓不住重点.本文从几个问题…

ThreadLocal 理解及面试

一、ThreadLocal 引用关系 图解关系说明&#xff1a; 每个线程拥有自己的 ThreadLocalMap 属性&#xff1b;ThreadLocalMap 的存储结构为 Entry[] 数组&#xff1b;Entry的Key是ThreadLocal类型且弱引用指向ThreadLocal对象&#xff0c;Value是我们自己定义的泛型值对象&#…

链表的替代品--Vector组件

概述 在之前的一篇文章中&#xff0c;作者写了一个事件组件-- 超精简的订阅发布事件组件–SPEvent&#xff0c;这个组件是采用链表建立所有事件节点的关系的。链表的优缺点&#xff1a; 优点&#xff1a;①链表上的元素在空间存储上内存地址不连续&#xff1b;②在插入和删除操…

注解开发定义bean

注解开发定义bean 使用Component定义bean在核心配置文件中通过组件扫描加载bean&#xff0c;需要指定扫描包的范围 当然也可以使用Component的衍生注解&#xff0c;可以更加形象的表示 纯注解的开发模式 使用java类来代替了以前的 配置文件&#xff0c;在java类中&#xff…

渗透测试之巧用工具搞定sharepoint

背景 在一次实战演练中 goby扫描到一个sharepoint的getshell漏洞 &#xff0c;漏洞cve编号为CVE-2019-0604&#xff0c;本想着一把梭&#xff0c;直接渗透内网&#xff0c;没想到有waf之类的防护&#xff0c;最后还是想办法解决了。 现在网络上各类漏洞利用工具很多&#xff…

项目中用到的知识点回顾---JWT(JSON Web Token)

1.JWT原理&#xff1a; JWT 的原理是&#xff0c;服务器认证以后&#xff0c;生成一个 JSON 对象&#xff0c;发回给用户&#xff0c;如下&#xff1b; {"姓名": "张三","角色": "管理员","到期时间": "2018年7月1日…

【调试】ftrace(一)基本使用方法

简介 Ftrace是Linux Kernel的官方tracing系统&#xff0c;支持Function trace、静态tracepoint、动态Tracepoint的跟踪&#xff0c;还提供各种Tracer&#xff0c;用于统计最大irq延迟、最大函数调用栈大小、调度事件等。 Ftrace还提供了强大的过滤、快照snapshot、实例&#…