Feign RequestInterceptor注入TraceId的关键是:通过TTL封装的上下文工具类获取TraceId,判空后写入RequestTemplate的"X-Trace-Id"头;需配对MVC拦截器初始化根ID,并确保@Async线程池经TtlExecutors包装。

Feign RequestInterceptor 怎么注入 TraceId 到请求头
关键在于拦截器必须在 Feign 发起 HTTP 请求前,把当前线程上下文里的 TraceId 取出来,塞进 RequestTemplate 的 header。不能依赖 MDC 自动输出——MDC 只影响日志格式,不参与网络传输。
常见错误是直接在拦截器里调用 MDC.get("traceId"),但 MDC 本身不跨线程,且 Feign 内部可能使用独立线程池(比如异步调用),此时 MDC 已为空。正确做法是:从统一的上下文工具类中取,该工具类底层用 ThreadLocal + TTL(TransmittableThreadLocal)封装,确保子线程可继承。
- 必须使用
TransmittableThreadLocal替代原生ThreadLocal,否则线程池场景下 TraceId 丢失 - Header 键名需与接收方约定一致,例如
"X-Trace-Id",不能写成"trace-id"或"X-B3-TraceId"(除非你集成 Sleuth) - 拦截器 Bean 必须被 Spring 扫描到,加
@Configuration或@Component,且不能是@Scope("prototype")
为什么 MVC 拦截器 + Feign 拦截器要配对使用
单靠 Feign 拦截器只能解决「服务 A → 服务 B」的出站透传,但无法处理「前端 → 服务 A」的入站初始化。如果前端没带 X-Trace-Id,服务 A 就没有根 TraceId,后续所有 Feign 调用都只能生成新 ID,链路就断了。
所以必须在入口(如 HandlerInterceptor.preHandle)做两件事:从请求头读 X-Trace-Id,有则设入上下文;无则生成 UUID 并设入上下文 + 响应头。这样 Feign 拦截器才能稳定拿到值。
- 响应头回写
X-Trace-Id是为了前端或网关能继续向下游传递,形成闭环 -
afterCompletion中必须调用clear(),否则线程复用时会污染下一个请求 - 若用 WebMvcConfigurer + addInterceptors 注册,注意拦截顺序:TraceId 拦截器应排在权限、日志等其他拦截器之前
Feign 拦截器里如何避免 NPE 和空字符串透传
最常踩的坑是直接取 LogTraceUtil.getTraceId() 后不做判空就塞进 header,结果发出去的是 X-Trace-Id: null 或 X-Trace-Id: "",下游解析失败或误认为新链路。
务必在拦截器 apply 方法里加防御性判断:
String traceId = LogTraceUtil.getTraceId();
if (StringUtils.hasText(traceId)) {
template.header("X-Trace-Id", traceId);
}
- 不要用
Objects.nonNull()判空,它对空字符串无效 - 不要在拦截器里生成新 TraceId——那是入口层的事,Feign 层只负责“透传”,不是“兜底”
- 若项目同时用 JWT 和 TraceId,两个 header 应分开注入,避免混淆逻辑
TTL(TransmittableThreadLocal)不生效的典型表现和修复点
现象是:本地调试正常,一上生产(尤其用了线程池、@Async、CompletableFuture)就丢 TraceId。根本原因是 JDK 原生 ThreadLocal 不支持线程池场景下的父子线程传递。
TTL 是唯一可靠解法,但容易配错:
- 必须引入
com.alibaba:transmittable-thread-local依赖,且版本 ≥ 2.12.2(低版本对 Spring Boot 3.x 兼容差) - 不能只改上下文工具类,还要在应用启动时注册 TTL 的 JVM agent 或显式调用
TtlAgent.premain()(推荐前者) - Spring 的
@Async线程池必须包装为TtlExecutors.getTtlExecutorService(),否则 @Async 内部仍用原生线程池
这个环节一旦漏掉,整个透传链路在异步分支就彻底断裂,而且问题极难复现——只在高并发、多线程混合调用路径下暴露。

















