遇到一个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);
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。