跳到主要内容
📖 本章预览

本章为预览版本,展示部分核心内容。完整内容包含详细源码解析、实战代码和面试要点,加入知识星球即可解锁全部章节。

第六章:线程池一跑 traceId 就丢了怎么破 — TaskWrapper 装饰器链

本章目标:理解 TaskWrapper 如何用装饰器模式解决跨线程上下文丢失问题,掌握 DtpRunnable 双引用设计的精妙之处,学会自定义 TaskWrapper 扩展。


6.1 问题复现

在 Web 应用中,框架会在当前线程的 MDC 中放入 traceId,方便全链路日志追踪。但当你把任务丢给线程池执行时:

// 主线程:MDC 中有 traceId = "abc123"
executor.execute(() -> {
log.info("处理订单"); // ← 这里 MDC 是空的,traceId 丢了!
});

线程池的工作线程和提交线程不是同一个线程,MDC 基于 ThreadLocal,数据不会自动传递。同样的问题也出现在 TransmittableThreadLocal、SecurityContext 等场景。

DynamicTP 的解决方案:在任务提交前,用装饰器链对 Runnable 层层包装,把上下文"捎带"进去。


6.2 TaskWrapper 极简接口

@FunctionalInterface
public interface TaskWrapper {

/**
* 包装器名称,用于配置文件中按名称选择
*/
default String name() {
return null;
}

/**
* 核心方法:接收原始 Runnable,返回增强后的 Runnable
*/
Runnable wrap(Runnable runnable);
}

设计精妙之处:

  • @FunctionalInterface 标注,只有一个抽象方法 wrap(),可以用 Lambda 实现
  • 输入一个 Runnable,输出一个 Runnable — 标准的装饰器模式签名
  • 它不关心你要传递什么上下文,只要求你把"传递逻辑"封装成 Runnable → Runnable 的变换

6.3 装饰器链执行

包装不是一次完成的,而是链式执行。核心逻辑在 TaskEnhanceAware 接口中:

public interface TaskEnhanceAware extends DtpAware {

default Runnable getEnhancedTask(Runnable command,
List<TaskWrapper> taskWrappers) {
// 1. 保存原始 Runnable 的引用
Runnable wrapRunnable = command;

// 2. 如果任务实现了 NamedRunnable,提取任务名称
String taskName = (wrapRunnable instanceof NamedRunnable)
? ((NamedRunnable) wrapRunnable).getName()
: null;

// 3. 装饰器链:按顺序逐个包装
if (CollectionUtils.isNotEmpty(taskWrappers)) {
for (TaskWrapper t : taskWrappers) {
wrapRunnable = t.wrap(wrapRunnable);
}
}

// 4. 最终封装为 DtpRunnable
return new DtpRunnable(command, wrapRunnable, taskName);
}
}

假设配置了 ["ttl", "mdc"],执行流程如下:

原始 Runnable (command)

├── TtlTaskWrapper.wrap() → TtlRunnable(捕获 TTL 快照)

├── MdcTaskWrapper.wrap() → MdcRunnable(捕获 MDC 快照)

└── new DtpRunnable(原始command, 最终wrapRunnable, taskName)

包装顺序由 taskWrappers 列表的顺序决定,最后包装的最先执行(洋葱模型)。


6.4 DtpRunnable 双引用设计

@Getter
public class DtpRunnable implements Runnable {

private final Runnable originRunnable; // 原始任务(未经任何包装)
private final Runnable runnable; // 包装后的任务(实际执行)
private final String taskName; // 任务名称

🔒 解锁完整内容

本章剩余内容需要解锁后查看

以上仅为本章部分预览内容,完整内容包含更多深度源码解析、实战代码和面试要点。

加入知识星球你将获得:

  • ✅ 全部 12 章完整内容 + 持续更新
  • ✅ 配套源码 + 实战项目
  • ✅ 一对一答疑 + 面试辅导
  • ✅ 简历优化 + 内推机会

📚 本章完整目录

以下为本章完整目录结构,加入知识星球即可解锁全部内容。

6.5 MdcTaskWrapper 源码精讲

6.6 TtlTaskWrapper

6.7 TaskWrappers 单例管理

6.8 完整调用链路

6.9 实战:自定义链路追踪 TaskWrapper

6.10 本章涉及的设计模式