apolloconfig / apollo-java

Apollo Java Clients
Apache License 2.0
40 stars 72 forks source link

SpringCloud 环境 @ConfigurationProperties 无法监听到新增和删除 key 问题 #88

Open liuxi1211 opened 3 weeks ago

liuxi1211 commented 3 weeks ago

问题描述: 当使用 SpringCloud 并且未主动关闭 BootstrapApplicationListener 时,如果动态调整配置,新增和删除 key 无法正确映射到绑定属性上

问题原因: SpringCloud 项目启动时 BootstrapApplicationListener 会在 applicationContext 创建之前先创建一个 bootstrapApplicationContext,用来优先加载环境变量,这会出现两次容器加载,触发两次 ApolloApplicationContextInitializer 初始化 当触发两次 ApolloApplicationContextInitializer 初始化时 CachedCompositePropertySource 缓存的 propertyNames 会因监听器问题无法刷新

代码说明:

   // ApolloApplicationContextInitializer::initialize

   CompositePropertySource composite;
    if (configUtil.isPropertyNamesCacheEnabled()) {

     // 此处每次都会创建一个新的 CachedCompositePropertySource ,但是 name 相同
      composite = new CachedCompositePropertySource(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME);
    } else {
      composite = new CompositePropertySource(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME);
    }
    for (String namespace : namespaceList) {
      Config config = ConfigService.getConfig(namespace);
      composite.addPropertySource(configPropertySourceFactory.getConfigPropertySource(namespace, config));
    }
 // AbstractConfig::addChangeListener
 @Override
  public void addChangeListener(ConfigChangeListener listener, Set<String> interestedKeys, Set<String> interestedKeyPrefixes) {

    /* 此处根据 PropertySource 名称判断,由于 bootstrapApplicationContext 中已经设置过一次
     * applicationContext 创建时新 new 对象 CachedCompositePropertySource 无法设置到监听里面
     * 进而导致监听器中的保存的一直是旧的 CachedCompositePropertySource,而 applicationContext 实际使用新的
     */
    if (!m_listeners.contains(listener)) {
      m_listeners.add(listener);
      if (interestedKeys != null && !interestedKeys.isEmpty()) {
        m_interestedKeys.put(listener, Sets.newHashSet(interestedKeys));
      }
      if (interestedKeyPrefixes != null && !interestedKeyPrefixes.isEmpty()) {
        m_interestedKeyPrefixes.put(listener, Sets.newHashSet(interestedKeyPrefixes));
      }
    }
  }
public class CachedCompositePropertySource extends CompositePropertySource implements
    ConfigChangeListener {

  private volatile String[] names;

  public CachedCompositePropertySource(String name) {
    super(name);
  }

  @Override
  public String[] getPropertyNames() {
   // 无法触发监听器,缓存仍然是所有旧 key,而 SpringBoot 属性绑定会使用这个方法
    String[] propertyNames = this.names;
    if (propertyNames == null) {
      this.names = propertyNames = super.getPropertyNames();
    }
    return propertyNames;
  }

  @Override
  public void onChange(ConfigChangeEvent changeEvent) {

    // 正常情况下应该触发监听器,让 names 置空,从而触发调用时刷新
   // 此处 applicationContext 中 CachedCompositePropertySource 无法触发
    this.names = null;
  }
}
nobodyiam commented 2 weeks ago
  1. 是否能上传一个最简 demo 复现该问题?
  2. 对于该问题是否有修复建议?