资讯动态

SpringMVC拦截器与过滤器:核心区别、执行顺序与实战选型指南

发布时间:2026/8/7 2:22:16 来源:尧图企业网站定制
1. 项目概述拦截器与过滤器的核心辨析在构建基于SpringMVC的Web应用时我们经常需要处理一些横切关注点比如权限校验、日志记录、请求参数预处理、响应内容加工等。这时候HandlerInterceptor拦截器和Filter过滤器这两个概念就会高频出现。很多开发者尤其是刚接触Spring生态的朋友常常会混淆它们觉得功能上似乎差不多都是“拦截请求干点事儿”。但事实上它们在技术栈中的定位、实现机制、执行时机乃至应用场景上都有着泾渭分明的区别。理解这些区别不仅是为了应付面试更是为了在实际项目中做出最合理、最优雅的技术选型避免因为用错工具而导致功能缺陷或性能瓶颈。今天我们就来彻底拆解SpringMVC拦截器和Servlet过滤器从源码设计、执行流程到实战场景把它们的异同和执行顺序讲透。简单来说过滤器是Servlet规范定义的标准组件作用于Web容器层面范围更广、更底层而拦截器是Spring MVC框架定义的组件作用于Spring的DispatcherServlet处理流程之内与Spring上下文深度集成功能更精细、更强大。一个请求从客户端发出到响应返回会依次经过过滤器链和拦截器链这个顺序是固定的但内部的细节却大有乾坤。搞不清这个顺序就可能出现“日志记录了但权限没拦住”或者“参数解密了但Spring没接收到”这类让人头疼的Bug。2. 核心概念与设计哲学深度解析要理解区别必须先回到它们的“出身”和设计目标。这就像理解螺丝刀和扳手虽然都能拧东西但一个针对螺丝一个针对螺母设计初衷不同用法自然不同。2.1 Servlet过滤器Web容器的守门人过滤器是Java EE现Jakarta EEServlet规范的一部分定义在javax.servlet现jakarta.servlet包中。它的核心接口是Filter。过滤器的设计哲学是提供一个通用的、声明式的机制用于对客户端请求和服务器响应进行预处理和后处理。核心特性与定位规范标准它是Java Web应用的标准任何实现了Servlet规范的Web容器如Tomcat, Jetty, Undertow都必须支持。这意味着你的过滤器代码可以几乎无修改地运行在不同的Web服务器上可移植性极强。作用范围广过滤器作用于Web容器级别。它过滤的是ServletRequest和ServletResponse对象。这意味着所有进入容器的请求和离开容器的响应都会经过过滤器无论这个请求是请求一个JSP、一个静态资源如.jpg, .css还是一个由Spring MVC的DispatcherServlet处理的动态请求。这是过滤器最强大的地方也是它与拦截器最根本的区别之一。功能纯粹过滤器主要对请求和响应的“流”进行操作。例如你可以通过ServletRequestWrapper和ServletResponseWrapper来修改请求参数、请求体或者修改响应头、响应体。常见的用例包括字符编码设置、敏感词过滤、压缩响应内容、记录全局访问日志等。与Spring上下文无关标准的Servlet过滤器在初始化时无法直接通过Autowired等方式注入Spring管理的Bean。因为它是由Web容器创建和管理的生命周期独立于Spring的ApplicationContext。虽然可以通过一些技巧如DelegatingFilterProxy让过滤器委托给Spring Bean执行但这本身也说明了它们的隔离性。2.2 Spring MVC拦截器MVC框架的流程钩子拦截器是Spring MVC框架的一部分定义在org.springframework.web.servlet包中。它的核心接口是HandlerInterceptor。拦截器的设计哲学是在Spring MVC处理请求的特定生命周期节点插入自定义逻辑实现与业务处理流程紧密相关的横切功能。核心特性与定位框架特定它是Spring MVC框架的专属特性。如果你的Web应用没有使用Spring MVC比如只用Spring Boot的WebFlux做响应式编程或者用其他MVC框架那么拦截器就不存在。作用范围精准拦截器作用于Spring的DispatcherServlet内部。只有当请求被DispatcherServlet接收并且已经成功映射到一个具体的处理器Handler通常是我们写的Controller中的方法之后拦截器链才会开始执行。这意味着对于静态资源的请求、直接访问的JSP页面或者未被DispatcherServlet处理的请求拦截器是不会触发的。生命周期钩子丰富HandlerInterceptor接口定义了三个方法对应了处理器执行的关键节点preHandle在处理器方法执行之前被调用。可以进行权限校验、参数预处理等。返回true则继续执行拦截器链和处理器返回false则中断流程后续拦截器和处理器都不会执行。postHandle在处理器方法执行之后但在视图渲染之前被调用。此时可以修改ModelAndView对象向模型添加公共数据等。afterCompletion在整个请求处理完毕即视图渲染完毕之后被调用。通常用于资源清理、性能监控统计计算整个请求耗时等。注意即使preHandle返回false已经执行了preHandle且返回true的拦截器的afterCompletion方法依然会被调用这为资源清理提供了保障。深度集成Spring拦截器本身是由Spring IoC容器创建和管理的Bean。因此你可以在拦截器中轻松地使用Autowired注入其他Spring Bean如Service、Repository、配置属性等实现非常复杂的业务逻辑。这是拦截器相对于过滤器的一大优势。一个关键类比想象一个公司的前台Web容器。过滤器就像是公司大门的安检Filter每个进出的人请求/响应都必须经过安检检查包裹、登记信息。而拦截器就像是某个特定部门如研发部对应DispatcherServlet内部的会议室管理规则。只有已经进入研发部、并且预定了一间具体会议室映射到Handler的访客才会触发这些规则Interceptor比如“进入会议室前必须签到preHandle”、“会议结束后要填写反馈表postHandle”、“所有人离开后要关灯锁门afterCompletion”。那些只是来送快递访问静态资源或者去其他部门的人不会触发研发部的会议室规则。3. 执行流程与顺序的逐帧拆解理解了各自的身份我们来看它们是如何协作的。一个HTTP请求的处理流水线是理解顺序的关键。下图清晰地展示了从请求到响应的完整路径请求生命周期Filter-DispatcherServlet-Interceptor-Controller-Interceptor-View-Filter- 响应我们来分解这个流程中的每一个关键步骤3.1 第一阶段过滤器链的预处理请求到达容器HTTP请求到达Tomcat等Servlet容器。创建请求/响应对象容器创建HttpServletRequest和HttpServletResponse对象。执行过滤器链容器根据web.xml或WebFilter注解Spring Boot中常用FilterRegistrationBean定义的顺序依次调用每个过滤器的doFilter方法。在doFilter内部开发者可以对ServletRequest进行包装或修改如HttpServletRequestWrapper。对ServletResponse进行包装或修改如HttpServletResponseWrapper。执行核心逻辑如权限判断。调用FilterChain.doFilter(request, response)将请求传递给链中的下一个过滤器或者如果这是最后一个过滤器则传递给目标Servlet对于Spring MVC应用就是DispatcherServlet。如果某个过滤器在doFilter中没有调用FilterChain.doFilter那么请求就在这里被拦截直接返回响应后续所有过滤器、DispatcherServlet、拦截器、控制器都不会执行。这是过滤器拦截请求的唯一方式。3.2 第二阶段DispatcherServlet与拦截器链进入DispatcherServlet经过所有过滤器后请求到达Spring MVC的核心——DispatcherServlet。映射处理器DispatcherServlet根据请求URL通过HandlerMapping找到对应的处理器Handler和拦截器链HandlerExecutionChain。这个链里包含了匹配到的所有HandlerInterceptor。执行拦截器preHandle按顺序执行拦截器链中每个拦截器的preHandle方法。如果某个preHandle返回false则该拦截器之后的拦截器的preHandle不会执行。对应的处理器Controller方法不会执行。但是之前已经执行过且preHandle返回true的拦截器其afterCompletion方法仍会被调用注意不是postHandle。然后流程直接跳到第10步执行过滤器的后处理。执行控制器方法所有拦截器的preHandle都返回true后DispatcherServlet通过HandlerAdapter调用实际的处理器方法如RequestMapping标注的方法执行业务逻辑并返回一个ModelAndView或ResponseBody直接写回响应。执行拦截器postHandle处理器执行完毕后倒序执行拦截器链中每个拦截器的postHandle方法。注意这里是倒序这给了拦截器一个“后进先出”的加工机会。渲染视图如果返回的是视图名DispatcherServlet会调用ViewResolver解析视图然后由View进行渲染将模型数据填入模板生成最终的响应内容。对于ResponseBody内容已在第7步写入响应流。执行拦截器afterCompletion视图渲染完成后或异常发生后倒序执行拦截器链中每个拦截器的afterCompletion方法。同样也是倒序。这是进行最终清理和统计的最终位置。3.3 第三阶段过滤器链的后处理返回过滤器链DispatcherServlet处理完毕控制权返回到过滤器链。执行过滤器后处理逻辑每个过滤器的doFilter方法中在调用FilterChain.doFilter()之后的代码此时开始执行。因为FilterChain.doFilter()是一个“分水岭”其调用前的代码是请求预处理调用后的代码是响应后处理。在这里你可以对已经由Spring MVC处理过的ServletResponse进行最终修改比如添加统一的响应头、对响应体进行加密或压缩。响应返回客户端响应经过所有过滤器的后处理最终由Web容器发送回客户端。3.4 顺序总结与记忆口诀总体顺序Filter(前置) -Interceptor.preHandle-Controller-Interceptor.postHandle(倒序) -View Render-Interceptor.afterCompletion(倒序) -Filter(后置)记忆口诀“先滤后拦先正后倒”。先滤后拦过滤器在拦截器之前开始执行请求进入时也在拦截器之后结束执行响应返回时。过滤器包裹着整个Spring MVC处理流程。先正后倒拦截器的preHandle是正序执行而postHandle和afterCompletion是倒序执行。想象成一个栈Stack的压入和弹出过程。4. 实战场景与选型指南理论清晰了到底该用哪个下面通过几个典型场景来分析。4.1 必须使用过滤器的场景全局字符编码设置你需要处理所有请求的编码包括静态文件、JSP、API。这必须在请求到达任何具体处理逻辑之前完成。// 示例在Filter中设置UTF-8编码 Component public class CharacterEncodingFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { request.setCharacterEncoding(UTF-8); response.setCharacterEncoding(UTF-8); chain.doFilter(request, response); // 必须调用否则请求中断 } } // 在Spring Boot中通过配置类注册 Configuration public class FilterConfig { Bean public FilterRegistrationBeanCharacterEncodingFilter registrationBean() { FilterRegistrationBeanCharacterEncodingFilter bean new FilterRegistrationBean(); bean.setFilter(new CharacterEncodingFilter()); bean.addUrlPatterns(/*); // 匹配所有路径 bean.setOrder(Ordered.HIGHEST_PRECEDENCE); // 设置最高优先级最先执行 return bean; } }静态资源访问控制/日志你想记录所有对/images/,/css/等目录的访问日志。这些请求根本不会进入DispatcherServlet拦截器无效必须用过滤器。响应内容压缩GZIP你想对所有文本响应JSON, HTML进行GZIP压缩以节省带宽。这需要在响应最终发出前对输出流进行包装和压缩是典型的过滤器后处理逻辑。跨域资源共享CORS虽然Spring MVC提供了CrossOrigin注解和WebMvcConfigurer配置但在某些复杂场景如需要动态配置、支持预检请求OPTIONS下一个配置完善的CORS过滤器是更通用和可靠的选择因为它能处理所有类型的请求。4.2 优先考虑拦截器的场景基于会话Session或Token的权限校验你需要判断用户是否有权限访问某个Controller的某个方法。这需要用到Spring管理的用户服务、Token解析服务等Bean。拦截器可以方便地注入这些Bean并且在preHandle中你已经能获取到具体的处理器Handler信息可以做更细粒度的权限判断比如基于注解。Component public class AuthInterceptor implements HandlerInterceptor { Autowired private UserService userService; // 方便注入Spring Bean Autowired private JwtTokenUtil jwtTokenUtil; Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 1. 判断handler类型避免静态资源等 if (!(handler instanceof HandlerMethod)) { return true; } // 2. 获取方法上的注解进行精细控制 HandlerMethod handlerMethod (HandlerMethod) handler; RequiresPermission anno handlerMethod.getMethodAnnotation(RequiresPermission.class); if (anno null) { return true; } // 3. 执行复杂的、依赖Spring Bean的权限校验逻辑 String token request.getHeader(Authorization); User user jwtTokenUtil.parseToken(token); if (user null || !userService.hasPermission(user, anno.value())) { response.sendError(HttpStatus.FORBIDDEN.value(), 权限不足); return false; // 中断流程 } // 将用户信息放入请求属性供Controller使用 request.setAttribute(currentUser, user); return true; } } // 通过WebMvcConfigurer注册 Configuration public class WebConfig implements WebMvcConfigurer { Autowired private AuthInterceptor authInterceptor; Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(authInterceptor) .addPathPatterns(/api/**) // 只拦截API路径 .excludePathPatterns(/api/auth/login); // 排除登录接口 } }接口耗时监控你想记录每个Controller方法的执行时间。在preHandle中记录开始时间存入request属性在afterCompletion中计算耗时并打印日志。这需要用到处理器执行完毕的钩子过滤器难以在Controller粒度上实现。统一处理Controller的返回结果在postHandle中你可以修改ModelAndView为所有视图添加一些公共模型数据如当前年份、用户菜单。虽然现在更推荐使用ControllerAdvice配合ModelAttribute但拦截器在某些场景下仍是一种选择。防重复提交在preHandle中根据请求参数和用户身份生成一个唯一令牌并检查该令牌是否已被使用过。这需要与业务状态如Redis中的令牌记录交互拦截器能方便地注入RedisTemplate等Bean。4.3 混合使用与协作案例一个成熟的Web应用通常会同时使用两者各司其职。案例一个安全的API网关层Filter 1 (LoggingFilter)最先执行记录所有进出的原始请求和响应日志包括静态资源。它只关心流量不关心业务。Filter 2 (CorsFilter)处理跨域请求添加必要的响应头。Filter 3 (CharacterEncodingFilter)设置请求/响应的编码。DispatcherServletInterceptor 1 (AuthInterceptor)进行JWT Token解析和基础身份认证将用户信息放入请求属性。Interceptor 2 (PermissionInterceptor)进行细粒度的接口权限校验。Controller执行业务逻辑。Interceptor 2 (postHandle)可能无需操作。Interceptor 1 (postHandle)可能无需操作。Interceptor 1 2 (afterCompletion)清理线程局部变量记录最终访问日志包含业务状态。Filter 4 (ResponseWrapperFilter)对API返回的JSON格式进行统一包装如添加code,msg,data字段或进行响应加密。Filter 1 (后处理)记录最终的响应大小和状态码。在这个协作中过滤器负责协议层、传输层的通用处理而拦截器负责业务层、应用层的特定逻辑。5. 常见陷阱、疑难排查与最佳实践即使理解了原理在实际编码和运维中还是会遇到各种坑。下面分享一些实战中积累的经验。5.1 典型问题排查清单问题现象可能原因排查思路与解决方案拦截器对静态资源不生效静态资源请求未经过DispatcherServlet。1. 检查拦截器的addPathPatterns确保没有错误地包含了静态资源路径。2. 确认Spring MVC的静态资源处理配置如ResourceHandlerRegistry默认情况下静态资源由容器或Spring的ResourceHttpRequestHandler处理不经过拦截器链。这是正常行为如需拦截应使用过滤器。Autowired在过滤器中为null过滤器由Servlet容器管理非Spring Bean。1. 让过滤器类实现ServletContextAware等接口来获取Spring上下文较为复杂。2.推荐使用DelegatingFilterProxySpring Boot默认或FilterRegistrationBean包装一个Spring Bean作为过滤器。在Spring Boot中最简单的方式是直接Component定义一个FilterSpring Boot会自动将其注册为DelegatingFilterProxy。拦截器的postHandle或afterCompletion未执行1. 对应的preHandle返回了false。2. 控制器方法或更早的流程中抛出了未处理的异常。1. 检查preHandle逻辑确保在正常流程下返回true。2. 使用全局异常处理器ControllerAdviceExceptionHandler捕获异常确保流程能正常走到视图渲染阶段。注意即使有异常afterCompletion仍会执行前提是preHandle返回了true这是进行资源清理的好地方。修改了请求参数但Controller获取不到在过滤器中修改request参数的方式不对。HttpServletRequest的参数Map默认是只读的。必须使用HttpServletRequestWrapper重写getParameter,getParameterMap等方法来实现参数的修改。直接调用request.setAttribute()设置的是请求属性不是请求参数。过滤器顺序混乱依赖了未声明的过滤器顺序。在Spring Boot中使用Order注解或实现Ordered接口来定义过滤器的执行顺序。数字越小优先级越高越先执行。对于FilterRegistrationBean使用setOrder方法。切记过滤器的“后处理”代码执行顺序与“前处理”相反是“先进后出”。响应已被提交无法修改头信息在拦截器postHandle或过滤器的后处理中尝试调用response.sendRedirect()或设置头信息但此时响应流可能已关闭或提交。1. 重定向操作应尽量在preHandle或控制器方法中完成。2. 修改响应头尽量在过滤器链的最开始或控制器的处理阶段。3. 对于响应内容的修改如包装JSON确保使用HttpServletResponseWrapper包装响应并小心处理输出流的关闭时机。5.2 最佳实践与心得职责分离保持纯粹让过滤器做它擅长的事编码、压缩、全局日志、CORS让拦截器做它擅长的事认证、授权、业务日志、性能监控。避免在过滤器中写大量业务逻辑也避免用拦截器去处理静态资源。警惕性能瓶颈过滤器和拦截器在每个请求中都会执行其中的代码必须是轻量级、无阻塞的。避免在其中进行复杂的数据库查询、远程HTTP调用等IO操作。如果必须做考虑异步处理或缓存。善用Spring Boot的自动化配置对于字符编码、CORS等通用需求Spring Boot提供了spring.http.encoding.charset,spring.mvc.cors.*等配置属性通常无需自己编写过滤器。先查阅官方文档看是否有现成的、经过充分测试的配置。拦截器的afterCompletion是资源清理的保险栓无论请求处理成功还是抛出异常只要对应的preHandle返回了trueafterCompletion就一定会被执行。这是释放线程局部变量ThreadLocal、关闭非托管资源如手动打开的数据库连接的绝佳位置。测试你的拦截链编写集成测试模拟发送请求验证过滤器、拦截器、控制器的执行顺序和结果是否符合预期。特别是当你有多个过滤器和拦截器时顺序测试至关重要。关于ComponentvsWebFilter在Spring Boot中如果你希望过滤器能使用Autowired注入Bean推荐使用Component或Configuration中定义Bean的方式让Spring管理过滤器的生命周期。单纯的WebFilter注解需要配合ServletComponentScan使用且该过滤器实例不是Spring Bean无法直接注入依赖。理解SpringMVC拦截器与过滤器的区别和执行顺序是构建健壮、可维护Web应用的基石。它帮助你清晰地规划不同层次的处理逻辑避免功能冲突和循环依赖。下次当你需要添加一个全局处理逻辑时不妨先问自己几个问题这个逻辑需要处理静态资源吗它需要访问Spring容器中的Bean吗它需要在Controller方法执行前、后还是视图渲染后执行回答完这些问题该用Filter还是Interceptor顺序如何自然就清晰了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价