在网站开发框架中,拦截器(Interceptor)和过滤器(Filter)的执行顺序是开发者最容易踩坑的问题之一。核心结论是:过滤器在请求进入DispatcherServlet之前执行,拦截器在DispatcherServlet处理请求之后执行,两者处于请求生命周期的不同阶段。简单来说,Filter → Interceptor(前置)→ Controller → Interceptor(后置)→ Filter(响应阶段)。搞清楚这个顺序,你就能解决90%的权限校验、日志记录和数据处理冲突问题。
很多开发者在实际项目中会遇到这样的困惑:明明在Filter里设置了请求头信息,为什么到了Interceptor里却拿不到?或者在Interceptor里做了权限校验,为什么Filter还是先执行了?这些问题的根源就在于没有理解两者在整个请求管道中的位置差异。今天这篇文章,我会从底层原理、执行流程图、具体代码示例、常见踩坑场景四个维度,把这个问题彻底讲透。
一、过滤器(Filter)的本质与执行位置过滤器是基于Servlet规范实现的,它工作在Servlet容器层面,也就是Tomcat、Jetty这些服务器内部。当一个HTTP请求到达服务器时,第一个接触到的就是Filter链。Filter可以对请求和响应进行预处理和后处理,它不关心你用的是Spring MVC还是其他框架,它是在框架启动之前就已经生效的。
Filter的典型应用场景包括:字符编码设置、跨域处理、XSS防护、请求日志记录、压缩响应等。它的生命周期完全由Servlet容器管理,通过web.xml或者@WebFilter注解来配置。一个请求可能经过多个Filter,它们按照配置顺序依次执行,形成一个责任链。
在Spring Boot中配置Filter的方式非常灵活,下面是一个典型的Filter实现:
@Component
@Order(1)
public class AuthFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String token = httpRequest.getHeader("Authorization");
if (token == null || token.isEmpty()) {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
httpResponse.getWriter().write("Missing token");
return;
}
// 验证通过,继续放行
chain.doFilter(request, response);
}
}
注意这里的chain.doFilter()方法,它表示当前Filter处理完毕后,将请求传递给下一个Filter或者最终的Servlet。如果你不调用这个方法,请求就会在这里终止,后面的所有组件都不会执行。
二、拦截器(Interceptor)的本质与执行位置拦截器是Spring MVC框架层面的组件,它工作在DispatcherServlet内部。只有当请求通过了Filter链,到达DispatcherServlet之后,才会进入拦截器的执行流程。拦截器可以在Controller方法执行之前(preHandle)和之后(postHandle、afterCompletion)进行逻辑处理。
拦截器的典型应用场景包括:权限校验、日志记录、性能监控、事务管理等。它能够获取到HandlerMethod信息,也就是具体是哪个Controller的哪个方法在处理请求,这是Filter做不到的。Filter只能拿到原始的HttpServletRequest,而Interceptor可以拿到更高层的Spring MVC上下文信息。
下面是一个典型的拦截器实现:
@Component
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
// 在Controller方法执行之前
String token = request.getHeader("Authorization");
if (token == null) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("No token");
return false;
}
return true;
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) throws Exception {
// 在Controller方法执行之后,视图渲染之前
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
// 整个请求完成之后,视图渲染之后
}
}
注册拦截器需要实现WebMvcConfigurer接口:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private AuthInterceptor authInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authInterceptor)
.addPathPatterns("/")
.excludePathPatterns("/login", "/register");
}
}
三、完整的执行顺序流程图解
理解执行顺序最好的方式就是看完整的请求生命周期。下面我用文字把整个流程梳理一遍,你可以对照着画一个流程图:
第一步:客户端发送HTTP请求,到达Servlet容器(如Tomcat)。第二步:请求进入Filter链,按照@Order或者web.xml中配置的顺序依次执行各个Filter。第三步:所有Filter执行完毕后,请求到达DispatcherServlet。第四步:DispatcherServlet根据请求URL找到对应的Handler(Controller)。第五步:执行Interceptor的preHandle方法,如果返回false则直接中断,不再执行后续流程。第六步:执行Controller中的具体业务方法。第七步:执行Interceptor的postHandle方法。第八步:进行视图渲染(如果有的话)。第九步:执行Interceptor的afterCompletion方法。第十步:响应经过Filter链的后半段(如果Filter在doFilter之后还有处理逻辑)。第十一步:响应返回客户端。
用一个简化的代码逻辑来表示就是:
// Filter 1
doFilter() {
// 前置处理
chain.doFilter(); // 传递给下一个Filter或DispatcherServlet
// 后置处理(响应阶段)
}
// Filter 2
doFilter() {
// 前置处理
chain.doFilter();
// 后置处理
}
// DispatcherServlet
dispatch() {
// 找到Handler
for (Interceptor interceptor : interceptors) {
if (!interceptor.preHandle()) return; // 中断
}
handler.handle(); // 执行Controller
for (Interceptor interceptor : interceptors) {
interceptor.postHandle();
}
// 视图渲染
for (Interceptor interceptor : interceptors) {
interceptor.afterCompletion();
}
}
四、Filter和Interceptor的核心区别对比
从作用范围来看,Filter是Servlet容器级别的,Interceptor是Spring MVC框架级别的。Filter可以拦截几乎所有的请求,包括静态资源、JSP页面等,而Interceptor只能拦截进入DispatcherServlet的请求,也就是通常意义上的Controller请求。
从依赖关系来看,Filter不依赖Spring容器,可以独立运行。Interceptor必须依赖Spring MVC框架,需要在Spring容器中才能工作。这也是为什么在Spring Boot项目中,Filter通常用@Component注解,而Interceptor需要实现HandlerInterceptor接口并注册。
从获取信息的能力来看,Filter只能获取到原始的ServletRequest和ServletResponse。Interceptor可以获取到HandlerMethod、ModelAndView等更丰富的Spring MVC上下文信息。如果你需要知道当前请求具体对应哪个Controller方法,必须用Interceptor。
从执行时机来看,Filter在请求进入框架之前就执行,Interceptor在框架内部执行。这意味着如果你要做全局性的安全防护、编码设置,用Filter更合适;如果你要做与业务逻辑相关的权限控制、参数校验,用Interceptor更合适。
五、实际开发中的常见踩坑场景场景一:重复校验导致性能浪费。有些开发者在Filter和Interceptor里都做了Token校验,导致每个请求都校验两次。正确做法是:在Filter层做粗粒度的校验(比如判断请求头是否存在),在Interceptor层做细粒度的校验(比如验证Token是否过期、权限是否匹配)。
场景二:Filter中修改了Request,Interceptor拿不到。这是因为Filter中如果直接修改了Request的属性或者包装了Request对象,而没有正确传递包装后的Request,后续的Interceptor拿到的还是原始Request。解决方法是使用HttpServletRequestWrapper进行包装,并确保chain.doFilter()传递的是包装后的对象。
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
// 包装请求,添加自定义属性
HttpServletRequest wrappedRequest = new HttpServletRequestWrapper(httpRequest) {
@Override
public String getHeader(String name) {
if ("X-Custom-Header".equals(name)) {
return "added-by-filter";
}
return super.getHeader(name);
}
};
chain.doFilter(wrappedRequest, response); // 传递包装后的请求
}
场景三:多个Interceptor之间的顺序问题。当你注册了多个Interceptor时,它们的执行顺序就是注册顺序。如果你有一个日志Interceptor和一个权限Interceptor,通常日志Interceptor要放在前面,这样即使权限校验失败,日志也能记录下来。通过@Order注解或者addInterceptors中的注册顺序来控制。
场景四:异步请求的执行顺序混乱。当Controller方法返回Callable或者DeferredResult进行异步处理时,Interceptor的afterCompletion会在异步完成之后才执行,而Filter的后置处理可能在异步完成之前就执行了。这种情况下需要特别注意资源清理和日志记录的时机。
六、最佳实践建议第一,明确职责划分。全局性的、与业务无关的处理放在Filter中,比如编码、跨域、压缩。与具体业务相关的、需要知道Handler信息的处理放在Interceptor中,比如权限、日志、参数预处理。
第二,避免重复工作。不要在Filter和Interceptor中做同样的事情,合理分工,各司其职。如果必须在两处都用到某个信息,考虑在Filter中将信息写入Request属性,Interceptor中直接读取。
第三,注意异常处理。Filter中如果抛出异常,请求不会进入DispatcherServlet,Interceptor根本不会执行。Interceptor中如果preHandle返回false,Controller不会执行,但Filter的后置处理仍然会执行。理解这个差异对调试问题非常关键。
第四,善用@Order注解。无论是Filter还是Interceptor,当有多个实例时,执行顺序由@Order的值决定,值越小优先级越高。Spring Boot中默认的Order值可能不符合你的预期,显式指定可以避免很多隐式问题。
第五,性能考虑。Filter链不要太长,每个Filter都会增加请求处理时间。Interceptor同理,不必要的拦截器会拖慢整体响应速度。生产环境中建议通过性能监控工具定期审查Filter和Interceptor的执行耗时。
七、总结过滤器和拦截器的执行顺序差异,本质上是因为它们处于请求处理管道的不同层级。Filter在最外层,负责请求的初步处理和响应的最终加工;Interceptor在框架内部,负责业务层面的精细化控制。掌握这个核心逻辑,你就能在架构设计时做出正确的技术选型,在遇到问题时快速定位根因。记住一句话:Filter管大门,Interceptor管房间,各管各的,别越界。
