我们说的 Filter 和 Interceptor 是啥?
-
Filter:javax.servlet.Filter
-
Interceptor:org.springframework.web.servlet.HandlerInterceptor
参考:
简介
Filter
Java代码
-
public interface Filter {
-
public void init(FilterConfig filterConfig) throws ServletException;
-
-
public void doFilter(ServletRequest request, ServletResponse response,
-
FilterChain chain)
-
throws IOException, ServletException;
-
-
public void destroy();
-
}
根据代码附属的文档:
Filter 用于过滤对资源的请求和响应。一个Filter可以同时用于过滤请求和响应。
目标资源可以是 Servlet 或 静态资源。
Filter 的 doFilter 方法执行具体的过滤操作。
Filter 可以通过 FilterConfig 对象获取初始化参数。还可通过 FilterConfig 提供的 ServletContext 引用获取更多资源信息用于过滤操作。
可通过 web descriptor 文件 web.xml 配置Filter,也可以用 @WebFilter 注解配置。
(标准的默认 web descriptor 文件是 WEB-INF/web.xml)
典型使用场景:
身份验证
日志、审计
图像转换
数据压缩
加解密
访问令牌处理
触发资源访问事件2
XSL 转换(XSL/T)
Mime 类型链式过滤
HandlerInterceptor
Java代码
-
public interface HandlerInterceptor {
-
boolean preHandle(HttpServletRequest request, HttpServletResponse response,
-
Object handler)
-
throws Exception;
-
-
void postHandle(HttpServletRequest request, HttpServletResponse response,
-
Object handler, ModelAndView modelAndView)
-
throws Exception;
-
-
void afterCompletion(HttpServletRequest request, HttpServletResponse response,
-
Object handler, Exception ex)
-
throws Exception;
-
}
根据代码附属文档:
HandlerInterceptor 用于自定义执行链。
HandlerInterceptor 会在相应的 HandlerAdaptor 被调用前执行,所以可用于“预处理”。
例:授权检查、区域设置、主题更改 等。
它的主要目的就是提取出可复用的 Handler 代码。
作为Spring中的概念,HandlerInterceptor 可以像一般的Bean那样配置在 Application Context 中。
HandlerInterceptor 与 Servlet Filter 很相似。
但是 Interceptor 只允许通过预处理禁止 Handler 本身运行,或执行一些后续处理(post-processing)。
Filter 更强大。如,允许交换传递给后续处理链的 request 和 response 对象。
基本准则:
-
细粒度的 Handler相关 预处理操作 可以用 HandlerInterceptor 实现。
尤其是 授权检查 和 公用的 Handler 代码。
-
Filter 适合用于处理 request 和 view 内容。如,Multipart 表单、Gzip压缩。
典型的作法就是处理特定的 Content-Type。
对比
最本质的区别是,Filter 是 Servlet 中的概念,HandlerInterceptor 是 Spring Web 中的概念。
它们是两个不同体系中的概念。因为它们都能实现对 HTTP Request 和 Response 的一些自定义处理,所以会被一些无聊的人强行拿来作比较,甚至成为考题。
其实在一些常见业务常见中,两种技术实现方式并不存在孰优孰劣。只有结合具体业务场景,才能真正比较出哪种方式更合适。
Filter 相关流程
HandlerInterceptor 相关流程
现在的编程模式都是 遇到 新型 业务需求时先搜索一下别人是如何实现的。
我认为这是最普遍、性价比最高、几乎每个人都默认使用、对个人自身成长提升最快的模式。
注意我的用词“新型”。没有人天生就会;自己埋头苦研,不借助巨人力量的,都是傻子;而且肯定不会得到值得推广的成功。编程就是“抄”!
只要清楚它们在 HTTP Request Response 流程中的角色,看到别人给的常规实现方式,就自然会明白 为什么选其中一种而不选另一种。
示例:HandlerInterceptor 用于授权检查
每个项目可能有自己独特的授权检查需求。此示例仅供参考。
很多项目会用Session来存放用户信息(Session可能存储在Redis等中间件中,以实现分布式架构)
RequestContext:存储用户信息的帮助类
此处使用 ThreadLocal 存储的用户信息,不用担心OOM。因为:
-
我们会在后续的HandlerInterceptor中清理用户信息
-
即使清理时遇到异常,线程中的用户信息也不会无限增长。
线程接到下一次用户请求时相关信息又会被设置为新请求的数据
Java代码
-
public abstract class RequestContext {
-
private static final ThreadLocal userIdHolder = new ThreadLocal();
-
// 可根据需要添加各种用户信息字段
-
-
public static String getUserId() { return userIdHolder.get(); }
-
public static void setUserId(String id) { userIdHolder.set(id); }
-
-
public static void clear() {
-
userIdHolder.remove();
-
}
-
}
AuthInterceptor:执行授权检查
Java代码
-
public class AuthInterceptor implements HandlerInterceptor {
-
@Override
-
public boolean preHandle(
-
HttpServletRequest request, HttpServletResponse response, Object handler) {
-
// 从 response 获取用户相关标识,并进行授权检查 ...
-
// 通过授权检查,并获得 User ID。如果未通过,可设置response相关内容,并返回 false
-
String userId = ...
-
-
// 保存用户信息,供后续 Controller 业务使用
-
RequestContext.setUserId(userId);
-
}
-
-
@Override
-
public void afterCompletion(
-
HttpServletRequest request, HttpServletResponse response,
-
Object handler, @Nullable Exception ex) throws Exception {
-
// 清除之前保存的用户信息。
-
// 为什么在 afterCompletion() 方法中清理,而不是在 postHandle() 中?
-
// 因为如果业务代码中抛出异常,将跳过 postHandle() 方法,但不会跳过 afterCompletion().
-
// 详见 DispatcherServlet.doDispatch() 方法
-
RequestContext.clear();
-
}
-
}
应用 AuthInterceptor
Java代码
-
@Configuration
-
public class MyWebAppConfigurer implements WebMvcConfigurer {
-
@Override
-
public void addInterceptors(InterceptorRegistry registry) {
-
registry.addInterceptor(new AuthInterceptor())
-
.addPathPatterns("/**");
-
}
-
}
使用Request用户信息
Java代码
-
@RestController
-
public class TestController {
-
@GetMapping("test")
-
public void test() {
-
String userId = RequestContext.getUserId();
-
...
-
}
-
}