Filter、Interceptor 与 AOP 的区别与选型

举报
yd_287908631 发表于 2026/11/02 08:51:54 2026/11/02
【摘要】 从执行时机、作用范围、能力边界三个维度给出选型依据。

"登录校验该用 Filter 还是 Interceptor""记录接口耗时用 AOP 还是拦截器""为什么 Filter 里拿不到 @RequestMapping 的信息"——这些问题本质上是同一个:三者的执行位置不同,能拿到的东西自然不同。本文把三者放进同一条请求链路,逐一说明它们的归属、时机与能力边界,并给出选型清单。适合需要搭建统一鉴权、日志、限流层的后端开发阅读。

一、一条请求的完整链路

以 Spring Boot 3 + 内嵌 Tomcat 为例,一个 HTTP 请求的经过顺序是:

Tomcat NIO Connector
  → FilterChain(Servlet 规范)
    → CharacterEncodingFilter
    → FormContentFilter
    → 自定义 Filter
    → DispatcherServlet#service
      → HandlerExecutionChain
        → HandlerInterceptor#preHandle
          → HandlerMethod(AOP 代理对象)
            → MethodInterceptor(@Around 等通知)
              → Controller 方法
            → MethodInterceptor 返回
          → HandlerInterceptor#postHandle
        → HandlerInterceptor#afterCompletion
    → Filter 出栈
  → 响应写出

关键结论:Filter 在 Servlet 层,Interceptor 在 Spring MVC 层,AOP 在方法调用层。越靠外,拿到的信息越少但覆盖面越广;越靠内,信息越丰富但作用范围越窄。

二、Filter

2.1 归属与时机

Filter 是 Servlet 规范定义的接口,由 Servlet 容器管理,Spring 只是提供 DelegatingFilterProxy 和 FilterRegistrationBean 来注册它。它在 DispatcherServlet 之前执行,请求和响应都会经过。

@Component
@Order(Ordered.HIGHEST_PRECEDENCE + 10)
public class TraceIdFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain chain) throws ServletException, IOException {
        String traceId = request.getHeader("X-Trace-Id");
        if (traceId == null || traceId.isBlank()) {
            traceId = UUID.randomUUID().toString().replace("-", "");
        }
        MDC.put("traceId", traceId);
        response.setHeader("X-Trace-Id", traceId);
        try {
            chain.doFilter(request, response);
        } finally {
            MDC.remove("traceId");
        }
    }
}

OncePerRequestFilter 保证在 FORWARD、INCLUDE 等分发场景下只执行一次,避免重复包装请求。直接实现 Filter 接口时,异步分发会导致多次执行。

2.2 能力边界

  • 能拿到 HttpServletRequest、HttpServletResponse,可包装请求体、修改响应头。
  • 拿不到 HandlerMethod,不知道最终会调用哪个 Controller 方法。
  • 能拦截静态资源、404 请求,因为它在 DispatcherServlet 之前。
  • 抛出异常不会进入 Spring MVC 的 HandlerExceptionResolver,除非异常发生在 DispatcherServlet 内部。Filter 里抛的异常由 Servlet 容器处理,跳转到 /error。

三、Interceptor

3.1 归属与时机

HandlerInterceptor 是 Spring MVC 的接口,由 HandlerMapping 返回的 HandlerExecutionChain 持有,在 DispatcherServlet#doDispatch 中被调用:

public interface HandlerInterceptor {
    default boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                              Object handler) throws Exception {
        return true;
    }
    default void postHandle(HttpServletRequest request, HttpServletResponse response,
                            Object handler, @Nullable ModelAndView modelAndView) throws Exception {
    }
    default void afterCompletion(HttpServletRequest request, HttpServletResponse response,
                                 Object handler, @Nullable Exception ex) throws Exception {
    }
}

执行规则:

  • preHandle 按 order 正序执行,返回 false 中断后续流程。
  • postHandle 按逆序执行,仅在 preHandle 全部通过且方法正常返回时执行。
  • afterCompletion 按逆序执行,无论成功或异常都会执行(前提是对应的 preHandle 已返回 true)。

3.2 能力边界

  • handler 参数在正常情况下就是 HandlerMethod,可以读到方法上的注解、参数类型、所属类。
  • 拿不到请求体(@RequestBody 的原始流),因为请求体在 HandlerAdapter 调用时才被读取。
  • 可以拿到 ModelAndView,适合做视图相关的处理。
  • 抛出的异常会进入 HandlerExceptionResolver,可被 @ExceptionHandler 捕获。

