- 动态配置更新 : 当config.key配置值发生变化时,系统能够实时感知并作出响应
- 无需重启服务: 配置变更后不需要重启应用程序即可生效
- 精准监听: 只关注特定配置项的变化,避免不必要的回调触发
通过长轮询(Long Polling)机制,监听 Apollo 配置中心的配置变化,并在检测到变化后,通过 Spring 的发布-订阅(Event Listener)模式在应用内部进行事件通信,最终调用你标注的方法。

第一步:与 Apollo 配置中心的通信(长轮询)
这是整个机制的基础。Apollo 客户端不会为每个配置都建立一个持续的连接,而是采用了一种高效的长轮询策略。
- 
启动时拉取与本地缓存: - 当你的应用(Apollo 客户端)启动时,它会从 Apollo 配置中心服务器(Config Service)拉取你所关注的命名空间(如 application)的完整配置。
- 这些配置会被缓存在客户端的内存和本地文件系统中(/opt/data/{appId}/config-cache目录),这样即使 Apollo 服务器短暂不可用,应用也能继续运行。
 
- 当你的应用(Apollo 客户端)启动时,它会从 Apollo 配置中心服务器(Config Service)拉取你所关注的命名空间(如 
- 
建立长轮询连接: - 启动后,客户端会立即向 Apollo 服务器的 /notifications/v2接口发起一个 HTTP 长轮询请求。
- 这个请求会携带一个 notificationId,它是一个版本号,代表了客户端当前已知的各个配置的最新版本。
- "长"体现在哪里? 服务器端收到这个请求后,并不会立即返回。它会将这个请求"挂起"(Hold),直到两种情况发生:
 a. 有配置发生变更 :Apollo 的Portal后台有人修改了配置并发布。服务器会检测到这次变更,发现有一个客户端的notificationId比新版本旧,于是立即返回一个 HTTP 200 响应, body 中包含了发生变化的配置Namespace 和新版本号 。
 b. 超时:为了防止连接无限期挂起,长轮询有一个超时时间(默认60秒)。如果60秒内没有任何配置变化,服务器会返回一个 HTTP 304(Not Modified)响应,表示"没有新消息"。
 
- 启动后,客户端会立即向 Apollo 服务器的 
- 
处理响应并重新轮询: - 客户端收到 HTTP 200 响应后,就知道有配置变了。但它只知道是哪个命名空间 变了,还不知道具体是哪个Key变了。
- 于是,客户端会根据响应的信息,只针对那个发生变化的命名空间 ,重新向服务器发起一次普通HTTP请求,拉取该命名空间的最新全量配置。
- 拉取到最新配置后,客户端会更新本地缓存。
- 无论收到的是 200(有变化)还是 304(无变化),在处理完响应后,客户端都会立即再次发起一个新的长轮询请求,从而形成一个持续的监听循环。
 
通过这种"挂起-通知-拉取"的长轮询机制,Apollo 客户端能够在上万客户端同时在线的情况下,在 1 分钟内感知到配置变更,同时避免了频繁请求对服务器造成的压力。

第二步:应用内部的通信(Spring 事件机制)
当客户端从 Apollo 服务器拉取到最新配置并更新本地缓存后,内部的通信流程就开始了。
- 
发布配置变更事件: - 在更新本地缓存后,Apollo 客户端(具体是 ConfigService的相关类)会构造一个ConfigChangeEvent对象。
- 这个对象包含了哪个命名空间发生了变化,以及具体的变更详情(哪个Key,旧值是什么,新值是什么,变更类型等)。
- 然后,Apollo 客户端会通过 Spring 的 ApplicationContext.publishEvent()方法,将这个ConfigChangeEvent事件发布到 Spring 容器中。
- Apollo 客户端publishEvent事件发布的聚合机制: Apollo发布的是 ConfigChangeEvent,这个事件是以命名空间为维度的 public class ConfigChangeEvent { private final String m_namespace; // 命名空间,如 "application" private final Map<String, ConfigChange> m_changes; // 所有变化的Key和变更详情 }
 
- 在更新本地缓存后,Apollo 客户端(具体是 
- 
监听并处理事件( @ApolloConfigChangeListener的工作) :- @ApolloConfigChangeListener是一个注解,它的背后有一个 Spring AOP(面向切面编程)的处理器,例如- ApolloAnnotationProcessor。
- 在 Spring 容器启动时,这个处理器会扫描所有带有 @ApolloConfigChangeListener注解的方法。
- 它会为这些方法创建一个 Spring 的 ApplicationListener,这个监听器专门监听ConfigChangeEvent事件。
- 当步骤1中的 ConfigChangeEvent被发布后,Spring 容器会通知所有监听此事件的ApplicationListener。
 
- 
条件过滤与方法调用: - 
你的监听器被触发后,并不会直接执行你的业务逻辑。它首先会进行条件过滤。 
- 
过滤器会检查: - 事件的命名空间是否与注解上指定的 value(或namespaces)匹配(如果未指定,则默认为application)。
- 更关键的是 :它会检查 ConfigChangeEvent中发生变化的 Key(changedKeys),是否包含你在interestedKeys中指定的 Key。
 
- 事件的命名空间是否与注解上指定的 
- 
只有完全满足条件,你注解标注的方法才会被最终调用 。方法的参数 ConfigChangeEvent也会被传入,让你可以获取到变化的详细信息。
 
- 
Apollo 源码解析 服务端 Config Service 如何感知配置变更并通知
github: github.com/apolloconfi...
服务端的核心是及时感知配置发布,并通知在线的客户端。
- 
配置发布写入消息 
            
            
              js
              
              
            
          
          CREATE TABLE `releasemessage` (
  `Id` int unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键',
  `Message` varchar(1024) NOT NULL DEFAULT '' COMMENT '发布的消息内容',
  `DataChange_LastTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后修改时间',
  PRIMARY KEY (`Id`),
  KEY `DataChange_LastTime` (`DataChange_LastTime`),
  KEY `IX_Message` (`Message`(191))
) ENGINE=InnoDB 
;- 
定时扫描消息 - ReleaseMessageScanner- 
Config Service 启动时, ReleaseMessageScanner会初始化一个定时任务,每隔1秒 扫描ReleaseMessage表
- 
扫描逻辑是查找 id大于已扫描的最大ID (maxIdScanned) 的新消息,分批(例如500条一页)取出
- 
扫描到新消息后,会通过 fireMessageScanned(releaseMessages)通知所有监听器(ReleaseMessageListener),其中就包括NotificationControllerV2
 
- 
- 
管理长轮询请求 - NotificationControllerV2- 这个控制器提供了 /notifications/v2接口,是客户端长轮询的入口
- 它使用 Spring DeferredResult 来异步处理请求。当客户端发起查询时,请求不会被立即处理,而是挂起
- 控制器内部使用一个 Multimap<String, DeferredResultWrapper>(deferredResults)来管理挂起的请求。Key 是 Watch Key(可理解为与ReleaseMessage的message内容相关),Value 是对应的客户端DeferredResultWrapper列表
 
- 这个控制器提供了 
- 
处理消息并响应 - handleMessage方法- 当 ReleaseMessageScanner触发NotificationControllerV2的handleMessage方法时
 scss// 伪代码: 根据消息内容找到等待的客户端并响应 public void handleMessage(ReleaseMessage message, String channel) { String content = message.getMessage(); // 消息内容,如 AppId+Cluster+Namespace // 获取关注此消息的DeferredResultWrapper列表 List<DeferredResultWrapper> results = Lists.newArrayList(deferredResults.get(content)); ApolloConfigNotification notification = new ApolloConfigNotification(changedNamespace, message.getId()); // 为每个DeferredResultWrapper设置结果 for (DeferredResultWrapper result : results) { result.setResult(notification); // 触发请求返回 } }- DeferredResult的- setResult被调用时,Spring MVC 会立即将结果(包含变更的 namespace 和新的 notificationId)发送给客户端。如果客户端数量很多,会使用单独的线程池进行分批通知,避免阻塞
 
- 当 
流程
- 客户端会发起一个Http 请求到 Config Service 的 notifications/v2 接口,也就是NotificationControllerV2 ,参见 RemoteConfigLongPollService 。
- NotificationControllerV2 不会立即返回结果,而是通过 Spring DeferredResult 把请求挂起。
- 如果在 60 秒内没有该客户端关心的配置发布,那么会返回 Http 状态码 304 给客户端。
- 如果有该客户端关心的配置发布,NotificationControllerV2 会调用 DeferredResult 的 setResult 方法,传入有配置变化的 namespace 信息,同时该请求会立即返回。客户端从返回的结果中获取到配置变化的 namespace 后,会立即请求 Config Service 获取该 namespace 的最新配置。
- NotificationControllerV2
            
            
              arduino
              
              
            
          
          public interface ReleaseMessageListener {
  void handleMessage(ReleaseMessage message, String channel);
}com.ctrip.framework.apollo.configservice.controller.NotificationControllerV2 ,实现 ReleaseMessageListener 接口,通知 Controller ,仅提供 notifications/v2 接口。
            
            
              java
              
              
            
          
          package com.ctrip.framework.apollo.configservice.controller; 
/**
 * Apollo配置变更通知控制器V2版本
 * 核心功能:处理客户端的长轮询请求,实现配置实时推送
 */
@RestController
@RequestMapping("/notifications/v2")
public class NotificationControllerV2 implements ReleaseMessageListener {
  private static final Logger logger = LoggerFactory.getLogger(NotificationControllerV2.class);
  
  /**
   * 核心数据结构:存储所有挂起的长轮询请求
   * Key: 监听的watchKey(格式:appId+cluster+namespace)
   * Value: 对应的DeferredResultWrapper列表
   * 使用同步的TreeMultimap保证线程安全,Key不区分大小写
   */
  private final Multimap<String, DeferredResultWrapper> deferredResults =
      Multimaps.synchronizedSetMultimap(TreeMultimap.create(String.CASE_INSENSITIVE_ORDER, Ordering.natural()));
  /**
   * Gson类型引用,用于解析客户端传来的通知列表
   */
  private static final Type notificationsTypeReference =
      new TypeToken<List<ApolloConfigNotification>>() {
      }.getType();
  /**
   * 用于处理大批量客户端通知的线程池(避免阻塞消息处理线程)
   */
  private final ExecutorService largeNotificationBatchExecutorService;
  // 工具类和依赖注入
  private final WatchKeysUtil watchKeysUtil;                    // 构建watchKey工具
  private final ReleaseMessageServiceWithCache releaseMessageService; // 发布消息服务(带缓存)
  private final EntityManagerUtil entityManagerUtil;            // 实体管理器工具
  private final NamespaceUtil namespaceUtil;                    // 命名空间工具
  private final Gson gson;                                      // JSON序列化工具
  private final BizConfig bizConfig;                            // 业务配置
  /**
   * 构造函数,依赖注入
   */
  public NotificationControllerV2(
      final WatchKeysUtil watchKeysUtil,
      final ReleaseMessageServiceWithCache releaseMessageService,
      final EntityManagerUtil entityManagerUtil,
      final NamespaceUtil namespaceUtil,
      final Gson gson,
      final BizConfig bizConfig) {
    // 创建单线程线程池,用于处理大批量客户端通知
    largeNotificationBatchExecutorService = Executors.newSingleThreadExecutor(ApolloThreadFactory.create
        ("NotificationControllerV2", true));
    this.watchKeysUtil = watchKeysUtil;
    this.releaseMessageService = releaseMessageService;
    this.entityManagerUtil = entityManagerUtil;
    this.namespaceUtil = namespaceUtil;
    this.gson = gson;
    this.bizConfig = bizConfig;
  }
  /**
   * 长轮询接口:客户端调用此接口监听配置变更
   * @param appId 应用ID
   * @param cluster 集群名称
   * @param notificationsAsString 客户端当前的通知状态(JSON格式)
   * @param dataCenter 数据中心
   * @param clientIp 客户端IP
   * @return DeferredResult 异步结果,会在配置变更或超时时返回
   */
  @GetMapping
  public DeferredResult<ResponseEntity<List<ApolloConfigNotification>>> pollNotification(
      @RequestParam(value = "appId") String appId,
      @RequestParam(value = "cluster") String cluster,
      @RequestParam(value = "notifications") String notificationsAsString,
      @RequestParam(value = "dataCenter", required = false) String dataCenter,
      @RequestParam(value = "ip", required = false) String clientIp) {
    
    // 1. 解析客户端传递的通知列表
    List<ApolloConfigNotification> notifications = null;
    try {
      notifications = gson.fromJson(notificationsAsString, notificationsTypeReference);
    } catch (Throwable ex) {
      Tracer.logError(ex);
    }
    // 2. 验证通知列表格式
    if (CollectionUtils.isEmpty(notifications)) {
      throw BadRequestException.invalidNotificationsFormat(notificationsAsString);
    }
    // 3. 过滤和规范化通知列表(处理命名空间大小写等问题)
    Map<String, ApolloConfigNotification> filteredNotifications = filterNotifications(appId, notifications);
    if (CollectionUtils.isEmpty(filteredNotifications)) {
      throw BadRequestException.invalidNotificationsFormat(notificationsAsString);
    }
    // 4. 创建DeferredResultWrapper,设置超时时间
    DeferredResultWrapper deferredResultWrapper = new DeferredResultWrapper(bizConfig.longPollingTimeoutInMilli());
    
    // 5. 提取客户端关注的命名空间和对应的通知ID
    Set<String> namespaces = Sets.newHashSetWithExpectedSize(filteredNotifications.size());
    Map<String, Long> clientSideNotifications = Maps.newHashMapWithExpectedSize(filteredNotifications.size());
    for (Map.Entry<String, ApolloConfigNotification> notificationEntry : filteredNotifications.entrySet()) {
      String normalizedNamespace = notificationEntry.getKey();
      ApolloConfigNotification notification = notificationEntry.getValue();
      namespaces.add(normalizedNamespace);
      clientSideNotifications.put(normalizedNamespace, notification.getNotificationId());
      // 记录命名空间名称规范化的结果(用于客户端校正)
      if (!Objects.equals(notification.getNamespaceName(), normalizedNamespace)) {
        deferredResultWrapper.recordNamespaceNameNormalizedResult(notification.getNamespaceName(), normalizedNamespace);
      }
    }
    // 6. 构建客户端关注的所有watchKey集合
    Multimap<String, String> watchedKeysMap =
        watchKeysUtil.assembleAllWatchKeys(appId, cluster, namespaces, dataCenter);
    Set<String> watchedKeys = Sets.newHashSet(watchedKeysMap.values());
    /**
     * 7. 设置DeferredResult的回调(重要:先设置再检查,避免竞态条件)
     * 原理:如果在检查之前设置deferredResult,可以避免在检查和设置之间收到通知而丢失
     */
    
    // 设置超时回调
    deferredResultWrapper
          .onTimeout(() -> logWatchedKeys(watchedKeys, "Apollo.LongPoll.TimeOutKeys"));
    // 设置完成回调(无论超时还是正常返回都会执行)
    deferredResultWrapper.onCompletion(() -> {
      // 注销所有watchKey的监听
      for (String key : watchedKeys) {
        deferredResults.remove(key, deferredResultWrapper);
      }
      logWatchedKeys(watchedKeys, "Apollo.LongPoll.CompletedKeys");
    });
    // 8. 注册当前DeferredResult到所有关注的watchKey
    for (String key : watchedKeys) {
      this.deferredResults.put(key, deferredResultWrapper);
    }
    // 记录日志
    logWatchedKeys(watchedKeys, "Apollo.LongPoll.RegisteredKeys");
    logger.debug("Listening {} from appId: {}, cluster: {}, namespace: {}, datacenter: {}",
        watchedKeys, appId, cluster, namespaces, dataCenter);
    /**
     * 9. 检查是否有新的发布消息(解决竞态条件的关键)
     * 在注册监听之后立即检查,避免在注册过程中有配置发布导致消息丢失
     */
    List<ReleaseMessage> latestReleaseMessages =
        releaseMessageService.findLatestReleaseMessagesGroupByMessages(watchedKeys);
    /**
     * 10. 手动关闭实体管理器
     * 对于异步请求,Spring不会立即关闭EntityManager,而长轮询会长时间持有数据库连接
     * 这里手动关闭避免连接泄露
     */
    entityManagerUtil.closeEntityManager();
    // 11. 比较客户端版本和服务端版本,判断是否有新配置
    List<ApolloConfigNotification> newNotifications =
        getApolloConfigNotifications(namespaces, clientSideNotifications, watchedKeysMap,
            latestReleaseMessages);
    // 12. 如果有新配置,立即返回结果(避免客户端等待)
    if (!CollectionUtils.isEmpty(newNotifications)) {
      deferredResultWrapper.setResult(newNotifications);
    } 
    // 13. 最后返回DeferredResult,这时Spring框架开始接管 
    return deferredResultWrapper.getResult();
  }
  /**
   * 过滤和规范化客户端传递的通知列表
   * 主要处理:命名空间后缀过滤、大小写规范化、去重
   */
  private Map<String, ApolloConfigNotification> filterNotifications(String appId,
                                                                    List<ApolloConfigNotification> notifications) {
    Map<String, ApolloConfigNotification> filteredNotifications = Maps.newHashMap();
    for (ApolloConfigNotification notification : notifications) {
      if (Strings.isNullOrEmpty(notification.getNamespaceName())) {
        continue; // 跳过空的命名空间
      }
      
      // 去除.properties后缀
      String originalNamespace = namespaceUtil.filterNamespaceName(notification.getNamespaceName());
      notification.setNamespaceName(originalNamespace);
      
      // 规范化命名空间(解决大小写问题,如FX.apollo <-> fx.apollo)
      String normalizedNamespace = namespaceUtil.normalizeNamespace(appId, originalNamespace);
      /**
       * 处理客户端命名空间大小写不一致但notificationId不同的情况
       * 例如:FX.apollo = 1 但 fx.apollo = 2
       * 选择保留notificationId较大的那个
       */
      if (filteredNotifications.containsKey(normalizedNamespace) &&
          filteredNotifications.get(normalizedNamespace).getNotificationId() < notification.getNotificationId()) {
        continue; // 如果已存在且新通知ID较小,则跳过
      }
      filteredNotifications.put(normalizedNamespace, notification);
    }
    return filteredNotifications;
  }
  /**
   * 比较客户端和服务端的通知版本,生成需要返回的新通知列表
   */
  private List<ApolloConfigNotification> getApolloConfigNotifications(Set<String> namespaces,
                                                                      Map<String, Long> clientSideNotifications,
                                                                      Multimap<String, String> watchedKeysMap,
                                                                      List<ReleaseMessage> latestReleaseMessages) {
    List<ApolloConfigNotification> newNotifications = Lists.newArrayList();
    if (!CollectionUtils.isEmpty(latestReleaseMessages)) {
      // 构建服务端最新的消息ID映射
      Map<String, Long> latestNotifications = Maps.newHashMap();
      for (ReleaseMessage releaseMessage : latestReleaseMessages) {
        latestNotifications.put(releaseMessage.getMessage(), releaseMessage.getId());
      }
      // 对每个命名空间检查是否有新版本
      for (String namespace : namespaces) {
        long clientSideId = clientSideNotifications.get(namespace); // 客户端版本
        long latestId = ConfigConsts.NOTIFICATION_ID_PLACEHOLDER;   // 服务端版本,初始化为占位符
        
        // 遍历该命名空间对应的所有watchKey,取最大的notificationId
        Collection<String> namespaceWatchedKeys = watchedKeysMap.get(namespace);
        for (String namespaceWatchedKey : namespaceWatchedKeys) {
          long namespaceNotificationId =
              latestNotifications.getOrDefault(namespaceWatchedKey, ConfigConsts.NOTIFICATION_ID_PLACEHOLDER);
          if (namespaceNotificationId > latestId) {
            latestId = namespaceNotificationId; // 取最大ID作为该命名空间的版本
          }
        }
        
        // 如果服务端版本大于客户端版本,说明有更新
        if (latestId > clientSideId) {
          ApolloConfigNotification notification = new ApolloConfigNotification(namespace, latestId);
          // 记录具体的watchKey和消息ID(用于调试和追踪)
          namespaceWatchedKeys.stream().filter(latestNotifications::containsKey).forEach(namespaceWatchedKey ->
              notification.addMessage(namespaceWatchedKey, latestNotifications.get(namespaceWatchedKey)));
          newNotifications.add(notification);
        }
      }
    }
    return newNotifications;
  }
  /**
   * 处理发布消息(实现ReleaseMessageListener接口)
   * 当有配置发布时,此方法会被调用
   */
  @Override
  public void handleMessage(ReleaseMessage message, String channel) {
    logger.info("message received - channel: {}, message: {}", channel, message);
    String content = message.getMessage();
    Tracer.logEvent("Apollo.LongPoll.Messages", content);
    
    // 1. 验证消息通道和内容
    if (!Topics.APOLLO_RELEASE_TOPIC.equals(channel) || Strings.isNullOrEmpty(content)) {
      return;
    }
    // 2. 从发布消息中提取命名空间
    String changedNamespace = retrieveNamespaceFromReleaseMessage.apply(content);
    if (Strings.isNullOrEmpty(changedNamespace)) {
      logger.error("message format invalid - {}", content);
      return;
    }
    // 3. 检查是否有客户端监听此消息
    if (!deferredResults.containsKey(content)) {
      return; // 没有客户端关注此变更,直接返回
    }
    // 4. 创建结果列表副本,避免在遍历时修改原始集合导致的ConcurrentModificationException
    List<DeferredResultWrapper> results = Lists.newArrayList(deferredResults.get(content));
    // 5. 创建配置通知对象
    ApolloConfigNotification configNotification = new ApolloConfigNotification(changedNamespace, message.getId());
    configNotification.addMessage(content, message.getId());
    // 6. 根据客户端数量决定同步通知还是异步通知
    if (results.size() > bizConfig.releaseMessageNotificationBatch()) {
      // 大批量客户端:使用异步通知,避免阻塞消息处理线程
      largeNotificationBatchExecutorService.submit(() -> {
        logger.debug("Async notify {} clients for key {} with batch {}", results.size(), content,
            bizConfig.releaseMessageNotificationBatch());
        
        // 分批通知,批间休眠避免瞬时压力过大
        for (int i = 0; i < results.size(); i++) {
          if (i > 0 && i % bizConfig.releaseMessageNotificationBatch() == 0) {
            try {
              TimeUnit.MILLISECONDS.sleep(bizConfig.releaseMessageNotificationBatchIntervalInMilli());
            } catch (InterruptedException e) {
              // 忽略中断异常
            }
          }
          logger.debug("Async notify {}", results.get(i));
          results.get(i).setResult(configNotification); // 触发客户端响应
        }
      });
      return;
    }
    // 7. 小批量客户端:同步通知
    logger.debug("Notify {} clients for key {}", results.size(), content);
    for (DeferredResultWrapper result : results) {
      result.setResult(configNotification); // 触发客户端响应
    }
    logger.debug("Notification completed");
  }
  /**
   * 从发布消息内容中提取命名空间的函数
   * 发布消息格式:AppId+Cluster+Namespace
   */
  private static final Function<String, String> retrieveNamespaceFromReleaseMessage =
      releaseMessage -> {
        if (Strings.isNullOrEmpty(releaseMessage)) {
          return null;
        }
        // 将消息字符串解析为列表:[AppId, Cluster, Namespace]
        List<String> keys = ReleaseMessageKeyGenerator.messageToList(releaseMessage);
        if (CollectionUtils.isEmpty(keys)) {
          return null;
        }
        return keys.get(2); // 返回第三个元素:Namespace
      };
  /**
   * 记录watchKey日志的辅助方法
   */
  private void logWatchedKeys(Set<String> watchedKeys, String eventName) {
    for (String watchedKey : watchedKeys) {
      Tracer.logEvent(eventName, watchedKey);
    }
  }
}- DeferredResultWrapper
            
            
              js
              
              
            
          
          /**
 * DeferredResult包装类,用于处理Apollo长轮询的异步结果
 * 实现了Comparable接口,用于在集合中排序
 */
public class DeferredResultWrapper implements Comparable<DeferredResultWrapper> {
  /**
   * 静态常量:表示配置未修改的HTTP响应(304状态码)
   * 当长轮询超时且没有配置变更时返回此响应
   */
  private static final ResponseEntity<List<ApolloConfigNotification>>
      NOT_MODIFIED_RESPONSE_LIST = new ResponseEntity<>(HttpStatus.NOT_MODIFIED);
  /**
   * 命名空间名称映射:记录规范化命名空间到原始命名空间的映射关系
   * Key: 规范化后的命名空间名称(统一大小写格式)
   * Value: 客户端原始的命名空间名称(保持原始大小写)
   * 用于在返回响应时恢复客户端的原始命名空间格式
   */
  private Map<String, String> normalizedNamespaceNameToOriginalNamespaceName;
  
  /**
   * Spring的DeferredResult对象,核心的异步结果容器
   * 泛型参数:ResponseEntity<List<ApolloConfigNotification>>
   * 表示最终返回的是包含Apollo配置通知列表的HTTP响应实体
   */
  private DeferredResult<ResponseEntity<List<ApolloConfigNotification>>> result;
  /**
   * 构造函数
   * @param timeoutInMilli 长轮询超时时间(毫秒)
   * 超过此时间没有配置变更,将返回NOT_MODIFIED_RESPONSE_LIST(304状态码)
   */
  public DeferredResultWrapper(long timeoutInMilli) {
    // 创建DeferredResult,设置超时时间和超时默认返回值
    result = new DeferredResult<>(timeoutInMilli, NOT_MODIFIED_RESPONSE_LIST);
  }
  /**
   * 记录命名空间名称的规范化映射关系
   * 用于解决客户端命名空间大小写不一致的问题
   * 例如:客户端传递"FX.apollo",服务端规范化为"fx.apollo"
   * @param originalNamespaceName 客户端原始的命名空间名称
   * @param normalizedNamespaceName 服务端规范化后的命名空间名称
   */
  public void recordNamespaceNameNormalizedResult(String originalNamespaceName, String normalizedNamespaceName) {
    // 懒加载初始化映射表
    if (normalizedNamespaceNameToOriginalNamespaceName == null) {
      normalizedNamespaceNameToOriginalNamespaceName = Maps.newHashMap();
    }
    // 记录映射关系
    normalizedNamespaceNameToOriginalNamespaceName.put(normalizedNamespaceName, originalNamespaceName);
  }
  /**
   * 设置超时回调函数
   * 当DeferredResult超时时(未在指定时间内设置结果),会执行此回调
   * @param timeoutCallback 超时回调逻辑
   */
  public void onTimeout(Runnable timeoutCallback) {
    result.onTimeout(timeoutCallback);
  }
  /**
   * 设置完成回调函数
   * 当DeferredResult完成时(无论超时还是正常设置结果),都会执行此回调
   * 常用于资源清理工作
   * @param completionCallback 完成回调逻辑
   */
  public void onCompletion(Runnable completionCallback) {
    result.onCompletion(completionCallback);
  }
  /**
   * 设置单个通知结果
   * 便捷方法,用于只有一个配置变更通知的情况
   * @param notification Apollo配置通知对象
   */
  public void setResult(ApolloConfigNotification notification) {
    // 将单个通知包装成列表,调用重载方法
    setResult(Lists.newArrayList(notification));
  }
  /**
   * 设置多个通知结果
   * 核心方法:将处理结果设置到DeferredResult中,从而完成异步请求
   * 在设置结果前,会处理命名空间名称的逆规范化(恢复客户端原始格式)
   * 
   * 注意:命名名称在客户端作为key使用,所以必须返回原始格式而不是规范化格式
   * 
   * @param notifications Apollo配置通知列表
   */
  public void setResult(List<ApolloConfigNotification> notifications) {
    // 如果存在命名空间名称映射关系,需要进行逆规范化处理
    if (normalizedNamespaceNameToOriginalNamespaceName != null) {
      // 遍历所有通知,对需要逆规范化的命名空间进行处理
      notifications.stream()
          // 过滤出需要逆规范化的通知(其命名空间名称在映射表中存在)
          .filter(notification -> normalizedNamespaceNameToOriginalNamespaceName.containsKey(
              notification.getNamespaceName()))
          // 对每个匹配的通知,将其命名空间名称恢复为客户端原始格式
          .forEach(notification -> notification.setNamespaceName(
              normalizedNamespaceNameToOriginalNamespaceName.get(notification.getNamespaceName())));
    }
    // 将处理后的通知列表包装成HTTP 200响应,并设置到DeferredResult中
    // 这会触发Spring MVC向客户端发送响应,完成此次长轮询请求
    result.setResult(new ResponseEntity<>(notifications, HttpStatus.OK));
  }
  /**
   * 获取底层的DeferredResult对象
   * @return Spring DeferredResult实例
   */
  public DeferredResult<ResponseEntity<List<ApolloConfigNotification>>> getResult() {
    return result;
  }
  /**
   * 实现Comparable接口,用于在集合中排序
   * 使用对象的hashCode进行比较,确保在有序集合中有一致的排序
   * @param deferredResultWrapper 要比较的另一个DeferredResultWrapper对象
   * @return 比较结果:-1(小于), 0(等于), 1(大于)
   */
  @Override
  public int compareTo(@NonNull DeferredResultWrapper deferredResultWrapper) {
    return Integer.compare(this.hashCode(), deferredResultWrapper.hashCode());
  }
}