竹简文档
过滤器

上下文过滤器

ContextFilter 以最高优先级为每个请求初始化上下文标识,支持 UUID 提取与 MDC 日志追踪

ContextFilter

ContextFilter 是优先级最高的过滤器(@Order(HIGHEST_PRECEDENCE)),负责在每个请求中注入链路追踪标识(UUID)与请求开始时间戳。这些信息将被 ContextHolderResultUtil 和日志切面等组件消费。

MVC vs WebFlux 对比

对比项MVC 版本WebFlux 版本
接口OncePerRequestFilterWebFilter
存储方式ThreadLocalReactor Context
请求对象HttpServletRequestServerWebExchange
清理方式finally 块手动清理Reactor 自动清理
自动配置类BaseSdkAutoConfigurationWebFluxSdkAutoConfiguration

存储方式的本质区别

仅 MVC

MVC 版本使用 ThreadLocal 存储上下文信息:

对比
// MVC 版本 - ThreadLocal
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
    ThreadLocal<String> contextId = new ThreadLocal<>();
    contextId.set(UUID.randomUUID().toString());
    chain.doFilter(request, response);
    contextId.remove();
}

仅 WebFlux

WebFlux 版本使用 Reactor Context:

对比
// WebFlux 版本 - Reactor Context
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
    String uuid = UUID.randomUUID().toString();
    return chain.filter(exchange)
            .contextWrite(Context.of(
                    ContextConstant.CONTEXT_KEY, uuid,
                    ContextConstant.START_TIME_KEY, System.currentTimeMillis()
            ));
}

执行流程

HTTP 请求进入


检查请求方法是否为 OPTIONS → 是 → 跳过,直接放行



检查 URL 是否在排除列表中 → 是 → 跳过,直接放行



检查 Handler 是否标注 @IgnoreContext(仅 MVC)→ 是 → 跳过



生成或提取 UUID


设置 MDC(日志追踪)


注入上下文(ThreadLocal / Reactor Context)


执行后续过滤器链和业务逻辑


清理上下文(仅 MVC,WebFlux 自动处理)

配置属性

application.yml
bamboo:
  context:
    enable-input: true
    exclude-urls:
      - /actuator/**
      - /health
      - /favicon.ico

字段

类型

仅 WebFlux

WebFlux 版本额外支持以下配置:

字段

类型

UUID 获取策略

enable-input = true ?

    ├── 是 → 检查请求头中是否存在 UUID
    │       │
    │       ├── 存在 → 使用请求头中的 UUID
    │       └── 不存在 → 生成新的 UUID

    └── 否 → 始终生成新的 UUID

enable-inputtrue 时,过滤器从请求头中提取 UUID;若请求头中无有效 UUID,则自动生成。这一机制适用于微服务场景中的链路追踪,上游服务可通过请求头传递上下文标识。

@IgnoreContext 注解(仅 MVC)

仅 MVC

对于不需要上下文注入的 Handler 方法,可使用 @IgnoreContext 注解跳过:

HealthController.java
@RestController
public class HealthController {

    // 该方法不会触发上下文初始化
    @IgnoreContext
    @GetMapping("/health")
    public String health() {
        return "OK";
    }
}

过滤器结构

仅 MVC

ContextFilter.java
@Order(Ordered.HIGHEST_PRECEDENCE)
public class ContextFilter extends OncePerRequestFilter {

    private final ContextProperties properties;
    private final HandlerMapping handlerMapping;

    @Override
    protected void doFilterInternal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain filterChain) throws ServletException, IOException {

        // 跳过 OPTIONS 请求
        if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
            filterChain.doFilter(request, response);
            return;
        }

        // 跳过排除的 URL
        if (isExcluded(request.getRequestURI())) {
            filterChain.doFilter(request, response);
            return;
        }

        // 检查 @IgnoreContext 注解
        HandlerMethod handler = HttpServletUtil.getHandlerMethod(
            request, handlerMapping);
        if (handler != null && hasIgnoreContext(handler)) {
            filterChain.doFilter(request, response);
            return;
        }

        try {
            // 生成或提取 UUID
            String contextId = resolveContextId(request);
            MDC.put("contextId", contextId);
            ContextHolder.initContext(contextId);

            filterChain.doFilter(request, response);
        } finally {
            // 确保清理,防止线程池复用导致的上下文泄漏
            ContextHolder.clear();
            MDC.clear();
        }
    }
}

仅 WebFlux

ContextFilter.java
// 最高优先级的上下文注入过滤器
@Order(Ordered.HIGHEST_PRECEDENCE)
public class ContextFilter implements WebFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        // 检查请求头中是否携带外部 UUID
        String externalContextId = exchange.getRequest().getHeaders()
                .getFirst(ContextConstant.CONTEXT_KEY);
        String uuid = (enableInput && externalContextId != null)
                ? externalContextId
                : UUID.randomUUID().toString();

        // 设置 MDC,使日志输出包含 contextId
        MDC.put("contextId", uuid);

        // 通过 contextWrite 注入 Reactor Context
        return chain.filter(exchange)
                .contextWrite(Context.of(
                        ContextConstant.CONTEXT_KEY, uuid,
                        ContextConstant.START_TIME_KEY, System.currentTimeMillis()
                ));
    }
}

MDC 集成

过滤器将上下文标识写入 SLF4J 的 MDC,使日志框架自动携带追踪信息:

logback-spring.xml
<pattern>
  %d{yyyy-MM-dd HH:mm:ss} [%thread] [%X{contextId}] %-5level %logger - %msg%n
</pattern>

日志输出示例:

2026-01-15 10:00:00 [http-nio-8080-exec-1] [550e8400-e29b-41d4-a716-446655440000] INFO  UserController - 处理用户请求

注意事项

  • 该过滤器以 HIGHEST_PRECEDENCE 注册,确保在所有其他过滤器之前执行
  • MVC 版本:finally 块中的清理逻辑确保 ThreadLocal 和 MDC 不会泄漏至线程池中的其他请求
  • WebFlux 版本:Reactor Context 随响应式流自动传播和清理
  • 由自动配置类注册,无需手动声明

下一步

On this page