导读:本期聚焦于北京SEO公司创作的《SpringBoot自定义过滤器与拦截器怎么用?常见误区与避坑指南》,敬请观看详情。过滤器Filter和拦截器Interceptor都是SpringBoot中常用的请求处理组件,但两者的执行时机、作用范围和实现方式差别很大。过滤器由Servlet容器管理,在请求进入Servlet之前执行,适合做编码设置、跨域处理、登录鉴权等底层操作;拦截器由Spring容器管理,能够访问Bean并获取Handler信息,适合做权限校验、日志记录、接口耗时统计等业务层控制。本文将详细介绍两者的注册方式、执行顺序、源码层面的触发机制,对比各自适用的业务场景,并总结实际开发中容易踩到的坑,比如拦截器中注入Service失效、过滤器中文乱码、静态资源被误拦截等问题,帮助你写出更健壮的Web应用控制逻辑。

在SpringBoot项目里做权限校验、日志记录或者接口防护时,几乎都会遇到过滤器Filter和拦截器Interceptor的选择问题。这两个组件名字听起来很接近,实际工作中也经常有人混用甚至用错,导致出现静态资源被拦截、Bean注入失败、请求参数丢失等莫名其妙的Bug。这篇文章从两者的底层执行机制讲起,配合完整的代码示例,把注册方式、执行顺序、典型误区一次讲清楚。

SpringBoot自定义过滤器与拦截器怎么用?常见误区与避坑指南

一、过滤器与拦截器的本质区别

首先要明确一点:过滤器是Servlet规范中的组件,由Servlet容器(比如Tomcat)管理;而拦截器是Spring MVC框架提供的机制,由Spring的IoC容器管理。这个出身上的差异决定了它们的行为差异。

从执行时机上看,过滤器的触发点在请求进入Servlet之前和响应返回之前,也就是说过滤器包裹在最外层。拦截器则是在DispatcherServlet把请求分发到具体Controller(也就是Handler)前后触发,位置更靠内。一次典型的请求,完整执行顺序是:Filter的前置处理、Interceptor的preHandle、Controller方法执行、Interceptor的postHandle、视图渲染、Interceptor的afterCompletion、Filter的后置处理。

另一个关键区别是:拦截器可以轻松注入Spring Bean,能拿到HandlerMethod信息,知道这次请求最终会调用哪个Controller的哪个方法;过滤器对Spring的感知能力很弱(除非手动处理),它只面向原始的ServletRequest和ServletResponse。简单记:过滤器管的是更底层的通用处理,拦截器管的是更贴近业务逻辑的控制。

二、自定义过滤器的实现与注册

实现过滤器有两种常见方式。第一种是实现javax.servlet.Filter接口(SpringBoot 3.x对应jakarta.servlet.Filter),第二种是继承OncePerRequestFilter。推荐使用后者,因为它保证了每次请求只过滤一次,避免内部转发导致过滤器被重复执行的问题。

@Component
public class AuthFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        String token = request.getHeader("Authorization");
        if (token == null || token.isEmpty()) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write("{\"code\":401,\"msg\":\"token缺失\"}");
            return;
        }
        // 校验通过,放行到下一个过滤器
        filterChain.doFilter(request, response);
    }
}

上面这种通过@Component注册的过滤器会默认拦截所有请求,包括静态资源、/error路径等,这往往是问题的来源。更可控的方式是通过FilterRegistrationBean来注册,可以显式指定拦截路径和执行顺序。

@Configuration
public class FilterConfig {

    @Bean
    public FilterRegistrationBean<AuthFilter> authFilterRegistration(AuthFilter filter) {
        FilterRegistrationBean<AuthFilter> registration = new FilterRegistrationBean<>();
        registration.setFilter(filter);
        registration.addUrlPatterns("/api/*");
        registration.setOrder(1);
        registration.setName("authFilter");
        return registration;
    }
}

这里有个非常常见的坑:如果过滤器类上加了@Component,同时又注册了FilterRegistrationBean,过滤器会被注册两次,鉴权逻辑执行两遍。解决办法是去掉@Component注解,只在配置类中手动注册。另外,过滤器中直接使用@Value或注入Service时,要注意过滤器初始化时机可能早于Spring容器完成依赖注入,普通@Component方式注册一般没问题,但通过web.xml风格或特殊初始化方式就可能拿到null。

三、自定义拦截器的实现与注册

拦截器需要实现HandlerInterceptor接口,或者继承HandlerInterceptorAdapter(新版本已标记废弃,推荐直接实现接口)。接口有三个核心方法:preHandle在Controller执行前调用,返回false则中断请求;postHandle在Controller执行后、视图渲染前调用;afterCompletion在整个请求完成后调用,适合做资源清理。

@Component
public class LogInterceptor implements HandlerInterceptor {

    private static final Logger log = LoggerFactory.getLogger(LogInterceptor.class);

    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws Exception {
        request.setAttribute("startTime", System.currentTimeMillis());
        // handler是HandlerMethod时说明匹配到了具体的Controller方法
        if (handler instanceof HandlerMethod) {
            HandlerMethod method = (HandlerMethod) handler;
            log.info("请求接口: {}#{}",
                    method.getBeanType().getSimpleName(),
                    method.getMethod().getName());
        }
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request,
                                HttpServletResponse response,
                                Object handler, Exception ex) {
        long cost = System.currentTimeMillis() - (Long) request.getAttribute("startTime");
        log.info("接口耗时: {}ms, 状态码: {}", cost, response.getStatus());
    }
}

光定义拦截器是不生效的,必须通过WebMvcConfigurer注册到Spring MVC中。注意要用addInterceptors方法而不是实现老版本的WebMvcConfigurationSupport,后者会导致SpringBoot默认的静态资源映射、消息转换器等自动配置全部失效,这是新人特别容易踩的坑。

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private LogInterceptor logInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(logInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns("/login", "/static/**", "/error");
    }
}

excludePathPatterns非常实用,可以把登录接口、静态资源、健康检查等路径排除在外。但要提醒一点:排除路径的匹配规则遵循Ant风格,写/static/**和写/static效果完全不同,后者只会排除精确匹配的这一个路径。

四、常见误区与避坑指南

误区一:拦截器里抛异常却没有统一异常兜底。preHandle中抛出的异常会被全局异常处理器@ControllerAdvice捕获吗?答案是能,因为拦截器异常发生在DispatcherServlet内部处理流程中,会走HandlerExceptionResolver链。但过滤器中的异常发生在进入DispatcherServlet之前,全局异常处理器管不到,只能自己在过滤器里try-catch并写回响应,这也是为什么统一鉴权逻辑有时要放在过滤器层。

误区二:postHandle拿不到正确的响应体。当Controller方法返回值被@ResponseBody标注或使用RestController时,响应体在返回值处理器阶段就已经写入响应流,等执行到postHandle时再去操作response的输出流会抛出异常或无效。如果需要对响应内容做加密、修改,应该使用ResponseBodyAdvice或者包装Response的装饰器模式,而不是拦截器。

误区三:异步请求下的重复执行。对于返回CallableDeferredResult的异步接口,preHandleafterCompletion可能在不同线程执行,postHandle则不会调用。过滤器如果使用OncePerRequestFilter可以天然规避重复执行问题,这也是前面推荐它的原因之一。

误区四:乱码问题。如果自定义过滤器在字符编码过滤器之前执行,并且直接读取了请求体或写回了响应,就可能出现中文乱码。解决办法是通过setOrder把字符编码过滤器的顺序调到最前(SpringBoot中默认注册的CharacterEncodingFilter的order是Ordered.HIGHEST_PRECEDENCE),或者确保自己的过滤器不提前消费输入流。

五、如何选择:一张表说清楚

对比维度过滤器Filter拦截器Interceptor
管理容器Servlet容器Spring IoC容器
执行时机请求进入Servlet前后Handler方法执行前后
能否获取Handler信息不能可以拿到HandlerMethod
典型场景编码、跨域、XSS过滤、登录token校验权限校验、日志、耗时统计、接口幂等
异常处理不经过全局异常处理器可被全局异常处理器捕获

实际项目中两者的划分可以这样把握:与业务无关、所有请求(包括非Controller请求)都需要生效的通用处理放过滤器,比如跨域、编码、安全防护;依赖Spring上下文、需要知道具体接口信息的业务控制放拦截器,比如基于注解的权限校验、操作日志。两者配合使用,各司其职,才能构建出层次清晰的Web应用控制体系。理解了执行顺序和作用边界,前面提到的那些坑基本都能提前规避。

SpringBoot过滤器拦截器Filter与Interceptor区别修改时间:2026-09-08 14:59:16

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/20260908/52830.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。