1. 拦截器与过滤器的本质区别
在Web开发中,拦截器(Interceptor)和过滤器(Filter)是两种常见的请求处理机制,但它们的定位和功能有本质差异。过滤器是Servlet规范定义的标准组件,工作在Servlet容器层面;而拦截器通常是框架提供的扩展点,工作在应用层面。
1.1 工作层级对比
过滤器处于Web容器的最外层,像一道安检门,所有进出容器的请求都会经过它。以Tomcat为例,当请求到达服务器时,过滤器链(FilterChain)会最先处理请求,然后才进入Servlet。这种机制使得过滤器可以:
- 修改请求和响应的头信息
- 阻止请求进入后续处理流程
- 对响应内容进行二次加工
拦截器则位于框架内部,以Spring MVC为例,它的拦截器工作在DispatcherServlet之后、Controller方法之前。这意味着:
- 拦截器能获取到框架封装的请求对象
- 可以访问HandlerMethod等框架元数据
- 但无法修改HTTP协议层面的内容
1.2 触发时机差异
典型Web请求的处理流程如下(以Spring Boot应用为例):
HTTP请求 -> 过滤器1 -> ... -> 过滤器N -> DispatcherServlet -> 拦截器preHandle -> Controller -> 拦截器postHandle -> 视图渲染 -> 拦截器afterCompletion -> 过滤器处理响应关键时间点说明:
- 过滤器的doFilter()在请求进入Servlet前执行
- 拦截器的preHandle()在HandlerAdapter调用Controller前触发
- postHandle()在Controller返回ModelAndView后执行
- afterCompletion()在整个请求完成后回调
1.3 功能定位不同
过滤器的典型使用场景:
- 跨域处理(CORS)
- XSS防护
- 请求/响应日志记录
- 权限校验(简单场景)
拦截器的典型应用:
- 认证与授权检查
- 参数预处理
- 接口耗时统计
- 业务逻辑校验
经验之谈:过滤器适合处理与业务无关的基础设施需求,拦截器更适合处理业务相关的横切关注点。我曾在一个电商项目中用过滤器处理商品图片的防盗链,而用拦截器实现优惠券的领取权限校验。
2. 注册机制深度解析
2.1 过滤器的注册方式
在Servlet规范中,过滤器有三种注册方式:
2.1.1 注解方式(推荐)
@WebFilter( urlPatterns = "/*", initParams = { @WebInitParam(name = "exclusions", value = "/static/*") } ) public class LogFilter implements Filter { // 实现方法 }特点:
- 需要配合@ServletComponentScan使用
- 无法指定order顺序(按类名排序)
- 适合简单场景
2.1.2 编程式注册
@Bean public FilterRegistrationBean<LogFilter> loggingFilter() { FilterRegistrationBean<LogFilter> registration = new FilterRegistrationBean<>(); registration.setFilter(new LogFilter()); registration.addUrlPatterns("/*"); registration.setOrder(Ordered.HIGHEST_PRECEDENCE); return registration; }优势:
- 可以精确控制执行顺序
- 支持动态配置
- 便于条件化注册
2.1.3 web.xml配置(传统方式)
<filter> <filter-name>logFilter</filter-name> <filter-class>com.example.LogFilter</filter-class> </filter> <filter-mapping> <filter-name>logFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>2.2 拦截器的注册方式
以Spring MVC为例,拦截器注册需要实现WebMvcConfigurer:
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new AuthInterceptor()) .addPathPatterns("/api/**") .excludePathPatterns("/api/public/**") .order(1); registry.addInterceptor(new LogInterceptor()) .addPathPatterns("/**") .order(2); } }关键配置项:
- addPathPatterns:配置拦截路径(支持Ant风格)
- excludePathPatterns:排除路径
- order:执行顺序(数字越小优先级越高)
2.3 注册机制对比表
| 特性 | 过滤器 | 拦截器 |
|---|---|---|
| 注册入口 | @WebFilter/FilterRegistrationBean | WebMvcConfigurer |
| 顺序控制 | 通过Order属性或web.xml顺序 | 通过order()方法指定 |
| 路径匹配 | urlPatterns | addPathPatterns |
| 排除配置 | 需自行实现 | excludePathPatterns |
| 条件化注册 | 支持 | 支持 |
3. 实战中的典型问题
3.1 执行顺序混乱
常见症状:
- 安全过滤器未生效
- 日志信息不完整
- 跨域处理失败
解决方案:
- 给过滤器设置明确的order值
registration.setOrder(Ordered.HIGHEST_PRECEDENCE + 1);- 拦截器使用order()方法排序
- 避免循环依赖
踩坑案例:曾遇到SecurityFilterChain因order值设置不当导致认证失效,最终通过调试FilterChainProxy定位到执行顺序问题。
3.2 性能瓶颈
错误示范:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) { // 在过滤器中同步调用远程服务 boolean valid = authService.checkToken(((HttpServletRequest)request).getHeader("token")); if(!valid) { return; } chain.doFilter(request, response); }优化方案:
- 改用异步校验
- 引入本地缓存
- 在拦截器中处理(更接近业务层)
3.3 路径匹配陷阱
问题场景:
.addPathPatterns("/api/**") .excludePathPatterns("/api/public/login")实际效果可能不生效,因为:
- Ant路径匹配区分大小写
- /**会匹配所有子路径
- 需要确保排除路径模式完全匹配
建议做法:
.excludePathPatterns("/api/public/login/**")4. 高级应用技巧
4.1 动态拦截器配置
通过实现HandlerInterceptor接口的适配器类:
public class DynamicInterceptor extends HandlerInterceptorAdapter { @Autowired private FeatureToggle featureToggle; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { if(featureToggle.isEnabled("new_auth")) { // 新校验逻辑 } else { // 旧逻辑 } return true; } }4.2 过滤器中的异步处理
利用AsyncContext实现异步操作:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) { HttpServletRequest request = (HttpServletRequest) req; AsyncContext asyncContext = request.startAsync(); CompletableFuture.runAsync(() -> { try { // 执行耗时操作 chain.doFilter(asyncContext.getRequest(), asyncContext.getResponse()); } finally { asyncContext.complete(); } }); }4.3 拦截器与ControllerAdvice协作
通过RequestAttributes传递数据:
// 在拦截器中 RequestContext.getRequestAttributes() .setAttribute("startTime", System.currentTimeMillis(), RequestAttributes.SCOPE_REQUEST); // 在@ControllerAdvice中 Long startTime = (Long)RequestContext.getRequestAttributes() .getAttribute("startTime", RequestAttributes.SCOPE_REQUEST);5. 选型决策指南
5.1 何时选择过滤器
适用场景:
- 需要处理原始HTTP请求/响应
- 要拦截静态资源请求
- 涉及容器级的安全控制
- 需要处理文件上传等底层操作
典型案例:
- 请求体重复读取(通过ContentCachingRequestWrapper)
- 响应压缩(GZIP)
- 全局字符编码设置
5.2 何时选择拦截器
适用场景:
- 需要访问HandlerMethod元数据
- 要基于业务规则进行拦截
- 需要与Spring生态深度集成
- 要求细粒度的路径控制
典型案例:
- 接口权限校验
- 参数签名验证
- API版本控制
- 请求耗时监控
5.3 混合使用建议
推荐架构模式:
HTTP请求 -> [安全过滤器] -> [日志过滤器] -> DispatcherServlet -> [认证拦截器] -> [业务拦截器] -> Controller黄金法则:
- 过滤器处理基础设施需求
- 拦截器处理业务相关逻辑
- 避免功能重叠
- 明确划分责任边界
在实际项目中,我通常会先定义清晰的拦截策略矩阵,明确每个组件的职责范围。例如在一个金融系统中,我们这样划分:
- 过滤器:TLS验证、请求签名检查
- 拦截器:会话超时控制、交易权限校验
- AOP:业务操作日志
这种分层设计使得系统既保证了安全性,又保持了良好的可维护性。