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

一、过滤器与拦截器的本质区别
首先要明确一点:过滤器是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的装饰器模式,而不是拦截器。
误区三:异步请求下的重复执行。对于返回Callable或DeferredResult的异步接口,preHandle和afterCompletion可能在不同线程执行,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