配置变更导致ConfigurationProperties配置List集合遍历出现NullPointerException
背景
在Configuration配置类中声明了一个配置bean,如下:
配置类中存在集合配置
对应在Apollo中的配置如下
使用时,注入 AccessControlConfigBean, 具体使用方式如下:
我在监听到Apollo配置变更时,发布了一个 EnvironmentChangeEvent 事件,如下:
问题描述
问题一:当apollo中该项目的任意配置变更时,上述用红色圈出来的match会有小概率出现为null的情况,从而导致空指针。
问题二:于是我加了一个判断,当遍历元素是null时,跳过处理
此时,出现了另一个问题,97行报了NullPointerException, 如下:
问题剖析
问题一:为什么变更配置会导致出现元素为null?
根据问题出现的场景,可以断定spring在处理EnvironmentChangeEvent事件,刷新bean配置时存在问题,我们找到这个事件的处理逻辑
可以看到配置Bean的刷新过程,就是将Bean进行destroy后,再重新init。
destroy的逻辑就是注销了一些监听器,以及调用了声明的destroy方法,并不会去清除bean里面的字段,所以问题不在destroy逻辑中。
init的逻辑是调用了bean的aware方法,并执行了BeanPostProcessor的扩展处理,配置刷新就是在这里完成的,相关类是:ConfigurationPropertiesBindingPostProcessor
最终完成字段值更新是在JavaBeanBinder这个类中。
对于配置类中的集合(Collection/List/Set)字段, 这边会调用 CollectionBinder 的merge方法
可以看到,原先的集合对象并不是被直接替换,而是先进行了 clear,再往里面addAll
至此,真相大白,由于这两部操作的非原子性,如果在这个期间,此集合正在被遍历或者使用,就会出现开头的异常,这本质上是属于线程安全问题。
解决方案
方案1:在使用集合时,new一个新的集合,如下:
方案2:在配置类中,将List的实现类修改为 CopyOnWriteArrayList, 如下: