跳到主要内容
约 6 分钟2 张卡

Spring 拦截机制

Filter、Interceptor 和 Spring AOP 都能在调用前后执行逻辑,但作用的层级不同。Filter 处理 Servlet 请求,Interceptor 围绕 MVC 处理器执行,AOP 围绕 Bean 方法调用执行。本文先比较它们能读取的信息和覆盖范围,再说明异常处理、异步线程清理与拦截器回调顺序。

1. 面试核心​

先记住这几条,再读正文
  • Filter 范围。 Filter 按 URL 与派发类型匹配 Servlet 请求,可包装原始请求和响应。卡 · Filter
  • MVC 拦截器。 HandlerInterceptor 在 MVC 找到处理器后执行,遇到 HandlerMethod 时可读取 Controller 方法和注解。
  • 正常回调顺序。 同步请求正常完成时,拦截器的 preHandle 按注册顺序执行,postHandle 和 afterCompletion 逆序执行。卡 · 拦截器回调顺序
  • 提前结束。 某个拦截器的 preHandle 返回 false 时,只有此前返回 true 的拦截器收到 afterCompletion 回调。
  • 异步与错误派发。 OncePerRequestFilter 默认跳过后续 ASYNC 与 ERROR 派发。

2. Filter​

Filter 是 Servlet 规范中的过滤器,用于在目标 Servlet 前后处理请求。它可以访问或包装原始 request/response,覆盖多个 Servlet。实际范围由 URL 和 dispatcher type 决定,后者表示请求派发的类型。比如 REQUEST 表示初始请求,ASYNC 表示异步派发,ERROR 表示错误派发。

需要覆盖 Servlet 请求入口的功能适合放在这一层:

  • 安全过滤链处理认证授权,CORS 处理跨域访问,编码过滤器设置字符编码。
  • 请求体包装、响应头设置和响应压缩直接操作请求或响应。
  • 对其他 Servlet 或静态资源的过滤,可以覆盖 DispatcherServlet 之外的目标。
  • 请求日志可以在 MVC 查找处理器之前建立请求 ID。

调用 chain.doFilter() 后,请求才进入下一过滤器或 Servlet。过滤器也可以直接写响应并返回,从而拒绝请求。后续调用返回后,过滤器可以清理资源,但这时响应已有可能提交。

Filter 进入时尚未执行 MVC 的处理器映射,所以依赖 Controller 注解的逻辑应放在映射完成之后。

3. HandlerInterceptor​

HandlerInterceptor 是 Spring MVC 的处理器拦截器,在 HandlerMapping 找到处理器后执行。因此,它能基于路由和控制器方法的信息做判断。下面先确认处理器是方法,再读取租户要求注解:

if (handler instanceof HandlerMethod method) {
RequiredTenant annotation =
method.getMethodAnnotation(RequiredTenant.class);
}

HandlerMethod 表示一个控制器方法,示例通过它读取方法上的 RequiredTenant 注解。静态资源等 handler 使用其他类型,所以读取前要先检查类型。

Interceptor 适合校验路由元数据、设置区域信息和记录轻量审计。请求未进入 DispatcherServlet,或尚未找到 handler 时,就不会走到这次拦截回调。

因此,通用 Web 认证授权优先使用 Spring Security 过滤链,业务方法授权使用 Method Security。Method Security 在方法调用时检查权限。手写 Interceptor 或切面可补充明确的规则,并用测试覆盖错误派发与静态资源等边界。

4. AOP​

AOP 用于给多个业务方法添加共有逻辑,例如事务或耗时记录。Spring AOP 拦截的是经过代理的 Bean 方法调用,调用入口可以来自 HTTP、消息消费或定时任务。

事务、方法级授权、重试和监控都可围绕业务方法实现。切面能观察方法参数、返回值和异常,但 Servlet 过滤链仍由 Filter 控制。若把 request/response 强行传入 service 切面,业务层就会依赖 Web 请求,其他入口也更难复用。

代理与拦截链机制见 Spring AOP。

5. 分层协作​

一次请求经过多个层级时,可以让各层记录自己能看到的信息。以请求追踪为例,可以这样分工:

  • Filter 读取或生成请求 ID,放入受控上下文,并在继续执行前写入响应头。
  • Interceptor 添加路径模板和 handler 名称,标明命中了哪个接口。
  • AOP 或监控库记录关键 service 调用的耗时,形成追踪中的子调用记录。
  • 当前线程退出时,在 finally 中清理 MDC 等线程上下文。MDC 是日志框架保存请求 ID 等字段的诊断上下文。

响应头需在响应提交前写入,过滤链返回后的 finally 则适合清理当前线程。此时应检查响应是否已提交,再决定还能修改哪些内容。各层按信息来源分工,也能避免重复记录同一份请求日志。

6. 异常处理范围​