基于 HandlerMethod 做注解驱动的鉴权:

@Component
public class PermissionInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                             Object handler) throws Exception {
        if (!(handler instanceof HandlerMethod handlerMethod)) {
            return true;
        }
        RequirePermission annotation =
                handlerMethod.getMethodAnnotation(RequirePermission.class);
        if (annotation == null) {
            return true;
        }
        String required = annotation.value();
        Set<String> owned = currentUserPermissions();
        if (!owned.contains(required)) {
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write("{\"code\":403,\"message\":\"无权限\"}");
            return false;
        }
        return true;
    }
}

四、AOP

4.1 归属与时机

AOP 作用于 Bean 的方法调用,与 HTTP 请求无关。它由 AbstractAutoProxyCreator 在 Bean 初始化后生成代理,调用时经过 MethodInterceptor 链。

@Aspect
@Component
public class CostAspect {

    private static final Logger log = LoggerFactory.getLogger(CostAspect.class);

    @Around("@annotation(com.example.metrics.Metered)")
    public Object measure(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.nanoTime();
        try {
            return pjp.proceed();
        } finally {
            long cost = (System.nanoTime() - start) / 1_000_000;
            log.info("{}.{} cost={}ms",
                    pjp.getSignature().getDeclaringType().getSimpleName(),
                    pjp.getSignature().getName(), cost);
        }
    }
}

4.2 能力边界

  • 能拿到方法参数、返回值、异常,可修改入参和返回值。
  • 不依赖 Web 环境,Service、Repository、定时任务都能用。
  • 只拦截经过代理的调用,自调用失效。
  • 拿不到 HttpServletRequest,除非通过 RequestContextHolder 获取(要求当前线程处于请求上下文)。
  • 无法拦截 private、final、静态方法。

五、三者对比

| 维度 | Filter | Interceptor | AOP |

| --- | --- | --- | --- |

| 规范归属 | Servlet | Spring MVC | Spring AOP |

| 执行位置 | DispatcherServlet 之前 | DispatcherServlet 内部 | Bean 方法调用 |

| 作用对象 | 所有 HTTP 请求 | MVC 处理的请求 | 任意 Spring Bean 方法 |

| 能否拿到 HandlerMethod | 否 | 是 | 否(但能拿到方法签名) |

| 能否拿到请求体 | 是(可包装) | 否 | 否(需 RequestContextHolder) |

| 能否拦截静态资源 | 是 | 否 | 否 |

| 异常是否进入 @ExceptionHandler | 否 | 是 | 是 |

| 是否依赖 Web 环境 | 是 | 是 | 否 |

| 自调用是否生效 | 不适用 | 不适用 | 否 |

| 典型用途 | 编码、CORS、TraceId、请求包装 | 登录态、权限、接口日志 | 事务、缓存、重试、方法耗时 |

六、三者如何共享数据

一条请求上,三者经常需要传递上下文,比如 Filter 生成的 traceId、Interceptor 解析出的用户身份、AOP 记录的耗时。传递方式有三种:

// 1. ThreadLocal:最常用,需在请求结束时清理
public final class UserContext {
    private static final ThreadLocal<Long> USER_ID = new ThreadLocal<>();

    public static void set(Long id) { USER_ID.set(id); }
    public static Long get() { return USER_ID.get(); }
    public static void clear() { USER_ID.remove(); }
}
// 2. 请求属性:随请求生命周期,天然隔离
request.setAttribute("userId", userId);

// 3. MDC:专用于日志,会自动附加到每条日志
MDC.put("traceId", traceId);

ThreadLocal 必须清理,否则线程池复用时会把上一个请求的用户身份带给下一个请求,属于安全事故级别的问题。Filter 的 finally、Interceptor 的 afterCompletion、AOP 的 finally 都是合适的清理位置,取决于在哪儿设置。

七、选型清单

按需求对号入座:

  • 需要在请求体被读取前处理:用 Filter。例如统一包装 ContentCachingRequestWrapper 以便多次读取。
  • 需要拦截静态资源或 404:用 Filter。Interceptor 只处理被 DispatcherServlet 接管的请求。
  • 需要读取方法注解做鉴权:用 Interceptor。HandlerMethod#getMethodAnnotation 是最直接的方式。
  • 需要按 Controller 方法维度统计耗时并记录异常:用 Interceptor,afterCompletion 的 ex 参数能拿到异常。
  • 需要作用于 Service 层、定时任务、MQ 消费者:用 AOP。Interceptor 根本不覆盖这些场景。
  • 需要修改方法返回值或实现重试:用 AOP,只有它能拿到返回值并重新调用 proceed()。
  • 需要跨域、编码、压缩:用 Filter,这些是 Servlet 规范层的能力。

