• nacos版本:2.2.0
  • springcloud-alibaba版本:2021.0.6.0
  • springcloud-commons版本:3.1.8

简单类型配置

@Data  
public class LogProperties {  
    private String testProperty;  
}

如果删除testProperty配置,内存中还有旧的数据。

列表类型配置

@Data  
public class LogProperties {  
    private Set<String> testSet;  
}
  1. 删除set中某个元素,生效
  2. 删除整个配置项,不生效

Map类型配置

@Data  
public class LogProperties {  
    private boolean enabled = true;  
    /**  
     * 不打印日志的参数  
     */  
    private Map<String, Set<String>> excludeRequestParams;  
}
  1. 删除Set<String>中的某项配置,生效
  2. 删除map中某项key,不生效
  3. 清空整个配置项(删除整个excludeRequestParams参数),不生效

配置项删除不生效,但是删除某个元素生效。
对于Map类型,key也算配置项级别,因此删除key不生效

原因分析

首先需要了解Nacos动态刷新原理。
nacos在配置变更后,会重新获取所有远程的配置项,然后利用ConfigDataLoader机制,重新覆盖spring内的配置项。因此,对于已经在远程删除的配置项,nacos无法做到删除spring配置项。

其实很好理解。假如配置类中有一个默认的属性enabled = true,总不能因为配置文件中没有这个配置项,就把该配置删除(置为null)吧!

根本原因是spring自身在rebind bean的时候,只是调用了bean的destroy方法和初始化方法。而不是重新创建一个bean,因此对于bean的配置属性,如果环境中没有,是无法修改的。

private boolean rebind(String name, ApplicationContext appContext) {  
    try {  
       Object bean = appContext.getBean(name);  
       if (AopUtils.isAopProxy(bean)) {  
          bean = ProxyUtils.getTargetObject(bean);  
       }  
       if (bean != null) {      
         if (getNeverRefreshable().contains(bean.getClass().getName())) {  
             return false; 
          }  
          appContext.getAutowireCapableBeanFactory().destroyBean(bean);  
          appContext.getAutowireCapableBeanFactory().initializeBean(bean, name);  
          return true;  
       }  
    }  
    catch (RuntimeException e) {  
       this.errors.put(name, e);  
       throw e;  
    }  
    catch (Exception e) {  
       this.errors.put(name, e);  
       throw new IllegalStateException("Cannot rebind to " + name, e);  
    }  
    return false;  
}

解决方案一

利用spring刷新配置的原理,在destroy和init方法上进行处理
在配置类中添加destroy方法

@PreDestroy  
public void destroy() {  
    //解决nacos无法删除配置问题  
    log = new LogProperties();  
}

LogProperties是一个@NestedConfigurationProperty

这样,spring在刷新配置时,就会调用到该销毁方法,将属性先销毁。
然后,在init中会自动从环境中获取配置,给该属性赋值。

如果LogProperties是采用@PostConstruct使用的,还需要在使用该配置的bean上加注解@RefreshScope

@PostConstruct  
public void init() {  
  logProperties = SpringUtil.getBean(GennHubGatewayProperties.class).getLog(); 

因为只有加了@RefreshScope的bean,才会触发spring的刷新。
如果是直接用外层配置类拿的属性,是不需要这样的。

因此,对于@NestedConfigurationProperty,在引用这些配置时,建议使用注入的原始配置类。