判断异常由谁处理,要看它发生在哪一层。Interceptor 的 preHandle、Controller 调用等 MVC 阶段抛错后,会进入异常解析器链。@ControllerAdvice 中的异常处理方法也通过这条链调用。

Filter 在进入 DispatcherServlet 前自身抛错,需要由过滤链或 Servlet 错误机制处理。AOP 则只能捕获其方法调用链内的异常。比如 JSON 序列化发生在 Controller 返回之后,已经超出该方法切面的范围。

响应提交后,状态码和响应头已发给客户端,这时再进入错误处理也无法重写完整响应。

统一错误响应与处理范围见 API 错误契约。

7. 异步与 ThreadLocal​

异步请求会更换执行线程,所以请求上下文需要显式传递。Servlet 异步处理释放初始线程后,可通过 ASYNC 派发继续执行;@Async 也会把任务交给其他线程。

ThreadLocal 将数据保存在当前线程中,后续线程需要重新建立请求 ID 等上下文。可以使用框架提供的上下文传播机制,并在每个线程的退出路径中清理。这样,线程被下一个请求复用时,才不会带上前一个请求的数据。

OncePerRequestFilter 默认跳过后续 ASYNC 与 ERROR 派发。如果过滤器需要参与这些派发,要同时检查过滤钩子与注册设置:

  • 参与 ASYNC 时,让 shouldNotFilterAsyncDispatch() 返回 false。
  • 参与 ERROR 时,让 shouldNotFilterErrorDispatch() 返回 false。
  • 注册时包含对应的 dispatcher type,使用异步请求时还要启用异步支持。

允许 ASYNC 后,后续派发可再次执行过滤逻辑,因此每次执行都需清理当前线程的数据。线程退出与整个请求完成是两个时刻,观察请求最终完成要使用异步完成回调。

8. 拦截器回调顺序​

回调顺序决定了哪里可以修改返回结果,以及哪里负责释放资源。同步请求正常完成时,A、B 按注册顺序进入,再按相反顺序退出:

A.preHandle → B.preHandle → handler
→ B.postHandle → A.postHandle
→ 视图渲染 → B.afterCompletion → A.afterCompletion

@ResponseBody 的正文在适配器内部写出,早于 postHandle。因此,修改响应体应使用 ResponseBodyAdvice 等返回值扩展点,它能在正文写出前介入。

若 B 的 preHandle 返回 false,请求就在 B 处结束,handler 与全部 postHandle 都跳过。只有已经返回 true 的 A 收到 afterCompletion。因此,B 需要自行完成拒绝响应,并释放刚取得的资源。

handler 抛错时会跳过 postHandle,将异常交给解析器。此前 preHandle 返回 true 的拦截器仍会收到 afterCompletion。如果解析器已经处理异常,完成回调中的异常参数就可为 null。

开启 MVC 异步时,初始线程会调用异步拦截器的 afterConcurrentHandlingStarted,用于清理该线程的上下文。

随后派发的回调过程见 Spring MVC 请求生命周期。

9. 常见问题​

9.1 Controller 鉴权应该放在哪里?​

入口认证交给 Spring Security 安全过滤链,业务方法授权交给 Method Security。若还需要基于路由元数据补充校验,可以放在 Interceptor 中,并测试其覆盖范围。

9.2 Filter 能直接拿到 Controller 方法注解吗?​

10 · 面试题卡片​

10.1 Filter、Interceptor 和 AOP 应怎样区分​

出现公司:京东数科、旷视

考察重点:根据能读取的信息和覆盖范围区分三种机制,并解释异常处理与异步清理的边界。

相关内容:Filter至异常处理范围。

参考回答

Filter 拦截 Servlet 请求,Interceptor 拦截 MVC 处理器,AOP 拦截经过代理的 Bean 方法调用。

三者能读取的信息不同,所以应按功能需要选择。

Filter 可以包装请求和响应,也能覆盖其他 Servlet,适合 CORS、请求 ID 和通用 Web 安全。Interceptor 在映射后获得处理器,适合路由和 Controller 元数据逻辑。AOP 与 HTTP 无关,适合事务和 service 方法监控。

异常也要按发生位置处理:Filter 自身异常由过滤链或容器接手,MVC 异常进入解析器链。异步请求切换线程后,还要传递上下文,并在每次线程退出时清理。

10.2 第二个拦截器 preHandle 返回 false 后,哪些回调会执行?​

出现公司:暂无已核验的公开来源(本站自测)

考察重点:说明请求被拒绝后哪些回调会跳过、哪些拦截器负责清理,并区分响应正文的写出时机。

相关内容:拦截器回调顺序。

参考回答

只有第一个拦截器收到 afterCompletion,handler 与全部 postHandle 都被跳过。

因为完成回调只交给此前 preHandle 返回 true 的拦截器,第二个拦截器需要自行写出拒绝响应并清理资源。

正常返回正文时,@ResponseBody 已在适配器内部写出,所以修改正文应使用 ResponseBodyAdvice。

11. 参考资料​