Filter 先于 Interceptor 执行,顺序为 Filter → DispatcherServlet → Interceptor → Controller;Filter 处理原始 Servlet 请求,Interceptor 可注入 Spring Bean 并操作 MVC 组件。

Filter 和 Interceptor 谁先执行?顺序不能搞错
过滤器(Filter)一定在拦截器(HandlerInterceptor)之前执行,而且是「外层包内层」的关系:请求路径上依次是 Filter → DispatcherServlet → Interceptor → Controller。这意味着:
• Filter.doFilter() 里调用 chain.doFilter() 之前,你只能拿到原始的 ServletRequest/ServletResponse,拿不到 Spring 的 HttpServletRequest 或任何 IOC Bean;
• Interceptor.preHandle() 已经能注入 @Service、@Repository 等 Bean,也能访问 ModelAndView 和控制器参数;
• 如果你在 Filter 中修改了请求体(比如解密 payload),但没重置 request.getInputStream(),后续 @RequestBody 可能读到空内容——这是高频翻车点。
什么时候必须用 Filter,而不是 Interceptor?
当你需要处理以下场景时,Filter 是唯一选择:
• 拦截静态资源(/js/app.js、/images/logo.png),因为 Interceptor 根本不生效;
• 强制设置字符编码(如 request.setCharacterEncoding("UTF-8")),必须在进入 DispatcherServlet 前完成,否则 @RequestParam 中文会乱码;
• 全局 CORS 头添加(response.setHeader("Access-Control-Allow-Origin", "*")),尤其在未启用 Spring WebMvc 的跨域配置时;
• 请求体预处理(如 AES 解密、gzip 解压),因为 Interceptor 拿不到原始字节流,也无法替换 HttpServletRequestWrapper。
Interceptor 能做而 Filter 做不了的关键能力
HandlerInterceptor 的核心优势在于「深度集成 Spring 生态」:
• 可以直接 @Autowired 任意 Spring Bean(比如 UserService、RedisTemplate),Filter 里只能靠 WebApplicationContextUtils.getRequiredWebApplicationContext() 手动捞,且需确保上下文已初始化;
• postHandle() 能修改 ModelAndView,比如统一加 footer 数据;
• afterCompletion() 可捕获 Controller 抛出的异常,做日志或监控埋点,而 Filter 的 doFilter() 后续无法感知业务异常;
• 支持按路径精确匹配(如 "/api/**"),也支持排除特定后缀("/api/**.html"),Filter 的 @WebFilter(urlPatterns = "/*") 只能粗粒度控制。
注册方式和生命周期差异直接影响调试体验
两者的注册机制决定了你查问题时该看哪块日志:
• Filter 由 Servlet 容器(Tomcat/Jetty)管理:启动时调用 init() 一次,销毁时调用 destroy() 一次,所有请求共用同一个实例——所以它必须是线程安全的,不能在字段存 request-scoped 数据;
• Interceptor 由 Spring IoC 容器管理:可以是 prototype 作用域,也可以是 singleton;preHandle() 每次请求都新建栈帧,天然隔离;
• 注册方式也不同:Filter 用 @WebFilter 或 FilterRegistrationBean,Interceptor 必须通过 WebMvcConfigurer.addInterceptors() 注册;
• 错误表现差异明显:如果 Filter.init() 抛异常,应用直接启动失败;而 Interceptor.preHandle() 返回 false 只会让当前请求 404 或 403,不影响其他请求。
立即学习“Java免费学习笔记(深入)”;
真正容易被忽略的是:Filter 修改了 response 内容(比如压缩或加水印)后,必须确保没有提前 commit response(即没调用 response.getOutputStream().flush()),否则 Interceptor 的 afterCompletion() 还没执行,响应就发出去了——这种时序问题在压测时才暴露,但日志里根本看不出因果。










