在网站开发框架中,拦截器(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管房间,各管各的,别越界。