1. 请求处理三剑客:Filter vs Interceptor vs Handler
在SpringBoot应用中,一个HTTP请求从进入系统到最终返回响应,需要经历多个处理环节。其中最核心的三个组件就是过滤器(Filter)、拦截器(Interceptor)和处理器(Handler)。它们各自承担着不同的职责,但又容易让人混淆。我们先通过一个实际案例来感受它们的差异:
假设我们正在开发一个电商平台的订单接口,需要实现以下需求:
- 记录所有请求的入参和出参日志
- 验证用户token有效性
- 对敏感字段进行加解密处理
- 计算接口响应时间
java复制// 过滤器示例:记录请求日志
public class LogFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
long start = System.currentTimeMillis();
// 包装请求对象以重复读取body
ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper((HttpServletRequest) request);
chain.doFilter(requestWrapper, response);
// 记录日志
log.info("RequestURL: {} | Cost: {}ms",
requestWrapper.getRequestURI(),
System.currentTimeMillis() - start);
}
}
1.1 过滤器(Filter)的工作机制
过滤器是Servlet规范定义的标准组件,它的工作时机最早,在请求进入Servlet容器后立即执行。过滤器的核心特点包括:
- 作用范围:所有进入容器的请求,包括静态资源
- 执行顺序:通过@Order注解或web.xml配置决定
- 典型应用场景:
- 请求/响应日志记录
- 字符编码设置
- 跨域处理(CORS)
- XSS防护
- 权限校验的预处理
重要提示:过滤器中无法获取Spring上下文信息,因为它执行时Spring的IOC容器还未介入请求处理流程。
1.2 拦截器(Interceptor)的独特优势
拦截器是SpringMVC框架提供的扩展点,在DispatcherServlet处理后、Controller方法执行前后介入。它的典型特征包括:
- 可以获取HandlerMethod信息(即具体要执行的Controller方法)
- 能够访问Spring上下文中的Bean
- 支持更精细的路径匹配规则
- 可以通过preHandle返回false来直接终止请求
java复制// 拦截器示例:权限校验
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String token = request.getHeader("Authorization");
if(!tokenService.validate(token)) {
response.setStatus(401);
return false;
}
return true;
}
}
1.3 处理器(Handler)的核心职责
处理器是最终执行业务逻辑的组件,通常就是我们编写的Controller方法。它有以下特点:
- 负责具体的业务处理
- 可以方便地使用Spring的各种注解(@RequestBody, @Valid等)
- 能够返回多种类型的响应(视图、JSON、XML等)
- 支持丰富的参数绑定方式
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 三者的执行顺序与协作模式
理解这三个组件的执行顺序对排查问题至关重要。一个完整请求的处理流程如下:
- 过滤器链执行(FilterChain)
- DispatcherServlet确定HandlerMapping
- 拦截器preHandle方法执行
- Controller方法执行
- 拦截器postHandle方法执行
- 视图渲染(如有)
- 拦截器afterCompletion方法执行
- 过滤器链逆序完成

