在 Spring MVC 开发中,开发者常会遇到一个选择题:什么时候用过滤器(Filter),什么时候用拦截器(HandlerInterceptor)? 两者都能拦截请求并插入通用逻辑,但它们的运行机制、所处层次和适用场景有着显著区别。理解这些差异,才能避免滥用或误用。
12.5.1 概念与工作位置
过滤器(Filter)
Filter 是 Servlet 规范 定义的标准组件,工作在 Web 容器(如 Tomcat)级别。它直接拦截进入容器的 原始请求(HttpServletRequest),在所有请求到达 Servlet(DispatcherServlet)之前和之后执行。Filter 无法直接感知 Spring 容器中的 Bean,因为此时请求尚未进入 Spring 的管辖范围。
拦截器(HandlerInterceptor)
HandlerInterceptor 是 Spring MVC 框架 提供的组件,运行在 DispatcherServlet 内部。它拦截的是已经由 DispatcherServlet 分发的 处理器执行链,可以精准地在 Controller 方法执行前后、视图渲染前后插入逻辑。拦截器天然能够访问 Spring 的 IoC 容器,注入任何 Spring 管理的 Bean。
12.5.2 详细对比
| 维度 | 过滤器 (Filter) | 拦截器 (HandlerInterceptor) |
|------|----------------|----------------------------|
| 规范/来源 | Servlet 规范,属于 J2EE 标准 | Spring MVC 框架自有组件 |
| 运行容器 | Servlet 容器(Tomcat 等),脱离 Spring 也可工作 | Spring IoC 容器内部,必须运行在 Spring 环境中 |
| 执行时机 | 在请求进入 Servlet 之前、之后 | 在 DispatcherServlet 接收请求后,HandlerMapping 确定处理器后,Controller 方法执行前后,视图渲染前后 |
| 能访问的对象 | ServletRequest, ServletResponse,无法直接获取 Handler 信息 | HttpServletRequest, HttpServletResponse, HandlerMethod(具体的 Controller 方法),以及经过 @RequestBody 解析的参数等 |
| Spring Bean 注入 | 困难,需通过 WebApplicationContextUtils 间接获取,或通过代理类手动注入 | 直接支持,拦截器本身可通过 @Component 声明为 Bean,使用 @Autowired 注入任何依赖 |
| 细粒度控制 | 只能通过 URL 模式匹配(如 /api/*)决定过滤哪些请求 | 可通过 excludePathPatterns、order 精确控制,甚至可以基于注解或方法信息进行条件判断 |
| 处理范围 | 所有进入应用的请求,包括静态资源 | 默认只拦截进入 Controller 的请求,除非配置包含静态资源 |
| 依赖 Spring 特性 | 无法天然感知 Spring 的上下文和特性(如 AOP 增强) | 可以使用 Spring 的全部能力,包括事务、异常处理、SpEL 表达式等 |
| 执行链顺序 | 多个 Filter 通过 web.xml 或 @Order 控制顺序 | 多个 Interceptor 通过 addInterceptor 顺序及 order 控制 |
| 生命周期 | 与容器生命周期一致,在容器启动时初始化 | 受 Spring 容器管理,默认与 ApplicationContext 生命周期一致 |
12.5.3 执行流程示意
结合一个典型的 Spring Boot 请求流程,可以清晰看到 Filter 和 Interceptor 的时序关系:
客户端请求
↓
[Filter 1] → [Filter 2] → ... → (所有 Filter 链)
↓
DispatcherServlet(前端控制器)
↓
[Interceptor preHandle] (拦截器前置处理)
↓
Controller 方法执行
↓
[Interceptor postHandle] (后置处理)
↓
视图解析 & 渲染
↓
[Interceptor afterCompletion] (完成处理)
↓
离开 DispatcherServlet
↓
回到 Filter 链的后处理逻辑
↓
响应返回客户端
Filter 在任何请求进入 DispatcherServlet 之前就已经生效,因此常见于字符编码设置、跨域处理(CORS)、安全认证(如 Spring Security 的 Filter 链)等场景。Interceptor 则更擅长与 Spring MVC 的处理器紧密协作,如权限注解检查、操作日志记录、全局用户信息注入等。
12.5.4 代码样例对比
Filter 示例
@WebFilter(urlPatterns = "/api/*")
public class LoggingFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpReq = (HttpServletRequest) request;
System.out.println("Filter: 请求进入 -> " + httpReq.getRequestURI());
chain.doFilter(request, response);
System.out.println("Filter: 响应返回 <- " + httpReq.getRequestURI());
}
}
Interceptor 示例
@Component
public class AuthInterceptor implements HandlerInterceptor {
@Autowired
private TokenService tokenService; // 直接注入 Spring Bean
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
// 可以获取到具体方法上的注解
HandlerMethod handlerMethod = (HandlerMethod) handler;
RequireLogin annotation = handlerMethod.getMethodAnnotation(RequireLogin.class);
if (annotation != null) {
String token = request.getHeader("Authorization");
return tokenService.validate(token);
}
}
return true;
}
}
配置拦截器时,可以精确排除静态资源或特定路径:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private AuthInterceptor authInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authInterceptor)
.addPathPatterns("/api/**")
.excludePathPatterns("/api/public/**", "/static/**");
}
}
12.5.5 实战选型建议
根据多年项目经验,以下原则具有普适性:
优先选用 Interceptor 的场景:
- 需要获取当前执行的具体 Controller 方法及其注解,进行权限校验(如
@PreAuthorize的粗粒度替代)。 - 需要在 Controller 方法执行前后注入统一的用户信息、语言区域等。
- 需要记录请求日志,并且日志中要包含方法签名、参数绑定结果等 Spring MVC 特有信息。
- 需要直接使用 Spring 管理的 Bean(如 Redis 缓存服务、用户权限服务)。
- 需要针对某个业务注解做通用处理,例如
@Idempotent(幂等)、@RateLimit(限流)。
必须选用 Filter 的场景:
- 字符编码设置(
CharacterEncodingFilter),若在 Interceptor 阶段处理已太晚。 - 跨域请求处理(CORS)的全局 Header 设置,Spring 4.2 后已有
CorsFilter实现,通常在 Filter 层最干净。 - Spring Security 的核心过滤器链,必须在请求进入 Spring MVC 前完成认证。
- 请求体内容缓存(如
ContentCachingRequestWrapper),因为 Interceptor 阶段可能已经消费了输入流。 - 与第三方 Servlet 组件集成,它们只认识 Filter 接口。
两者协同的典型模式:
一个微服务通常会同时使用 Filter 和 Interceptor。例如:
- 在 Filter 层完成 CORS 配置、请求 ID 生成注入、请求体缓存包装。
- 在 Interceptor 层完成用户认证信息解析、操作日志记录、统一返回格式包装。
这样的分层不仅职责清晰,而且不会因为跨层依赖导致混乱。此外,Filter 天然可以用于保护静态资源,而 Interceptor 默认不拦截静态资源(若启用了 spring.mvc.static-path-pattern 或静态资源映射,通常 Filter 层处理更稳妥)。
关键决策问句:
在犹豫时,可以问自己两个问题:
- 是否需要感知 Spring 容器中的 Bean 或具体的 Handler 信息? 如果需要,选 Interceptor。
- 这个逻辑是否必须在请求进入 DispatcherServlet 之前执行? 如果是(比如编码、安全链、请求体包装),选 Filter。
理解了 Filter 和 Interceptor 的边界,不仅能写出更清晰的代码,也能避免在项目中引入不必要的“万能组件”,使横向逻辑各安其位。