大家好,我是君哥,最近在使用Nacos的过程中遇到了一个场景。配置的字符串可以解析成Map类型。有一个配置如下:map:test:key1:value1,key2:value2,key3:value3后来同事建议Nacos可以直接配置可以转成Map类型,使用Java地图类型在后台。配置如下:map:test:key1:value1key2:value2key3:value3下面分享一下配置Map类型过程中遇到的问题。一、使用Bean方法获取配置1.1使用方法参考了网上的一些案例。第一种方法是将读取的Map当成SpringBean使用,看代码就明白了。@Bean@ConfigurationProperties(prefix="map.test")publicMapmapping(){returnnewHashMap<>();}1.2Slot这样确实可以将Nacos中读入的配置转换为Map类型,但是一个致命槽是映射bean不能自动刷新。这样,如果修改Nacos中的配置,必须重启应用服务才能使配置生效。这怎么能接受呢?2、ConfigurationProperties2.1直接使用@Value和@NacosValue取不到值。在下面的方法中,在类定义中使用@ConfigurationProperties注解,然后定义一个与Nacos中配置的后缀同名的变量,这样就可以获取到Map类型的配置。@Component@RefreshScope@ConfigurationProperties(prefix="map")publicclassNacosRefresh{privateLoggerlogger=LoggerFactory.getLogger(getClass());publicvoidsetTest(Maptest){this.test=test;}privateMaptest;}注意:上面的setTest方法是必须的,否则test变量取不到值。2.2Slots这样在Nacos中读取的配置确实可以转换成Map类型,但是和第一种方式一样,定义的Map类型变量不能自动刷新。3.使用监听NacosAPI提供了一个监听功能,可以监听配置的变化并对变化进行处理,只要在监听方法中添加@NacosConfigListener注解即可生效。见下面代码:@ServicepublicclassNacosListener{privateLoggerlogger=LoggerFactory.getLogger(getClass());privateMapmap=newHashMap<>();@NacosConfigListener(dataId="maptest.yaml",groupId="DEFAULT_GROUP")publicvoidlistener(Stringcontext){logger.info("================listenercontext:{}“,语境);如果(StringUtils.isBlank(上下文)){返回;}Yamlyaml=newYaml();MapcontextMap=yaml.load(context);Mapmap=(Map)contextMap.get("map");如果(CollectionUtils.isEmpty(地图)){返回;}Maptest=(Map)map.get("test");如果(CollectionUtils.isEmpty(测试)){返回;}map.clear();map.putAll(测试);map.forEach((k,v)->logger.info("映射中的条目,key:{},value:{}",k,v));}}这段代码是从Nacos的配置中解析出Map类型的配置,然后将配置放到局部变量map中。这样也可以满足我们的需求,但是有几点需要注意。3.1服务重启如果服务重启,局部变量map无法拉取值。因为上面的监控逻辑没有走,即使重新发布到Nacos上,也是不行的。上述监控方式只有在Nacos配置发生变化并发布后才会触发。例如map.test配置修改如下:map:test:key1:value1key2:value2key3:value3key4:value43.2并发问题上面的监控代码里面,需要先清除局部变量map,然后再putAll.如果这两个方法调用之间发生线程上下文切换,读取线程可能会异常,因为它无法从map中获取值。4.改进上面解释了使用Nacos配置Map类型的弊端,但是使用Nacos配置Map类型也有一个好处。可以不解析字符串,直接转成Map类型。4.1字符串的使用完全没有使用Map类型,改为配置字符串。配置如下:map:test:key1:value1,key2:value2,key3:value3解析代码如下:@NacosValue(value="${map.test}",autoRefreshed=true)privateStringmapTest;publicStringget(Stringkey){String[]keys=mapTest.split(",");for(Stringitem:keys){if(!item.contains(key)){继续;}返回item.split(":")[1];}returnnull;}这种写法的好处是不需要监听Nacos,配置改变后会自动刷新mapTest变量。缺点是每次调用get方法都需要调用ParsethemapTeststring。4.2刷新局部Map,将字符串解析结果放入局部变量map中。考虑到Nacos中的配置可能会发生变化,使用定时线程池1秒刷新一次。代码如下:privateMapmap=newHashMap<>();@NacosValue(value="${map.test}",autoRefreshed=true)privateStringmapTest;@PostConstructpublicvoidrefreshLocalMap(){ScheduledThreadPoolExecutorscheduled=newScheduledThreadPoolExecutor(1);scheduled.scheduleAtFixedRate(()->refresh(),0,1000,TimeUnit.MILLISECONDS);}publicvoidrefresh(){String[]keys=mapTest.split(",");for(Stringitem:keys){String[]kv=item.split(":");map.put(kv[0],kv[1]);}}这种写法的好处是每次调用时都不会解析字符串,而是由异步线程每秒刷新一次。但是也有两个问题:需要定时线程池,会消耗CPU资源。refresh方法每秒执行一次,会存在短命局部变量与Nacos配置不一致的问题。5.总结Nacos中Map类型配置不好,主要是不方便刷新。不过对于不需要刷新配置的场景还是很有好处的,尤其是key比较多的时候,比解析字符串要方便很多,而且Hash的时间复杂度是o(1),也就是最好是数据结构。对于需要刷新的场景,无论采用哪种方案,都有利有弊。没有最好,只有最合适,要根据系统的业务场景来选择。