一个常见组合:Filter 生成 TraceId 并放入 MDC,Interceptor 做鉴权与访问日志,AOP 做 Service 层的方法耗时与重试。三者各管一段,不重叠。

八、一个组合实战

需求:所有接口生成 TraceId,/api/** 需要登录,Service 层的写操作记录耗时。三者各司其职:

// 1. Filter:最外层生成 TraceId,覆盖所有请求
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class TraceFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest req, HttpServletResponse resp,
                                    FilterChain chain) throws ServletException, IOException {
        String traceId = UUID.randomUUID().toString().substring(0, 8);
        MDC.put("traceId", traceId);
        resp.setHeader("X-Trace-Id", traceId);
        try {
            chain.doFilter(req, resp);
        } finally {
            MDC.remove("traceId");
        }
    }
}

// 2. Interceptor:需要 HandlerMethod,做登录校验
@Component
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest req, HttpServletResponse resp,
                             Object handler) throws IOException {
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        String token = req.getHeader("Authorization");
        if (token == null || !tokenService.verify(token)) {
            resp.setStatus(401);
            resp.setContentType("application/json;charset=UTF-8");
            resp.getWriter().write("{\"code\":401,\"message\":\"未登录\"}");
            return false;
        }
        UserContext.set(tokenService.parseUserId(token));
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest req, HttpServletResponse resp,
                                Object handler, Exception ex) {
        UserContext.clear();   // 必须清理 ThreadLocal
    }
}

// 3. AOP:不依赖 Web,覆盖 Service 方法
@Aspect
@Component
@Order(2)
public class ServiceCostAspect {
    @Around("execution(* com.example.service..*.save*(..)) "
            + "|| execution(* com.example.service..*.update*(..))")
    public Object cost(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.nanoTime();
        try {
            return pjp.proceed();
        } finally {
            log.info("{} cost={}ms", pjp.getSignature().toShortString(),
                    (System.nanoTime() - start) / 1_000_000);
        }
    }
}

三层顺序是:TraceFilter → LoginInterceptor → ServiceCostAspect。TraceId 在 Filter 生成,拦截器和切面的日志都能带上;登录态在 Interceptor 设置,切面可以读到 UserContext;耗时统计放在切面,能覆盖 Service 被定时任务、MQ 消费者调用的场景。

九、常见坑 / 最佳实践

  1. 在 Filter 里读取请求体导致 Controller 拿到空流。getInputStream 只能读一次。需要读取时用 ContentCachingRequestWrapper 包装并重写 getInputStream,或改用 Interceptor。
  2. Interceptor 的 preHandle 返回 false 但没写响应。请求会一直挂起直到客户端超时。返回 false 前必须写出状态码或响应体。
  3. Filter 里抛异常期待被 @RestControllerAdvice 捕获。Filter 在 DispatcherServlet 之外,异常不会进入 HandlerExceptionResolver。需要在 Filter 内部自己处理,或改用 HandlerExceptionResolver 级别的组件。
  4. @Order 与注册顺序混淆。Filter 的 @Order 只在通过 FilterRegistrationBean 或 @Component 自动注册时生效;Interceptor 的顺序由 InterceptorRegistry#order 决定,两者互不影响。
  5. AOP 切面里依赖 RequestContextHolder。异步线程中 RequestContextHolder 默认取不到请求,需要 RequestContextHolder.setRequestAttributes(attributes, true) 显式传递,或改用 TaskDecorator。
  6. 在 AOP 中修改返回值却返回了 null。@Around 必须返回 pjp.proceed() 的结果(或明确的新值),漏写 return 会让方法返回 null,且编译期不报错。

十、小结

  • Filter 在 Servlet 层,覆盖所有请求,拿不到 HandlerMethod,适合编码、CORS、TraceId、请求包装。
  • Interceptor 在 Spring MVC 层,能拿到 HandlerMethod 与 ModelAndView,适合鉴权、接口日志、访问统计。
  • AOP 在 Bean 方法层,不依赖 Web 环境,适合事务、缓存、重试、Service 耗时。
  • 越靠外覆盖面越广、信息越少;越靠内信息越丰富、作用范围越窄。
  • 选型的判断标准是"需要什么信息"和"需要覆盖哪些调用",而不是"哪个更常用"。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。