过滤器与拦截器
Spring Boot 中有三种拦截 HTTP 请求的机制,它们处于请求处理的不同层级:
客户端请求
│
▼
┌─────────────────────────────────────┐
│ Servlet Filter(过滤器) │ ← Servlet 规范,最外层
│ ┌───────────────────────────────┐ │
│ │ DispatcherServlet │ │
│ │ ┌─────────────────────────┐ │ │
│ │ │ HandlerInterceptor │ │ │ ← Spring MVC 层
│ │ │ ┌───────────────────┐ │ │ │
│ │ │ │ Controller / AOP │ │ │ │ ← 方法级别
│ │ │ └───────────────────┘ │ │ │
│ │ └─────────────────────────┘ │ │
│ └───────────────────────────────┘ │
└─────────────────────────────────────┘
三者对比
| 对比项 | Filter(过滤器) | HandlerInterceptor(拦截器) | AOP |
|---|---|---|---|
| 规范来源 | Servlet 规范 | Spring MVC | Spring AOP |
| 作用范围 | 所有请求(含静态资源) | 仅经过 DispatcherServlet 的请求 | Spring Bean 方法 |
| 能获取的信息 | HttpServletRequest/Response | HttpServletRequest/Response + HandlerMethod | 方法参数、返回值、注解 |
| 能否访问 Spring Bean | 需通过 WebApplicationContextUtils | 直接注入 | 直接注入 |
| 执行时机 | 最早 | Controller 前后 | 方法执行前后 |
| 典型用途 | 字符编码、CORS、安全认证 | 登录校验、日志、权限 | 日志、缓存、事务 |
更细粒度的方法级增强参见 AOP。
Filter(过滤器)
实现方式
实现 OncePerRequestFilter 保证每次请求只执行一次(推荐):
@Component
public class RequestLogFilter extends OncePerRequestFilter {
private static final Logger log = LoggerFactory.getLogger(RequestLogFilter.class);
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
long start = System.currentTimeMillis();
String uri = request.getRequestURI();
String method = request.getMethod();
try {
chain.doFilter(request, response); // 继续执行后续过滤器和 Servlet
} finally {
long cost = System.currentTimeMillis() - start;
log.info("{} {} - {}ms - status:{}", method, uri, cost, response.getStatus());
}
}
// 排除不需要过滤的路径
@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
return request.getRequestURI().startsWith("/actuator");
}
}注册与排序
通过 FilterRegistrationBean 精确控制过滤器的 URL 范围和执行顺序:
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean<RequestLogFilter> requestLogFilter() {
FilterRegistrationBean<RequestLogFilter> bean = new FilterRegistrationBean<>();
bean.setFilter(new RequestLogFilter());
bean.addUrlPatterns("/api/*"); // 只过滤 /api/** 请求
bean.setOrder(1); // 数字越小,优先级越高
bean.setName("requestLogFilter");
return bean;
}
@Bean
public FilterRegistrationBean<AuthFilter> authFilter() {
FilterRegistrationBean<AuthFilter> bean = new FilterRegistrationBean<>();
bean.setFilter(new AuthFilter());
bean.addUrlPatterns("/api/*");
bean.setOrder(2); // 在日志过滤器之后执行
return bean;
}
}若 Filter 已通过
@Component注册,@Bean FilterRegistrationBean和@Component只能选其一,否则过滤器会执行两次。
读取请求体(Body)
HttpServletRequest 的 InputStream 只能读一次,重复读取需包装:
public class CachingRequestBodyFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
ContentCachingRequestWrapper wrappedRequest =
new ContentCachingRequestWrapper(request);
chain.doFilter(wrappedRequest, response);
// 此时才能安全读取 body(chain 执行后 body 已被缓存)
byte[] body = wrappedRequest.getContentAsByteArray();
log.debug("Request body: {}", new String(body, StandardCharsets.UTF_8));
}
}HandlerInterceptor(拦截器)
三个回调方法
@Component
public class AuthInterceptor implements HandlerInterceptor {
@Autowired
private TokenService tokenService;
/**
* Controller 执行前调用。
* 返回 false 则中断请求,后续拦截器和 Controller 不再执行。
*/
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
// 非 Controller 方法(如静态资源)直接放行
if (!(handler instanceof HandlerMethod)) {
return true;
}
HandlerMethod method = (HandlerMethod) handler;
// 检查是否有 @SkipAuth 注解
if (method.hasMethodAnnotation(SkipAuth.class)) {
return true;
}
String token = request.getHeader("Authorization");
if (!tokenService.validate(token)) {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.getWriter().write("{\"message\":\"未授权\"}");
return false;
}
return true;
}
/**
* Controller 执行后、视图渲染前调用。
* 发生异常时不会调用此方法。
*/
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) {
// 可在此修改 ModelAndView,常用于添加公共数据
}
/**
* 请求完成后(视图渲染后)调用,无论是否发生异常。
* 适合清理 ThreadLocal 等资源。
*/
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
UserContext.clear(); // 清理线程变量,防止内存泄漏
}
}注册拦截器
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private AuthInterceptor authInterceptor;
@Autowired
private RateLimitInterceptor rateLimitInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authInterceptor)
.addPathPatterns("/api/**")
.excludePathPatterns(
"/api/auth/login",
"/api/auth/register",
"/api/public/**"
)
.order(1);
registry.addInterceptor(rateLimitInterceptor)
.addPathPatterns("/api/**")
.order(2);
}
}多拦截器执行顺序
假设有拦截器 A(order=1)和 B(order=2):
请求进入:
A.preHandle → B.preHandle → Controller
↓
A.afterCompletion ← B.afterCompletion ← A.postHandle ← B.postHandle
若 B.preHandle 返回 false:
A.preHandle → B.preHandle(false) → A.afterCompletion
(B 和 Controller 的后续不执行,已执行 preHandle 的拦截器 afterCompletion 仍会执行)
常见应用场景
登录状态校验(拦截器)
@Override
public boolean preHandle(HttpServletRequest request, ...) {
String token = request.getHeader("Authorization");
Claims claims = jwtUtil.parse(token);
if (claims == null) {
response.setStatus(401);
return false;
}
// 将用户信息存入 ThreadLocal,Controller 中直接获取
UserContext.set(new CurrentUser(claims.getSubject()));
return true;
}Token 相关实现参见 OAuth2与JWT,Session 管理参见 Session管理。
CORS 跨域(过滤器)
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CorsFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS");
response.setHeader("Access-Control-Allow-Headers", "Authorization,Content-Type");
if ("OPTIONS".equals(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
return;
}
chain.doFilter(request, response);
}
}Spring MVC 内置的跨域配置参见 跨域处理。
接口耗时统计(拦截器)
public class TimingInterceptor implements HandlerInterceptor {
private static final String START_TIME = "startTime";
@Override
public boolean preHandle(HttpServletRequest request, ...) {
request.setAttribute(START_TIME, System.currentTimeMillis());
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, ...) {
long cost = System.currentTimeMillis() - (Long) request.getAttribute(START_TIME);
if (cost > 1000) {
log.warn("慢请求: {} {} - {}ms",
request.getMethod(), request.getRequestURI(), cost);
}
}
}异常处理
过滤器中的异常无法被 @ControllerAdvice 捕获(因为在 DispatcherServlet 之外),需自行处理:
@Override
protected void doFilterInternal(...) throws ServletException, IOException {
try {
chain.doFilter(request, response);
} catch (TokenExpiredException e) {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.getWriter().write("{\"code\":401,\"message\":\"Token已过期\"}");
}
}拦截器中的异常会被 DispatcherServlet 转发给 @ControllerAdvice,参见 全局异常处理。