关于spring:使用Nacos实现Spring的ConfigurationProperties的map类型key动态更新删除不了的问题解决方案

遇到一个nacos配置动静更新map的问题,对于map的key能够新增,不能删除的问题排查,比方我有个

 map:
   a:a
   b:b

这里我能够批改配置进行追加c:c,变成

 map:
   a:a
   b:b
   c:c

这样是能够的,然而不能删除,比方删除掉a:a,后果还是

 map:
   a:a
   b:b
   c:c

源码剖析一波,进行排查
首先要介绍一下spring的各生命周期周期,这里动静更新就用到了RefreshEvent事件,在NacosContextRefresher#registerNacosListener()里,nacos实现了一个批改配置的回调监听,并且会播送一个RefreshEvent事件
而后RefreshEventListener会收到事件,并调用ContextRefresher#refresh()->refreshEnvironment(),执行updateEnvironment(),从新加载Context的Environment,之后持续播送一个EnvironmentChangeEvent事件,ConfigurationPropertiesRebinder接管到该事件后,会调用rebind(),这里就是更改具体@ConfigurationProperties的配置类了,具体做法就是在该办法里调用了

this.applicationContext.getAutowireCapableBeanFactory().destroyBean(bean);
this.applicationContext.getAutowireCapableBeanFactory().initializeBean(bean, name);

先destroy原来的bean,而后在从新初始化bean。而spring初始化bean的过程中就蕴含了一个BeanPostProcessor回调解决,其中实现类ConfigurationPropertiesBindingPostProcessor,就会对@ConfigurationProperties的配置类进行解决,把配置文件内容(比方application.yml,systemProperties等,有优先级程序,如果是同一个属性还会笼罩)和配置类字段进行bind,bind()->bindObject(),这里会依据类型判断执行哪种策略,首先是判断复合类型的

AggregateBinder有三种实现:MapBinder、CollectionBind、ArrayBinder
再而后是bindProperty(),应用的是spring的Converter,
最初是bindDataObject()
DataObjectBinder有两种实现:JavaBeanBinder、ValueObjectBinder

我的配置类的属性就是一个map类型,所以进入到MapBinder里,这里在调用AggregateBinder#bind()会执行子类MapBinder的merge()
问题就出在这个办法里,

 @Override
       protected Map<Object, Object> merge(Supplier<Map<Object, Object>> existing, Map<Object, Object> additional) {
        Map<Object, Object> existingMap = getExistingIfPossible(existing);
        if (existingMap == null) {
            return additional;
       }
        try {
            existingMap.putAll(additional);
            return copyIfPossible(existingMap);
       }
        catch (UnsupportedOperationException ex) {
            Map<Object, Object> result = createNewMap(additional.getClass(), existingMap);
            result.putAll(additional);
            return result;
       }
   }

能够看到这里existingMap.putAll(additional);是把旧的map.putAll新的map,对于删除操作,旧map的数据更全,新map的数据少一下
这时候进行put,所以数据和旧map一样,没有删除成果
那有没有方法实现删除呢,也是有的,就是在后面的从新初始化bean前回调的destroy,只有咱们在销毁bean时把map==null
则实现了全量删除再新增的成果,以此实现删除成果

 @PreDestroy
 public void destroy() {
     map = null;
 }

题外:能够看到咱们加载configuration配置时应用的是ConfigurationPropertySource类,然而咱们应用nacos的配置也好NacosPropertySource,还是其余配置也好,父类都是PropertySource,这里spring会把PropertySource包装成SpringConfigurationPropertySource(ConfigurationPropertySource的子类),具体可见SpringConfigurationPropertySource.from(source);

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理