- 动态配置更新 : 当
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());
}
}