背景
Spring中我们经常会对请求做一些特殊处理,比如对请求体的编码解码,做请求的日志,统一修改响应体等等。其中提到了的功能我们会用到Filter,Interceptor,Advice,但是我们感觉都是对请求做操作,什么时候要用哪个呢?下面会进行介绍。
流程图
实际上Filter和Interceptor都是有前置和后置的方法,将请求一层一层包裹起来,而Advice像是扩展点,可以注意一下下面的流程图。
职责
其实他们的职责需要结合他们的触发时机和返回值去区别,下面时一些他们之间的区别。
- Filter:用于判断所有请求(可以自行匹配路由,或者如一些服务注册请求等),包装和读请求(字节流)和响应(字节流)。
- Interceptor:判断进入Spring MVC的请求是否放行,但不能去读请求体(读了之后无法传递),响应体也无法修改,他的后置方法触发时响应体已经发送出去了。
- Advice:允许拿到请求体(对象),响应体(对象)和异常等,就是用于做统一数据处理的。
实现
Filter
FILter可以通过传参获取请求和响应,虽然请求体直接读会导致ServletRequest 不能被后面读,不过可以用过滤器链继续传递一个新的request。
package com.example.demo.filter;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
public class SimpleLogFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("SimpleLogFilter init");
}
@Override
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
long start = System.currentTimeMillis();
System.out.println("请求进入:" + req.getRequestURI());
try {
chain.doFilter(request, response);
} finally {
System.out.println("请求结束,耗时:" + (System.currentTimeMillis() - start) + "ms");
}
}
@Override
public void destroy() {
System.out.println("SimpleLogFilter destroy");
}
}Interceptor
Interceptor传参很像过滤器,但是他没办法读完请求后继续传递给下一个处理环节,所以他不适合读请求,afterCompletion却执行在返回响应之后,所以他的职责就是看是否放行请求。
package com.example.demo.interceptor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
@Component
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
if (!(handler instanceof HandlerMethod hm)) {
return true;
}
// 方法上的注解
RequireLogin annotation = hm.getMethodAnnotation(RequireLogin.class);
if (annotation == null) {
return true;
}
String token = request.getHeader("Authorization");
if (token == null || token.isEmpty()) {
response.setStatus(401);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"code\":401,\"msg\":\"未登录\"}");
return false;
}
// 模拟解析 token
Long userId = parseUserId(token);
if (userId == null) {
response.setStatus(401);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"code\":401,\"msg\":\"token 无效\"}");
return false;
}
request.setAttribute("userId", userId);
return true;
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) throws Exception {
// 传统 MVC 场景:可以往 modelAndView 里塞公共数据
if (modelAndView != null) {
modelAndView.addObject("appName", "demo");
}
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
Long start = (Long) request.getAttribute("startTime");
if (start != null) {
System.out.println("耗时:" + (System.currentTimeMillis() - start) + "ms");
}
if (ex != null) {
System.out.println("请求异常:" + ex.getMessage());
}
}
private Long parseUserId(String token) {
// 模拟
return 1L;
}
}Advice
这里时处理响应体的Advice,这里它的传参有响应体的对象,比较友好。
package com.example.demo.advice;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
@RestControllerAdvice(basePackages = "com.example.demo.controller")
public class GlobalResponseAdvice implements ResponseBodyAdvice<Object> {
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public boolean supports(MethodParameter returnType,
Class<? extends HttpMessageConverter<?>> converterType) {
// 跳过已经包装过的、或者加了 @SkipWrap 注解的方法
return !returnType.hasMethodAnnotation(SkipWrap.class);
}
@Override
public Object beforeBodyWrite(Object body,
MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request,
ServerHttpResponse response) {
// 已经是 Result,直接返回
if (body instanceof Result) {
return body;
}
Result<Object> result = Result.success(body);
// 如果返回类型是 String,需要手动序列化,否则会 ClassCastException
if (body instanceof String) {
try {
return objectMapper.writeValueAsString(result);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
return result;
}
}三者对比
其实不难发现Filter和Advice已经可以覆盖Interceptor很多的作用,所以基本上在前后端分离的项目很少看见它,不过对于一些独特的需求如:在反序列化之前拦截,基于 HandlerMethod 注解短路等还是需要它的。
| 维度 | Filter | Interceptor | Advice |
|---|---|---|---|
| 所属层 | Servlet 容器 | Spring MVC | Spring AOP |
| 规范来源 | Servlet 规范 | Spring 框架 | Spring AOP |
| 作用范围 | 所有请求(含静态资源) | 匹配 Controller 的请求 | 任意 Spring bean 方法 |
| 能否拿 HandlerMethod | ❌ | ✅ | ❌ |
| 能否拿方法参数值 | ❌ | ❌ | ✅ |
| 能否改返回值 | ❌ | ❌ | ✅ |
| 能否读请求体 | ✅(需 Wrapper) | ❌ | ❌ |
| 能否读响应体 | ✅(需 Wrapper) | ❌ | ❌ |
| 能否按路径匹配 | ✅(urlPatterns) | ✅(addPathPatterns) | ✅(切点表达式) |
| 能否按注解匹配 | ❌ | ⚠️ 手动判断 | ✅(切点表达式) |
| 能否注入 Spring bean | ⚠️ 需特殊处理 | ✅ | ✅ |
| 异常处理能力 | 只能捕获,难细分 | afterCompletion 拿到异常 | @AfterThrowing 精确捕获 |
最后
希望这篇文章能够帮助您!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用。你还可以使用@来通知其他用户。