过滤器 Filter 和拦截器 Interceptor 的异同

在Web应用程序开发中,Filter和Interceptor是常用的拦截器机制,可以用于对请求和响应进行拦截和处理。它们都可以在请求到达目标资源之前或者离开目标资源之后,对请求和响应进行处理。

通常情况下,Filter是一个用于在请求到达ServletController之前拦截和处理请求和响应的组件。它可以修改请求或响应、执行身份验证或授权检查,或执行其他预处理或后处理任务。Filter可以在web.xml文件中进行配置,也可以在现代框架如Spring中使用注解进行配置;而Interceptor一般用于在框架级别拦截和处理请求和响应。它特定于某些框架,如Spring MVC或Struts。Interceptor用于执行横切关注点,如日志记录、身份验证或事务管理。它们可以在框架的配置文件中进行配置,也可以使用注解进行配置。

Filter介绍

Filter可以被视为Servlet的一种“增强版”,主要用于在处理请求前对其进行预处理,也可以在处理响应后对HttpServletResponse进行后处理,形成一个典型的处理链。虽然Filter也可以生成响应,与Servlet类似,但实际上很少这样使用Filter。使用Filter的完整流程是:Filter对用户请求进行预处理,然后将其转发给Servlet进行处理和生成响应,最后Filter再对服务器响应进行后处理。

Filter有如下几个用处:

  • HttpServletRequest到达Servlet之前拦截客户端的HttpServletRequest
  • 检查HttpServletRequest,并可能修改HttpServletRequest请求头和数据
  • HttpServletResponse到达客户端之前,拦截HttpServletResponse
  • 检查HttpServletResponse,也可以修改HttpServletResponse请求头和数据

Filter有如下几个常见用法:

  • 用户授权的FilterFilter负责检查用户请求,根据请求过滤用户非法请求。
  • 日志Filter:详细记录某些特殊的用户请求。
  • 负责解码的Filter:包括对非标准编码的请求解码。
  • 能改变XML内容的XSLT Filter等。
  • Filter可以负责拦截多个请求或响应;一个请求或响应也可以被多个Filter拦截。

Filter的配置

过滤器的配置比较简单,直接实现Filter 接口即可,也可以通过@WebFilter注解实现对特定URL拦截,看到Filter 接口中定义了三个方法。

  • init() :该方法在容器启动初始化过滤器时被调用,它在 Filter 的整个生命周期只会被调用一次。注意:这个方法必须执行成功,否则过滤器会不起作用。
  • doFilter() :容器中的每一次请求都会调用该方法, FilterChain 用来调用下一个过滤器 Filter
  • destroy(): 当容器销毁 过滤器实例时调用该方法,一般在方法中销毁或关闭资源,在过滤器 Filter 的整个生命周期也只会被调用一次
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Component
public class MyFilter implements Filter {

@Override
public void init(FilterConfig filterConfig) throws ServletException {

System.out.println("Filter 前置");
}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

System.out.println("Filter 处理中");
filterChain.doFilter(servletRequest, servletResponse);
}

@Override
public void destroy() {

System.out.println("Filter 后置");
}
}

Interceptor介绍

拦截器Interceptor是一种在Web应用程序中经常使用的组件,它可以在请求到达Controller之前或之后对请求进行拦截和处理。它是基于AOP的思想,在Spring框架中被广泛应用

与Filter不同,拦截器是在框架级别上实现的,主要用于对请求进行横切关注点cross-cutting concern的处理,如身份验证、权限控制、日志记录、事务管理等。它可以在请求到达Controller之前或之后进行拦截、处理和转发,以实现特定的业务需求

拦截器在Spring框架中有多种使用方式,可以使用注解或 XML 配置进行定义。在 Spring MVC 中,可以通过实现HandlerInterceptor接口来自定义拦截器。拦截器可以在Controller方法执行之前或之后进行拦截,并可以在请求处理之前或之后进行一些额外的处理,例如:

  • 身份验证和授权:Interceptor可以拦截请求并检查用户的身份信息或权限信息,以确保只有授权用户才能访问相关资源
  • 缓存和性能优化:Interceptor可以对请求进行缓存,避免重复的计算或数据查询,提高应用程序的性能
  • 日志记录:Interceptor可以记录请求和响应的日志信息,用于监控和错误排查
  • 异常处理:Interceptor可以拦截异常并进行处理,例如对异常进行统一的日志记录和返回错误信息
  • 参数转换和校验:Interceptor可以对请求参数进行转换和校验,确保请求参数的正确性和合法性

Interceptor的配置

拦截器它是链式调用,一个应用中可以同时存在多个拦截器 Interceptor, 一个请求也可以触发多个拦截器 ,而每个拦截器的调用会依据它的声明顺序依次执行

首先编写一个简单的拦截器处理类,请求的拦截是通过HandlerInterceptor 来实现,看到HandlerInterceptor 接口中也定义了三个方法

  • preHandle() :这个方法将在请求处理之前进行调用。注意:如果该方法的返回值为false ,将视为当前请求结束,不仅自身的拦截器会失效,还会导致其他的拦截器也不再执行
  • postHandle():只有在 preHandle() 方法返回值为true 时才会执行。会在Controller 中的方法调用之后,DispatcherServlet 返回渲染视图之前被调用。 有意思的是postHandle() 方法被调用的顺序跟 preHandle() 是相反的,先声明的拦截器 preHandle() 方法先执行,而postHandle()方法反而会后执行
  • afterCompletion():只有在 preHandle() 方法返回值为true 时才会执行。在整个请求结束之后, DispatcherServlet 渲染了对应的视图之后执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Component
public class MyInterceptor implements HandlerInterceptor {

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

System.out.println("Interceptor 前置");
return true;
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

System.out.println("Interceptor 处理中");
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

System.out.println("Interceptor 后置");
}
}

将自定义好的拦截器处理类进行注册,并通过addPathPatternsexcludePathPatterns等属性设置需要拦截或需要排除的 URL

1
2
3
4
5
6
7
8
9
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**");
}
}

Filter与Interceptor的异同

FilterInterceptor的相同点在于它们都是在Web应用程序中用于拦截和处理请求和响应的组件。它们都可以用于实现横切关注点的处理,如身份验证、日志记录、性能监控等

虽然Filter和Interceptor在实现和应用上有一些差异,例如Filter是在Servlet容器层面上实现的,而Interceptor是在应用程序框架层面上实现的,但它们的目的都是为了对请求和响应进行拦截和处理,以实现某些特定的业务需求

实现原理不同

  • Filter是基于函数回调实现的:

每个自定义过滤器都会实现一个doFilter()方法,这个方法有一个关键参数FilterChain。它是一个回调接口,ApplicationFilterChain 是它的具体实现类,该类内部也有一个doFilter()方法,这个方法就是回调方法

1
2
3
public interface FilterChain {
void doFilter(ServletRequest var1, ServletResponse var2) throws IOException, ServletException;
}

ApplicationFilterChain 里面能拿到我们自定义的xxxFilter类,在其内部回调方法doFilter()里调用各个自定义xxxFilter过滤器,并执行 doFilter() 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public final class ApplicationFilterChain implements FilterChain {
@Override
public void doFilter(ServletRequest request, ServletResponse response) {
...//省略
internalDoFilter(request,response);
}

private void internalDoFilter(ServletRequest request, ServletResponse response){
if (pos < n) {
//获取第pos个filter
ApplicationFilterConfig filterConfig = filters[pos++];
Filter filter = filterConfig.getFilter();
...
filter.doFilter(request, response, this);
}
}
}

而每个xxxFilter 会先执行自身的 doFilter() 过滤逻辑,最后在执行结束前会执行filterChain.doFilter(servletRequest, servletResponse),也就是回调ApplicationFilterChaindoFilter() 方法,以此循环执行实现函数回调

1
2
3
4
5
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

filterChain.doFilter(servletRequest, servletResponse);
}
  • Interceptor 是基于反射实现的

自定义过滤器可以直接继承 HandlerInterceptor 这个接口 (HandlerInterceptorAdapter这个抽象类已经在springmvc 5.3被废弃),实现其中任意三个方法 preHandle()postHandle()afterCompletion(),再使用 addInterceptors() 方法将 interceptor bean 注册到 registryList<interceptor>

1
2
3
4
5
6
7
8
9
10
11
public class InterceptorRegistry {

private final List<InterceptorRegistration> registrations = new ArrayList<>();

public InterceptorRegistration addInterceptor(HandlerInterceptor interceptor) {
InterceptorRegistration registration = new InterceptorRegistration(interceptor);
this.registrations.add(registration);
return registration;
}
...//省略其余代码
}

WebMvcConfigurationSupport 中创建 RequestMappingHandlerMapping 时将自定义的 interceptor 加入到拦截器列表中

1
2
3
4
5
6
7
8
9
10
@Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping(...) {

RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
mapping.setOrder(0);
mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));

...//省略
return mapping;
}

拦截器的使用是在 DispatcherServlet 中的 doDispatch()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
{
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);

// Determine handler for the current request.
// 拿到请求处理器,里面有拦截器的数组HandlerInterceptor[]
// 根据当前请求,找到可以处理请求的handler以及handler的所有拦截器作为HandlerExecutionChain返回
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}

// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

...// 省略

// 目标方法执行之前,会执行该方法
// 遍历所有拦截器,执行拦截器的preHandle方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}

// Actually invoke the handler.
// 执行目标方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

...// 省略

// 倒叙执行所有拦截器的postHandle方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
}

所以这里是先调用PreHandle,如果返回了false,则不会执行PostHandle,而afterCompletion的调用这是在catch里和 processDispatchResult 里,先看看 PreHandle 里面的东西:

1
2
3
4
5
6
7
8
9
10
11
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
for (int i = 0; i < this.interceptorList.size(); i++) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
return true;
}

在有任何一个 preHandle 返回 false 的时候就会触发 triggerAfterCompletion 代码,triggerAfterCompletion 里的代码的for循环 是从 interceptorIndex 开始递减的

1
2
3
4
5
6
7
8
9
10
11
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
for (int i = this.interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
try {
interceptor.afterCompletion(request, response, this.handler, ex);
}
catch (Throwable ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
}
}
}

所以执行拦截器的 preHandle 方法的顺序是先来顺序执行所有拦截器的 preHandle 方法

  • 如果当前拦截器 prehandler 返回为 true ,则执行下一个拦截器的 preHandle 方法
  • 如果当前拦截器返回为 false ,直接倒序执行所有已经执行了的拦截器的 afterCompletion 方法。

如果任何一个拦截器返回false。直接跳出不执行目标方法。

执行拦截器的 postHandle 方法,是倒叙执行

1
2
3
4
5
6
7
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {

for (int i = this.interceptorList.size() - 1; i >= 0; i--) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
interceptor.postHandle(request, response, this.handler, mv);
}
}

页面成功渲染完成后执行 processDispatchResult(),其中还会倒叙触发 triggerAfterCompletion()

1
2
3
4
5
6
7
8
9
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,@Nullable Exception exception) throws Exception {

...// 省略

if (mappedHandler != null) {
// Exception (if any) is already handled..
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
总结
  1. 根据当前请求,找到 HandlerExecutionChain (可以处理请求的handler以及handler的所有拦截器)
  2. 先来顺序执行所有拦截器的 preHandle方法
    1. 如果当前拦截器 prehandle 返回为 true,则执行下一个拦截器的 preHandle
    2. 如果当前拦截器返回为 false,直接倒序执行所有已经执行了的拦截器的 afterCompletion
  3. 如果任何一个拦截器返回 false,直接跳出不执行目标方法
  4. 所有拦截器都返回 true,则继续执行目标方法
  5. 目标方法执行完成之后,倒序执行所有拦截器的 postHandle 方法
  6. 前面的步骤有任何异常都会直接倒序触发 afterCompletion
  7. 页面成功渲染完成以后,也会倒序触发 afterCompletion

使用范围不同

过滤器(Filter) 实现的是 javax.servlet.Filter 接口,而这个接口是在Servlet规范中定义的,也就是说过滤器Filter 的使用要依赖于Tomcat等容器,导致它只能在web程序中使用

而拦截器(Interceptor) 它是一个Spring组件,并由Spring容器管理,并不依赖Tomcat等容器,是可以单独使用的。不仅能应用在web程序中,也可以用于ApplicationSwing等程序中

触发时机不同

filter & interceptor

过滤器Filter是在请求进入容器后,但在进入servlet之前进行预处理,请求结束是在servlet处理完以后;拦截器 Interceptor 是在请求进入servlet后,在进入Controller之前进行预处理的,Controller 中渲染了对应的视图之后请求结束

拦截范围不同

过滤器 Filter 几乎可以拦截所有进入容器的请求

拦截器 Interceptor 只会对 Controller 请求或访问static目录下的静态资源请求起作用

初始化时机不同

过滤器 Filter 是随着 Tomcat 等 web 容器启动时而进行初始化

拦截器 Interceptor 时随着 spring 启动而进行初始化

在实际的业务场景中,当使用到过滤器或拦截器时,难免会引入一些依赖的service服务。下面就通过例子进行简要说明:

Filter 中注入service

1
2
3
4
5
6
7
8
9
10
@Autowired
private TestService testService;

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

System.out.println("Filter 处理中");
testService.a();
filterChain.doFilter(servletRequest, servletResponse);
}

Interceptor 中注入service

直接采用注解 @Autowired,但是在将拦截器注入到 spring 容器中时,不能自己通过 new 来进行创建。需要将拦截器当做一个普通的 bean 注入到spring容器中

这是因为加载顺序导致的问题,interceptor 加载的时间点在 springcontext 之前,而 Bean 又是由 spring 进行管理,所以 interceptor 中的 service 还是 Null

解决方法在注册拦截器之前,先将Interceptor 手动进行注入:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

@Bean
public MyInterceptor getMyInterceptor(){
return new MyInterceptor();
}

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(getMyInterceptor()).addPathPatterns("/**");
}
}

控制执行顺序不同

实际开发过程中,会出现多个过滤器或拦截器同时存在的情况,不过,有时我们希望某个过滤器或拦截器能优先执行,就涉及到它们的执行顺序

Filter@Order注解控制执行顺序,通过@Order控制过滤器的级别,值越小级别越高越先执行

1
2
3
@Order(Ordered.HIGHEST_PRECEDENCE)
@Component
public class MyFilter2 implements Filter {

Interceptor 默认的执行顺序,就是它的注册顺序,也可以通过Order手动设置控制,值越小越先执行

1
2
3
4
5
6
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor2()).addPathPatterns("/**").order(2);
registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**").order(1);
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**").order(3);
}

看到输出结果发现,先声明的拦截器 preHandle() 方法先执行,而postHandle()方法会后执行

postHandle() 方法被调用的顺序跟 preHandle() 是相反的,这点在实现原理不同中也有解释,postHandle()是根据 preHandle() 的顺序倒叙执行

1
2
3
4
5
6
7
8
9
10
Interceptor1 前置
Interceptor2 前置
Interceptor 前置
我是controller
Interceptor 处理中
Interceptor2 处理中
Interceptor1 处理中
Interceptor 后置
Interceptor2 后置
Interceptor1 后置

过滤器 Filter 和拦截器 Interceptor 的异同
https://sugayoiya.github.io/posts/41603.html
作者
Sugayoiya
发布于
2021年7月19日
许可协议