配置变更导致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, 如下:

 

posted @ 2022-06-10 14:10  EEEEET  阅读(925)  评论(0编辑  收藏  举报