这部分是属于apollo这一工业级配置的详细解释
这篇文章由“马哥”撰写,旨在为一个动态线程池项目(oneThread)增加对 Apollo 配置中心的支持。虽然之前章节已经使用了 Nacos,但为了让组件具备通用性和可插拔性(适配不同公司的基础设施),作者引入了 Apollo。
以下是对这篇文章的详细通透讲解,我将内容划分为五个核心部分进行解读:
第一部分:为什么要讲 Apollo?(设计哲学的升华)
核心观点: 做中间件/基础组件,不能只适配一种环境。
- 现状: 很多教程或项目只绑定一种技术栈(比如只用 Nacos)。
- 痛点: 真实企业环境是复杂的。有的公司用 Nacos,有的用 Apollo,有的用 Zookeeper。如果你的组件强依赖 Nacos,那些用 Apollo 的公司就没法用你的组件了。
- 解决方案: 适配器模式的思想。核心逻辑(动态修改线程池)是不变的,变的是“监听配置变更”的方式。Apollo 和 Nacos 只是不同的配置来源,组件应该提供一套标准接口,分别实现对两者的适配。
第二部分:Apollo 是什么?(核心能力解析)
Apollo(阿波罗)是携程开源的配置中心,在微服务领域地位极高,尤其是对配置治理要求严格的大厂。
关键特性通俗解读:
- 统一管理: 不管你有多少个环境(开发、测试、生产),多少个集群(北京机房、上海机房),多少个微服务,都在一个网页上管理。
- 热发布(Hot Deploy): 这是动态线程池的基础。改了配置,1秒内推送到所有服务器,不需要重启服务。
- 灰度发布(非常重要):
- 场景: 你想把核心线程数从 10 改成 50,但怕改出问题。
- Apollo做法: 先只发给 1 台机器,观察没报错,再全量发布。这是 Nacos 社区版比较弱甚至缺失的功能。
- 版本管理与回滚: 改错了?一键回滚到上一个版本,像 Git 一样方便。
- 权限与审计: 谁在什么时候改了什么配置,都有记录。这对金融、支付类业务至关重要。
第三部分:Apollo 架构原理(它是怎么工作的?)
文中展示了基础模型和总体架构图,我们拆解一下数据流向:
角色分工:
- Portal(管理界面): 也就是我们操作的网页,管理员在这里修改配置。
- Admin Service: Portal 的后台,负责把配置写入数据库。
- Config Service: 真正的“搬运工”,负责把配置推送到你的 Java 应用(Client)。
- Client(你的应用): 嵌入在你项目里的 Jar 包。
配置更新流程(硬核知识点):
- 推拉结合(Long Polling): Client 和 Config Service 保持一个长连接。
- 当配置没变时,连接挂起,不浪费资源。
- 当配置变了,服务端立即返回结果(推的效果)。
- 同时,Client 还会定时(默认5分钟)主动拉一次,作为兜底(拉的效果)。
- 本地缓存(容灾): Client 获取配置后,不仅存在内存里,还会存一份到本地文件。
- 意义: 就算 Apollo 服务端全挂了,你的应用重启时还能读取本地文件,保证服务不挂。
第四部分:Apollo vs Nacos(选型指南)
这是大家最关心的部分。马哥给出了非常客观的评价:
| 维度 | Apollo (携程) | Nacos (阿里) |
|---|---|---|
| 定位 | 纯粹的配置中心,功能做到了极致。 | 注册中心 + 配置中心 (2合1)。 |
| 功能深度 | 强。支持灰度、详细权限、版本管理、审批流。 | 中。社区版功能够用,但高级治理能力弱。 |
| 部署运维 | 重。需要部署 Config、Admin、Portal 等多个服务,依赖 MySQL。 | 轻。一个 Jar 包启动即可,依赖 MySQL(也可不依赖)。 |
| 适用场景 | 中大型企业,对配置治理、权限、灰度有强需求。 | 中小型企业,或者想省事(一套系统解决注册+配置)。 |
马哥的建议:
- 想要功能强大、治理规范 -> 选 Apollo。
- 想要部署简单、生态统一(Spring Cloud Alibaba) -> 选 Nacos。
- 两者不是你死我活,oneThread 这个项目选择全都支持。
第五部分:代码实战(如何在 oneThread 中接入 Apollo)
这部分是技术落地的核心,展示了如何用代码监听 Apollo 的变化。
1. 引入依赖
<dependency>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo-client-config-data</artifactId>
</dependency>
这是 Apollo 的官方客户端 SDK。
2. 配置文件 (application.yml)
这里有几个关键参数需要注意:
apollo.bootstrap.enabled: true: 必须开启。因为线程池可能在 Spring 启动早期就初始化,所以需要 Apollo 尽早加载配置。apollo.bootstrap.namespaces: 指定监听哪个命名空间(Namespace),通常是application。
3. 核心监听代码解读 (ApolloRefresherHandlerV1)
这个类实现了 ApplicationRunner,意味着 Spring Boot 启动完成后会立即执行 run 方法。
代码逻辑拆解:
- 获取配置对象:
// 拿到指定的 namespace Config config = ConfigService.getConfig("application");ConfigService是 Apollo 客户端的入口。 - 创建监听器 (Listener):
这是观察者模式的应用。ConfigChangeListener configChangeListener = changeEvent -> { // 1. 获取变更后的全部内容 ConfigFile configFile = ConfigService.getConfigFile("application", ConfigFileFormat.Properties); String content = configFile.getContent(); // 2. 调用 oneThread 的核心方法刷新线程池 refreshThreadPoolProperties(content); };注意: 这里作者使用的是获取整个配置文件内容 (getConfigFile),而不是获取单个 Key。这是因为动态线程池的配置通常是一组 Key(核心线程数、最大线程数、队列等),获取整个文件内容解析更方便。 - 注册监听器:
java config.addChangeListener(configChangeListener);
这一行代码执行后,只要你在 Apollo 界面改了配置并点击“发布”,上面的configChangeListener就会被触发,线程池参数就会被动态修改。
4. 流程总结
用户在 Apollo 界面修改配置 -> 点击发布 -> Apollo Server 推送给 Client -> Client 触发 ConfigChangeListener -> 解析新配置 -> refreshThreadPoolProperties 修改线程池参数 -> 生效(无需重启)。
全文总结
这篇文章不仅是一篇技术教程,更体现了一个优秀的开源项目架构师的思维:
- 解耦: 核心业务逻辑(线程池管理)与基础设施(配置中心)分离。
- 兼容性: 考虑到不同用户的技术栈差异,提供多种实现方案(Nacos & Apollo)。
- 深度: 不仅教怎么用,还分析了架构原理和选型对比,帮助读者“知其然,知其所以然”。
如果你在学习这个项目,重点在于理解 ConfigService.getConfig().addChangeListener() 这一套标准 API 的用法,这在任何需要对接 Apollo 的业务中都是通用的。
基于原文已经介绍的基础集成,如果想要深入理解 “动态线程池 + Apollo” 这一组合的架构设计与落地细节,必须补充以下几个深层次的技术盲点和架构陷阱。
这部分内容通常不会写在入门教程中,但却是中间件开发和生产环境运维的核心。
1. JDK 原生队列的“不可变”陷阱
原文代码中通过 refreshThreadPoolProperties 方法刷新配置,其中包含修改 队列容量 (queueCapacity)。这是一个巨大的技术坑。
- 问题所在:JDK 原生的
LinkedBlockingQueue的容量(capacity)字段是final的,且没有提供setCapacity方法。这意味着一旦线程池创建,队列大小就固定了。 - 深层实现: 要实现动态修改队列长度,不能直接使用 JDK 的队列,必须重写队列。通常的做法是 copy 一份
LinkedBlockingQueue的代码,将capacity字段的final修饰符去掉,并添加setCapacity(int capacity)方法。- oneThread/Hippo4j 等开源项目:内部都实现了一个
ResizableLinkedBlockingQueue。 - 补充点:如果你的项目直接用
ArrayBlockingQueue或原生的LinkedBlockingQueue,Apollo 推送的新队列大小是不会生效的(除非销毁重建线程池,但这会丢失任务)。
- oneThread/Hippo4j 等开源项目:内部都实现了一个
2. Apollo 的“长轮询”实现机制 (Long Polling)
文中提到了“实时推送”,但深入理解其网络模型对排查生产网络问题至关重要。Apollo 并非使用 WebSocket 或 TCP 长连接,而是基于 HTTP 的 DeferredResult(Servlet 3.0+ 异步处理)。
- 客户端:发起一个 HTTP 请求(
/notifications/v2),超时时间设置为 90秒(通常)。 - 服务端:
- 如果配置有变化:立即返回 200 及变化的 namespace。
- 如果配置无变化:Hold 住该请求(不立即返回),将其放入一个类似 Netty HashWheelTimer 的时间轮或异步队列中。
- 如果在 60秒内发生了配置变更:取出 Hold 住的请求,立即返回结果。
- 如果 60秒到了还没变化:返回 304 Not Modified。
- 深度价值:这种机制在没有配置变更时几乎不消耗服务端线程(非阻塞 IO),但在配置变更时能做到毫秒级响应。这解释了为什么 Apollo 客户端日志里会有大量 304 状态码,这是正常的保活机制,不是错误。
3. @RefreshScope 与 Stateful 对象的冲突
在 Spring Cloud 体系中,通常使用 @RefreshScope 注解来实现配置自动刷新。但在动态线程池场景下,绝对不能直接对线程池 Bean 使用 @RefreshScope。
- 原因:
@RefreshScope的刷新机制是 “销毁 + 重建”。当配置变更时,Spring 会销毁旧 Bean,创建新 Bean。 - 后果:对于无状态对象(如 Controller)没问题,但对于 线程池(Stateful),销毁意味着正在执行的任务可能被中断,队列中排队的任务会被丢弃。
- 正确姿势:必须像文中
ApolloRefresherHandlerV1那样,使用 “热更新(Hot Modify)” 模式。即 Bean 单例保持不变,通过 Setter 方法(如setCorePoolSize)修改其内部属性。这是中间件开发与普通业务开发的重大区别。
4. 关联命名空间 (Public Namespace) 的应用
文中演示的是监听 application(私有命名空间)。但在微服务架构中,动态线程池通常作为基础组件,需要全局策略。
- 场景:公司有一套默认的线程池配置(如核心线程数=CPU核数),希望所有微服务默认继承,个别服务单独覆盖。
- Apollo 深度用法:
- 创建一个 Public Namespace(例如
middleware.threadpool.default)。 - 所有微服务的应用 ID 关联这个 Public Namespace。
- 覆盖优先级:Apollo 客户端在读取配置时,会按照
Private Namespace > Public Namespace的顺序覆盖。
- 创建一个 Public Namespace(例如
- 代码调整:
config.getNamespace()需要处理关联 Namespace 的逻辑,或者直接在apollo.bootstrap.namespaces中注入多个 Namespace。
5. 监听器线程安全与异常隔离
ConfigChangeListener 的回调是在 Apollo 的独立守护线程中执行的,而不是主线程或 HTTP 请求线程。
- 并发风险:如果
refreshThreadPoolProperties方法内部涉及到复杂的非原子操作(例如同时修改 coreSize 和 maxSize,且有逻辑依赖),需要考虑线程同步问题。虽ThreadPoolExecutor的 setter 方法是线程安全的,但复合操作可能需要加锁。 - 异常吞没:如果监听器逻辑中抛出了未捕获的
RuntimeException,Apollo 的监听线程可能会被中断或卡住,导致后续的配置变更无法处理。 - Best Practice:在监听器内部必须进行
try-catch包裹,确保一次错误的配置推送不会搞挂整个配置监听功能,同时应配合 Alert 系统报警。
6. 本地缓存文件 (Local Cache) 的运维价值
Apollo 会在本地(通常是 /opt/data/{appId}/config-cache)生成缓存文件。
- 深度补充:这不仅是容灾手段,更是排查问题的黑匣子。
- 当你怀疑 Apollo 没推送到位,或者代码逻辑有 Bug 时,直接去服务器
cat这个缓存文件。 - 如果文件变了但线程池没变 -> 监听器代码逻辑问题。
- 如果文件没变 -> 网络问题或 Apollo 服务端问题。
- 启动顺序:应用启动时 -> 1. 读远程配置 -> 失败则 2. 读本地文件。这意味着如果 Apollo 挂了,重启服务依然可以使用最后一次成功的配置,这一点对于高可用架构设计至关重要。
- 当你怀疑 Apollo 没推送到位,或者代码逻辑有 Bug 时,直接去服务器
这篇文章由“马哥”撰写,是关于开源动态线程池项目 oneThread 的架构演进章节。文章的核心主题是:如何利用“模板方法模式(Template Method Pattern)”来重构配置中心的监听与刷新逻辑,从而优雅地支持多种配置中心(Nacos, Apollo 等)。
以下是对这篇文章的详细通透讲解,我将从背景痛点、模式原理、重构实现、深度细节四个方面为你拆解。
一、 背景与痛点:为什么要重构?
在软件开发中,“跑通功能”和“架构设计”是两个层级。
文章开头展示了早期(V1版本)的代码实现。当时为了让大家快速理解,作者分别写了 NacosCloudRefresherHandlerV1 和 ApolloRefresherHandlerV1。
详见第6部分已修改bug无报错里面的NacosCloudRefresherHandlerV1文件
1. 发现问题(Design Smells)
这两个类虽然能工作,但存在明显的坏味道:
- 重复代码(DRY原则被打破):
- 两个类都实现了
ApplicationRunner接口。 - 两个类都有
refreshThreadPoolProperties方法,里面的逻辑(解析配置、绑定 Bean、打印日志)是完全一样的。
- 两个类都实现了
- 扩展性差:
- 如果要支持 Zookeeper、Etcd 或 Consul,开发者需要把这些重复代码再复制一遍。
- 如果未来要修改配置解析的逻辑(比如增加 JSON 支持),需要去修改每一个配置中心的实现类,容易漏改。
- 行为不可控:
- 缺乏统一的流程控制。比如:我想在注册监听器之前统一做一些日志记录或权限校验,现在需要在每个类里单独写。
结论: 我们需要一种机制,把变与不变分离开来。
二、 核心武器:模板方法模式
文章引入了设计模式中的模板方法模式来解决上述问题。
1. 什么是模板方法?
通俗理解:“流程标准化,细节定制化”。
父类(抽象类)规定好做事的步骤(第一步干嘛,第二步干嘛),其中通用的步骤父类自己实现,差异化的步骤留给子类去实现。
2. 文章中的“登录”案例解读
作者用登录流程做类比,非常直观:
- 流程(父类定): 获取输入 -> 认证(变) -> 登录后处理。
- 实现(子类定):
- 账号密码登录类:认证 = 查数据库比对密码。
- 微信扫码登录类:认证 = 调用微信接口校验 Token。
好处: 无论增加多少种登录方式,主流程永远不用改,只需要写那个“认证”的小逻辑。
三、 重构实战:动态线程池的架构演进
这是文章最核心的技术部分。作者定义了一个抽象类 AbstractDynamicThreadPoolRefresher。

1. 角色分工
- 抽象父类 (
AbstractDynamicThreadPoolRefresher):负责“公有”逻辑。- 实现了
ApplicationRunner接口(Spring Boot 启动后的入口)。 - 定义了标准启动流程:
before->registerListener->after。 - 实现了配置解析与刷新的核心方法
refreshThreadPoolProperties。
- 实现了
- 具体子类 (
Nacos...Handler,Apollo...Handler):负责“特有”逻辑。- 只需要实现
registerListener方法,调用各自 SDK 的 API 去监听变化。
- 只需要实现
2. 关键代码深度解析

A. 抽象父类的 run 方法(模板骨架)
@Override
public void run(ApplicationArguments args) throws Exception {
beforeRegister(); // 钩子方法
registerListener(); // 【核心】抽象方法,由子类实现
afterRegister(); // 钩子方法
}
- 解读: 这里锁死了执行顺序。不管你是 Nacos 还是 Apollo,启动时必须按这个顺序走。
before和after是钩子(Hook),留给未来扩展用(比如埋点监控),目前默认是空的。
B. 抽象父类的 refreshThreadPoolProperties(复用逻辑)
public void refreshThreadPoolProperties(String configInfo) {
// 1. 解析配置(YAML/Properties 转 Map)
// 2. 绑定到 Java Bean
// 3. 【重点】发布事件
ApplicationContextHolder.publishEvent(new ThreadPoolConfigUpdateEvent(this, refresherProperties));
}
- 解读: 这段代码以前散落在各个子类中,现在统一上收。
- 深度知识点 - 事件发布(Event Publish): 作者在这里用了一个很巧妙的观察者模式(Spring Event)。刷新器只负责“拿到配置并解析”,然后它大喊一声:“配置变啦!”(
publishEvent)。- 谁关心配置变了?可能是 Web 容器的线程池,可能是普通的业务线程池。
- 这样做实现了解耦。刷新器不需要知道具体的线程池在哪里,它只负责通知。
C. 子类实现(以 Nacos 为例)
public class NacosCloudRefresherHandler extends AbstractDynamicThreadPoolRefresher {
// ... 构造函数 ...
@Override // 只需要重写这就行了
public void registerListener() throws NacosException {
// 调用 Nacos SDK 监听
configService.addListener(dataId, group, new Listener() {
@Override
public void receiveConfigInfo(String configInfo) {
// 监听到变化后,调用父类的通用刷新逻辑
refreshThreadPoolProperties(configInfo);
}
});
}
}
- 解读: 代码量大幅减少,逻辑非常清晰。开发者只需关注如何调用 Nacos 的 API 即可。
四、 架构总结与深度思考
1. 重构前后的对比
| 维度 | 重构前 (V1) | 重构后 (模板方法) |
|---|---|---|
| 代码结构 | 冗余,到处是重复的解析逻辑 | 清晰,父类管流程,子类管实现 |
| 扩展新中间件 | 复制粘贴代码,容易出错 | 继承抽象类,仅需实现 1 个方法 |
| 维护成本 | 修改公共逻辑需改 N 个文件 | 修改公共逻辑只需改父类 1 个文件 |
| 设计模式 | 无 | 模板方法模式 + 观察者模式 |
2. 为什么说这是“中间件思维”?
做业务开发时,我们往往只关注“当下能跑通”。但做中间件(Infrastructure)开发,必须考虑:
- 通用性: 不能绑定死某一个配置中心。
- 扩展性(Open-Closed Principle): 对扩展开放(支持 Etcd 很容易),对修改关闭(不需要改动核心流程代码)。
3. 这里的时序图解读
文章最后的时序图展示了完整的生命周期:
- 启动阶段: SpringBootApp 启动 -> 调用
run()-> 执行registerListener()-> 向 Nacos/Apollo 注册监听。 - 触发阶段: 运维在 Nacos 改配置 -> Nacos 推送 -> 子类
Listener收到 -> 调用父类refresh-> 解析配置 -> 发布 Spring 事件 -> 最终更新线程池参数。
结合你提供的目录结构和之前讨论的“模板方法模式”重构思路,我们需要对代码进行重新组织。目前的目录结构中,starter 模块里存放了 V1 版本的实现,这是我们需要改造的地方。
以下是基于你现有工程目录的最佳实践落地指南。我们将代码分为 事件定义、抽象模板、具体实现 和 自动装配 四个步骤。
第一步:定义事件与监听器 (在 spring-base 模块)
为了解耦“收到配置变更”和“刷新线程池”这两个动作,我们需要定义一个 Spring 事件。建议放在 spring-base 模块,因为它是 Spring 基础能力的封装。
1. 定义刷新事件 (ThreadPoolConfigUpdateEvent.java)
位置建议: spring-base/src/main/java/com/nageoffer/onethread/spring/event/ThreadPoolConfigUpdateEvent.java (新建包 event)
package com.nageoffer.onethread.spring.event;
import com.nageoffer.onethread.core.config.BootstrapConfigProperties;
import org.springframework.context.ApplicationEvent;
/**
* 线程池配置更新事件
*/
public class ThreadPoolConfigUpdateEvent extends ApplicationEvent {
private final BootstrapConfigProperties properties;
public ThreadPoolConfigUpdateEvent(Object source, BootstrapConfigProperties properties) {
super(source);
this.properties = properties;
}
public BootstrapConfigProperties getBootstrapConfigProperties() {
return properties;
}
}
第二步:创建抽象模板类 (在 starter 模块)
这是核心重构点。我们在 starter 模块中创建一个新的包 refresher,将公共逻辑(解析 YAML/Properties、数据绑定、发布事件)抽取出来。
2. 抽象刷新处理器 (AbstractDynamicThreadPoolRefresher.java)
位置建议: starter/src/main/java/com/nageoffer/onethread/starter/refresher/AbstractDynamicThreadPoolRefresher.java
package com.nageoffer.onethread.starter.refresher;
import com.nageoffer.onethread.core.config.BootstrapConfigProperties;
import com.nageoffer.onethread.core.parser.ConfigParserHandler;
import com.nageoffer.onethread.spring.event.ThreadPoolConfigUpdateEvent;
import com.nageoffer.onethread.spring.toolkit.ApplicationContextHolder;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import java.util.Map;
@Slf4j
@RequiredArgsConstructor
public abstract class AbstractDynamicThreadPoolRefresher implements ApplicationRunner {
protected final BootstrapConfigProperties properties;
/**
* 核心模板方法:由子类实现具体的监听注册逻辑
*/
protected abstract void registerListener() throws Exception;
@Override
public void run(ApplicationArguments args) throws Exception {
registerListener();
}
/**
* 公共逻辑:刷新配置并发布事件
* @param content 配置中心的内容(String格式)
*/
public void refreshThreadPoolProperties(String content) {
try {
// 1. 解析配置 (Yaml/Properties -> Map)
Map<Object, Object> configMap = ConfigParserHandler.getInstance()
.parseConfig(content, properties.getConfigFileType());
// 2. 绑定到对象 (Map -> Java Bean)
ConfigurationPropertySource sources = new MapConfigurationPropertySource(configMap);
Binder binder = new Binder(sources);
BootstrapConfigProperties refreshedProps = binder.bind(
BootstrapConfigProperties.PREFIX,
Bindable.ofInstance(properties)
).get();
// 3. 发布事件 (通知监听者去修改线程池)
ApplicationContextHolder.publishEvent(
new ThreadPoolConfigUpdateEvent(this, refreshedProps)
);
} catch (Exception e) {
log.error("Failed to refresh thread pool properties", e);
}
}
}
第三步:具体实现类 (在 starter 模块)
不再使用 V1 版本,而是让 Nacos 和 Apollo 的处理器继承上面的抽象类。
3. Nacos 实现 (NacosCloudRefresherHandler.java)
位置建议: starter/src/main/java/com/nageoffer/onethread/starter/refresher/NacosCloudRefresherHandler.java
package com.nageoffer.onethread.starter.refresher;
import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.nacos.api.config.listener.Listener;
import com.nageoffer.onethread.core.config.BootstrapConfigProperties;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.Executor;
@Slf4j
public class NacosCloudRefresherHandler extends AbstractDynamicThreadPoolRefresher {
private final NacosConfigManager nacosConfigManager;
public NacosCloudRefresherHandler(BootstrapConfigProperties properties, NacosConfigManager nacosConfigManager) {
super(properties);
this.nacosConfigManager = nacosConfigManager;
}
@Override
protected void registerListener() throws Exception {
// 调用 Nacos SDK 添加监听
nacosConfigManager.getConfigService().addListener(
properties.getNacos().getDataId(),
properties.getNacos().getGroup(),
new Listener() {
@Override
public Executor getExecutor() {
return null;
}
@Override
public void receiveConfigInfo(String configInfo) {
log.info("Received Nacos config change event.");
// 调用父类的公共刷新逻辑
refreshThreadPoolProperties(configInfo);
}
}
);
log.info("Nacos listener registered successfully.");
}
}
4. Apollo 实现 (ApolloRefresherHandler.java)
位置建议: 同上
package com.nageoffer.onethread.starter.refresher;
import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigService;
import com.ctrip.framework.apollo.model.ConfigChangeEvent;
import com.ctrip.framework.apollo.model.ConfigChangeListener;
import com.nageoffer.onethread.core.config.BootstrapConfigProperties;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class ApolloRefresherHandler extends AbstractDynamicThreadPoolRefresher {
public ApolloRefresherHandler(BootstrapConfigProperties properties) {
super(properties);
}
@Override
protected void registerListener() {
String namespace = properties.getApollo().getNamespace();
Config config = ConfigService.getConfig(namespace);
config.addChangeListener(new ConfigChangeListener() {
@Override
public void onChange(ConfigChangeEvent changeEvent) {
log.info("Received Apollo config change event.");
// Apollo 比较特殊,通常需要拉取整个文件内容,或者遍历变更key
// 这里假设我们能获取到完整的配置文件内容,或者根据变更项重组
// 为简化演示,这里需要根据 Apollo SDK 的 getConfigFile 接口获取内容
// String content = ...
// refreshThreadPoolProperties(content);
}
});
log.info("Apollo listener registered successfully.");
}
}
第四步:消费事件并更新线程池 (在 spring-base 或 core)
现在配置变了,事件发出了,必须有一个人接收事件并真正去修改 ThreadPoolExecutor 的参数。
5. 核心事件监听器 (DynamicThreadPoolRefreshListener.java)
位置建议: spring-base/src/main/java/com/nageoffer/onethread/spring/listener/DynamicThreadPoolRefreshListener.java
package com.nageoffer.onethread.spring.listener;
import com.nageoffer.onethread.core.config.BootstrapConfigProperties;
import com.nageoffer.onethread.core.executor.ThreadPoolRefresher;
import com.nageoffer.onethread.spring.event.ThreadPoolConfigUpdateEvent;
import org.springframework.context.ApplicationListener;
/**
* 监听配置更新事件,执行实际的线程池参数修改
*/
public class DynamicThreadPoolRefreshListener implements ApplicationListener<ThreadPoolConfigUpdateEvent> {
@Override
public void onApplicationEvent(ThreadPoolConfigUpdateEvent event) {
BootstrapConfigProperties newProperties = event.getBootstrapConfigProperties();
// 调用 Core 模块的刷新器,遍历所有线程池进行参数比对和修改
// 假设 core 模块有 ThreadPoolRefresher 工具类
ThreadPoolRefresher.refresh(newProperties);
}
}
第五步:Spring 自动装配 (配置 DynamicThreadPoolAutoConfiguration)
最后,在 starter 模块的自动装配类中,根据条件加载不同的 Handler。
6. 修改 DynamicThreadPoolAutoConfiguration.java
位置: starter/src/main/java/.../config/DynamicThreadPoolAutoConfiguration.java
@Configuration
@EnableConfigurationProperties(BootstrapConfigProperties.class)
@Import({ApplicationContextHolder.class}) // 确保 ContextHolder 被加载
public class DynamicThreadPoolAutoConfiguration {
// 1. 注册核心事件监听器 (处理实际更新逻辑)
@Bean
public DynamicThreadPoolRefreshListener dynamicThreadPoolRefreshListener() {
return new DynamicThreadPoolRefreshListener();
}
// 2. 如果存在 Nacos 相关类,注册 Nacos 处理器
@Configuration
@ConditionalOnClass(name = "com.alibaba.cloud.nacos.NacosConfigManager")
@ConditionalOnProperty(prefix = "onethread.nacos", name = "data-id") // 简单的开启条件
static class NacosConfiguration {
@Bean
public NacosCloudRefresherHandler nacosCloudRefresherHandler(BootstrapConfigProperties properties,
NacosConfigManager nacosConfigManager) {
return new NacosCloudRefresherHandler(properties, nacosConfigManager);
}
}
// 3. 如果存在 Apollo 相关类,注册 Apollo 处理器
@Configuration
@ConditionalOnClass(name = "com.ctrip.framework.apollo.ConfigService")
@ConditionalOnProperty(prefix = "onethread.apollo", name = "namespace")
static class ApolloConfiguration {
@Bean
public ApolloRefresherHandler apolloRefresherHandler(BootstrapConfigProperties properties) {
return new ApolloRefresherHandler(properties);
}
}
}
总结
- 删除/废弃
V1后缀的类。 - Spring-Base 模块:负责定义事件 (
Event) 和最终执行更新的监听器 (Listener)。 - Starter 模块:负责定义抽象模板 (
Abstract...) 和具体配置中心的适配器 (Nacos/Apollo Handler),并通过 AutoConfig 进行装配。 - 流程:
ConfigCenter->Handler (Template)->Event->Listener->Core Executor Update。
这样设置后,你的代码结构清晰,且完全符合“模板方法模式”和“观察者模式”的结合,扩展性极强。

Comments NOTHING