本文目录导读:

- 目录导读
- 开篇问答:为什么Java开发者总在“拦截”上纠结?
- 核心概念:两者的“血缘”与“阶层”
- 基于真实Java案例的对比实验(Spring Boot 2.7 + 鉴权场景)
- 关键维度深度分析(核心对比表)
- 实战决策指南:什么业务选谁?
- 总结与最佳实践黄金法则
- 高频面试/架构设计问答
Java实战对决:拦截器(Interceptor)与过滤器(Filter)谁才是数据拦截的王者?
目录导读
- 开篇问答:为什么Java开发者总在“拦截”上纠结?
- 核心概念:过滤器(Filter)与拦截器(Interceptor)的底层逻辑差异
- 基于真实Java案例的对比实验(Spring Boot + 鉴权场景)
- 关键维度深度分析:执行时机、依赖注入、作用域、异常处理
- 实战决策指南:什么业务选谁?附代码级证据
- 总结与最佳实践黄金法则
- 高频面试/架构设计问答
开篇问答:为什么Java开发者总在“拦截”上纠结?
Q1:我用了Spring Boot,为什么还要区分Filter和Interceptor?直接每个请求里写判断不行吗? A:写判断没问题,但那是“过程式”代码,在企业级系统中,横切关注点(如日志、鉴权、防XSS)必须与业务解耦,Filter属于Servlet规范,Interceptor属于Spring MVC组件,两者拦截阶段不同、权限不同、能力不同,选错不仅导致性能浪费,还会出现“拦截到了但拿不到Controller参数”的诡异Bug。
Q2:很多博客说“Filter先执行,Interceptor后执行”,但我的日志顺序总是反的?
A:这触及核心盲区,Filter在Servlet容器层(Tomcat)包裹一切,而Interceptor在Spring的DispatcherServlet内部,如果配置了@ControllerAdvice或异步处理,顺序可能因链路变化,下文案例会还原真实时序。
核心概念:两者的“血缘”与“阶层”
过滤器(Filter)——Servlet的“看门大爷”
- 源自
javax.servlet.Filter,由Servlet容器(Tomcat/Jetty)管理。 - 作用域:覆盖所有能被容器匹配的URL,包括静态资源、JSP、REST接口。
- 特点:拿不到Controller的方法名、参数注解,只能拿到
HttpServletRequest/Response原始对象。 - 生命周期:随容器启动初始化,随容器关闭销毁。
拦截器(Interceptor)——Spring MVC的“交警”
- 源自
org.springframework.web.servlet.HandlerInterceptor,由Spring IoC容器管理。 - 作用域:仅拦截
DispatcherServlet映射到的HandlerMethod,静态资源不经过(除非配置)。 - 特点:可以拿到目标Controller类和方法对象,可访问
HandlerMethod参数注解。 - 典型接口:
preHandle(前置)、postHandle(后置,视图渲染前)、afterCompletion(。
基于真实Java案例的对比实验(Spring Boot 2.7 + 鉴权场景)
案例需求
- 所有
/api/**请求必须校验Token。 - 请求处理完成后记录耗时及用户ID。
- 校验失败返回401 JSON。
实现A:纯Filter实现
@Component
public class AuthFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) {
HttpServletRequest request = (HttpServletRequest) req;
String token = request.getHeader("Authorization");
if (token == null || !token.startsWith("Bearer ")) {
((HttpServletResponse) resp).setStatus(401);
return;
}
// 解析Token存到ThreadLocal(注意线程池传递)
chain.doFilter(req, resp);
}
}
实现B:纯Interceptor实现
@Component
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) {
HandlerMethod method = (HandlerMethod) handler;
// 可获取@RequireAuth等注解,甚至做权限级校验
String token = req.getHeader("Authorization");
if (token == null || !token.startsWith("Bearer ")) {
resp.setStatus(401);
return false;
}
// 将用户ID放入ThreadLocal
return true;
}
@Override
public void afterCompletion(...) {
// 清ThreadLocal防内存泄漏
}
}
实验关键现象
- 当请求打到
/api/user时,Filter先输出filter-start,随后Interceptor输出interceptor-pre,Controller返回后,Interceptor输出post,最后Filter输出filter-end。 - 但若请求路径是
/js/app.js且未配置静态资源放行,Filter依然会拦截并执行Token校验(除非排除),而Interceptor完全不会触发——因为没有对应的HandlerMethod。 - 异常处理差异:Filter中抛异常不会走
@RestControllerAdvice全局异常,而Interceptor的preHandle失败后若抛异常,会进入Spring的异常解析器。
关键维度深度分析(核心对比表)
| 维度 | Filter | Interceptor | 决策优先级 |
|---|---|---|---|
| 执行时机 | 容器级,在DispatchServlet之前 | 处理器映射器找到方法后,执行之前/之后 | 若需在Spring解析参数之前做处理,必须用Filter |
| 依赖注入 | 可以通过@Autowired注入,但生命周期比Spring Bean更早,需注意初始化顺序 |
本身就属于Spring容器,注入自然,推荐优先 | 复杂业务校验推荐Interceptor |
| 作用范围 | 所有URL(包括静态、错误页) | 仅匹配到HandlerMethod的请求 | 静态资源需放行时选Interceptor |
| 获取方法级信息 | 无法获取 | HandlerMethod可获取类/方法/注解/参数名 |
做权限粒度控制时Interceptor完胜 |
| 对请求体/响应体处理 | 可以包装HttpServletRequest(如读取Body后重新放回) |
无法直接包装ServletInputStream(需要过滤器配合) |
防XSS/解密Body场景必须Filter |
| 异常处理 | 需自行try-catch,不会走@ExceptionHandler |
preHandle返回false不会继续,但抛异常会走HandlerExceptionResolver |
全局异常统一用Interceptor更优雅 |
| 性能开销 | 极少(Servlet原生) | 多一层Spring类型转换和handler匹配 | 高并发简单校验选Filter |
实战决策指南:什么业务选谁?
场景1:CORS跨域配置、字符编码、压缩响应
- 必选Filter,因为需要最早介入,且针对容器层面,CORS预检OPTIONS请求不会被Spring MVC拦截。
场景2:统计API耗时、用户登录状态校验、权限注解校验
- 首选Interceptor,因为要访问
HandlerMethod拿到@RequireRole("admin")注解,且能利用Spring的@Async等特性。
场景3:请求体内容解密、防止重复提交(流只能读一次)
- Filter + 包装器,拦截器拿到的是已经解析好的对象,若需要密文流,必须用Filter里的
HttpServletRequestWrapper缓存流。
场景4:多模块异构系统(如纯JSP + REST混用)
- Filter更稳,避免漏掉非Spring管理的资源。
总结与最佳实践黄金法则
- 法则一:优先考虑Interceptor作为业务拦截入口,它符合Spring生态,可测试性强,能利用
HandlerMethod做精细化控制。 - 法则二:当涉及协议层/容器层需求(如CORS、XSS过滤、Gzip解压、静态资源拦截)时,回归Filter。
- 法则三:两者可共存,但多次重复校验会浪费性能,建议Filter只做“粗粒度安检”,Interceptor做“细粒度业务闸机”。
- 法则四:警惕异步请求,对
Callable或DeferredResult,Interceptor的afterCompletion可能提前执行,此时需要配置AsyncHandlerInterceptor。
高频面试/架构设计问答
Q:面试官问“如果Filter和Interceptor都配置了,谁先抛异常?”
A:Filter抛异常直接由容器处理,不经过Spring异常解析;若Interceptor的preHandle抛异常,Spring会尝试匹配@ExceptionHandler或HandlerExceptionResolver,所以Filter异常更原始,Interceptor异常更“Spring化”。
Q:如何做接口幂等性校验?哪个更适合? A:幂等性需要读取请求体或Token,且需要忽略静态资源,推荐Filter + 包装器读取Body并存入Redis,因为Filter能包装原始流,且对于不经过Spring的URL也能生效。
Q:Logback的MDC放入TraceID,放哪一层最好?
A:Interceptor的preHandle最合适,因为能拿到HandlerMethod信息拼接到日志,且可以在afterCompletion中清除,若放Filter,丢失方法名等上下文。
最终结论:没有绝对“更好”,只有“更合适”,如果你的业务逻辑依赖Spring的注解和方法参数,拦截器(Interceptor)是王者;如果涉及协议解析、流重读或静态资源防护,过滤器(Filter)是基石,经验丰富的老手通常同时使用,Filter负责“容器级卫生”,Interceptor负责“业务级执法”,两者协作且不重复劳动,才能构建坚不可摧的数据防线。