2.1 典型问题:重复处理陷阱
在实际开发中,一个常见错误是在多个环节重复处理相同逻辑。例如:
- 在过滤器和拦截器都做了权限校验
- 在拦截器和AOP都做了日志记录
- 在过滤器和Controller都做了参数解密
这种重复不仅影响性能,还可能导致逻辑冲突。正确的做法应该是:
- 与协议强相关的处理放在过滤器(如HTTPS重定向)
- 与业务弱相关的通用处理放在拦截器(如权限校验)
- 纯业务逻辑放在Controller
2.2 性能监控的最佳实践
假设我们要监控接口耗时,三种组件的实现方式各有优劣:
java复制// 过滤器方案:记录整体耗时(包含静态资源)
public class TimingFilter implements Filter {
public void doFilter(...) {
long start = System.currentTimeMillis();
chain.doFilter(request, response);
long cost = System.currentTimeMillis() - start;
metrics.record(request.getRequestURI(), cost);
}
}
// 拦截器方案:只记录业务接口耗时
public class TimingInterceptor implements HandlerInterceptor {
public boolean preHandle(...) {
request.setAttribute("startTime", System.currentTimeMillis());
return true;
}
public void afterCompletion(...) {
long cost = System.currentTimeMillis() - (long)request.getAttribute("startTime");
metrics.record(((HandlerMethod)handler).getMethod().getName(), cost);
}
}
// AOP方案:最精确的业务方法耗时
@Around("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
Object result = pjp.proceed();
long cost = System.currentTimeMillis() - start;
metrics.record(pjp.getSignature().getName(), cost);
return result;
}
3. 高级应用场景解析
3.1 敏感字段加解密的实现策略
在金融、医疗等对数据安全要求高的场景,我们需要对接口中的敏感字段(如身份证号、银行卡号)进行加解密。常见的实现方案有:
-
过滤器方案:在请求进入时解密,响应返回时加密
- 优点:对业务代码零侵入
- 缺点:无法针对特定字段处理,性能开销大
-
拦截器方案:在preHandle解密,在afterCompletion加密
- 优点:可以结合注解实现字段级控制
- 缺点:仍然需要处理整个请求体
-
自定义ArgumentResolver方案:
java复制@GetMapping("/user") public User getUser(@DecryptParam String idCard) { // idCard已经是解密后的值 }- 优点:最精确的字段级控制
- 缺点:实现复杂度高
推荐使用拦截器+注解的折中方案:
java复制@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface DecryptParam {
String value() default "";
}
public class DecryptInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
MethodParameter[] parameters = handlerMethod.getMethodParameters();
// 解析@DecryptParam注解并处理对应参数
}
return true;
}
}
3.2 防重放攻击的实践方案
防止网络请求被恶意重放是一个常见的安全需求。一个完整的防重放方案通常包含:
- 在过滤器中校验必传参数(timestamp, nonce, sign)
- 在拦截器中验证时间戳有效性(如5分钟内)
- 使用Redis校验nonce唯一性
- 在Controller中处理业务逻辑
java复制// 防重放过滤器示例
public class ReplayAttackFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String timestamp = httpRequest.getHeader("timestamp");
String nonce = httpRequest.getHeader("nonce");
String sign = httpRequest.getHeader("sign");
if (StringUtils.isEmpty(timestamp)
|| StringUtils.isEmpty(nonce)
|| StringUtils.isEmpty(sign)) {
throw new IllegalArgumentException("Missing required headers");
}
chain.doFilter(request, response);
}
}
4. 常见问题排查指南
4.1 过滤器不生效的排查步骤
当发现配置的过滤器没有执行时,可以按照以下步骤排查:
- 检查过滤器是否被Spring管理(有@Component或通过FilterRegistrationBean注册)
- 确认urlPatterns配置是否正确
- 检查是否有更高优先级的过滤器提前终止了请求
- 查看是否被SpringSecurity的过滤器链拦截
- 检查Filter的顺序是否被其他过滤器影响
4.2 拦截器preHandle返回true但请求未到达Controller
这种诡异现象通常有以下几种原因:
- 有其他拦截器的preHandle返回了false
- Controller方法抛出了未被捕获的异常
- 请求被HandlerMethodArgumentResolver拒绝
- SpringMVC的RequestMapping匹配失败
可以通过以下方式调试:
java复制@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new HandlerInterceptor() {
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) {
// 打印完整处理链路
System.out.println("请求处理完成:" + request.getRequestURI() +
",异常:" + ex);
}
});
}
}
4.3 性能优化建议
在高并发场景下,请求处理链路的性能优化尤为重要:
-
过滤器优化:
- 避免在过滤器中做耗时操作(如远程调用)
- 对静态资源使用独立的过滤器链
- 考虑使用OncePerRequestFilter避免重复执行
-
拦截器优化:
- 将轻量级校验放在preHandle前端
- 耗时操作移到postHandle或异步处理
- 使用缓存减少重复计算
-
全局异常处理:
- 统一异常处理能避免在每个环节都写try-catch
- 合理区分业务异常和系统异常
java复制// 性能优化示例:缓存权限校验结果
public class CachingAuthInterceptor implements HandlerInterceptor {
private Cache<String, Boolean> authCache = Caffeine.newBuilder()
.expireAfterWrite(5, TimeUnit.MINUTES)
.build();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String token = request.getHeader("Authorization");
return authCache.get(token, t -> tokenService.validate(t));
}
}
在实际项目中,我通常会建立一个请求处理的checklist来确保各个环节配置正确:
- 静态资源是否被意外拦截
- 跨域配置是否生效
- 异常处理是否覆盖所有场景
- 性能监控数据是否准确
- 安全防护措施是否到位
通过这种系统化的方式,可以大大减少请求处理环节出现的问题。
