Skip to content

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

Closed

Description

@liuxi1211

问题描述:
当使用 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;
  }
}

Activity

  1. nobodyiam commented on Nov 2, 2024

    @nobodyiam
    Member
    1. 是否能上传一个最简 demo 复现该问题?
    2. 对于该问题是否有修复建议?
  2. lucky8987 commented on Nov 26, 2024

    @lucky8987

    同样遇到了这个问题,因为CachedCompositePropertySource中的names缓存未被刷新,导致新增和删除apollo的key应用端匹配不到,可以用logging.level.xxx这个前缀的key进行验证一下。

  3. nobodyiam commented on Dec 1, 2024

    @nobodyiam
    Member

    我用 spring-boot-demo 没有重现该问题,是有特殊配置吗?

  4. added 2 commits that reference this issue on Feb 8, 2026
    2bf66f6
    6d32daa
  5. nobodyiam commented on Feb 8, 2026

    @nobodyiam
    Member

    已复现这个问题。

    根因是 Spring Cloud Bootstrap 双 context 场景下会出现两个同名 CachedCompositePropertySource,而当前 AbstractConfig#addChangeListener 按 equals 去重(PropertySource 又按 name 判等),导致后注册的 listener 被误去重,新增/删除 key 时 propertyNames 缓存无法及时刷新。

    修复 PR 已提交:
    #121

    修复点是把 listener 的去重/移除改为按实例(==)判断,并补了对应回归测试。
    临时规避可先关闭缓存:apollo.property.names.cache.enable=false。

  6. added 2 commits that reference this issue on Feb 8, 2026
    ad4aa96
    85b3cf4
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions