过滤器 Filter 和拦截器 Interceptor 的异同
在Web应用程序开发中,Filter和Interceptor是常用的拦截器机制,可以用于对请求和响应进行拦截和处理。它们都可以在请求到达目标资源之前或者离开目标资源之后,对请求和响应进行处理。
通常情况下,Filter是一个用于在请求到达Servlet或Controller之前拦截和处理请求和响应的组件。它可以修改请求或响应、执行身份验证或授权检查,或执行其他预处理或后处理任务。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有如下几个常见用法:
- 用户授权的
Filter:Filter负责检查用户请求,根据请求过滤用户非法请求。 - 日志
Filter:详细记录某些特殊的用户请求。 - 负责解码的
Filter:包括对非标准编码的请求解码。 - 能改变XML内容的
XSLT Filter等。 Filter可以负责拦截多个请求或响应;一个请求或响应也可以被多个Filter拦截。
Filter的配置
过滤器的配置比较简单,直接实现Filter 接口即可,也可以通过@WebFilter注解实现对特定URL拦截,看到Filter 接口中定义了三个方法。
init():该方法在容器启动初始化过滤器时被调用,它在Filter的整个生命周期只会被调用一次。注意:这个方法必须执行成功,否则过滤器会不起作用。doFilter():容器中的每一次请求都会调用该方法,FilterChain用来调用下一个过滤器Filter。destroy(): 当容器销毁 过滤器实例时调用该方法,一般在方法中销毁或关闭资源,在过滤器Filter的整个生命周期也只会被调用一次
1 | |
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 | |
将自定义好的拦截器处理类进行注册,并通过addPathPatterns、excludePathPatterns等属性设置需要拦截或需要排除的 URL
1 | |
Filter与Interceptor的异同
Filter和Interceptor的相同点在于它们都是在Web应用程序中用于拦截和处理请求和响应的组件。它们都可以用于实现横切关注点的处理,如身份验证、日志记录、性能监控等
虽然Filter和Interceptor在实现和应用上有一些差异,例如Filter是在Servlet容器层面上实现的,而Interceptor是在应用程序框架层面上实现的,但它们的目的都是为了对请求和响应进行拦截和处理,以实现某些特定的业务需求
实现原理不同
Filter是基于函数回调实现的:
每个自定义过滤器都会实现一个doFilter()方法,这个方法有一个关键参数FilterChain。它是一个回调接口,ApplicationFilterChain 是它的具体实现类,该类内部也有一个doFilter()方法,这个方法就是回调方法
1 | |
ApplicationFilterChain 里面能拿到我们自定义的xxxFilter类,在其内部回调方法doFilter()里调用各个自定义xxxFilter过滤器,并执行 doFilter() 方法
1 | |
而每个xxxFilter 会先执行自身的 doFilter() 过滤逻辑,最后在执行结束前会执行filterChain.doFilter(servletRequest, servletResponse),也就是回调ApplicationFilterChain的doFilter() 方法,以此循环执行实现函数回调
1 | |
Interceptor是基于反射实现的
自定义过滤器可以直接继承 HandlerInterceptor 这个接口 (HandlerInterceptorAdapter这个抽象类已经在springmvc 5.3被废弃),实现其中任意三个方法 preHandle(), postHandle() 和 afterCompletion(),再使用 addInterceptors() 方法将 interceptor bean 注册到 registry 的 List<interceptor>中
1 | |
在 WebMvcConfigurationSupport 中创建 RequestMappingHandlerMapping 时将自定义的 interceptor 加入到拦截器列表中
1 | |
拦截器的使用是在 DispatcherServlet 中的 doDispatch()
1 | |
所以这里是先调用PreHandle,如果返回了false,则不会执行PostHandle,而afterCompletion的调用这是在catch里和 processDispatchResult 里,先看看 PreHandle 里面的东西:
1 | |
在有任何一个 preHandle 返回 false 的时候就会触发 triggerAfterCompletion 代码,triggerAfterCompletion 里的代码的for循环 是从 interceptorIndex 开始递减的
1 | |
所以执行拦截器的 preHandle 方法的顺序是先来顺序执行所有拦截器的 preHandle 方法
- 如果当前拦截器
prehandler返回为true,则执行下一个拦截器的preHandle方法 - 如果当前拦截器返回为
false,直接倒序执行所有已经执行了的拦截器的afterCompletion方法。
如果任何一个拦截器返回false。直接跳出不执行目标方法。
执行拦截器的 postHandle 方法,是倒叙执行
1 | |
页面成功渲染完成后执行 processDispatchResult(),其中还会倒叙触发 triggerAfterCompletion()
1 | |
总结
- 根据当前请求,找到
HandlerExecutionChain(可以处理请求的handler以及handler的所有拦截器) - 先来顺序执行所有拦截器的
preHandle方法- 如果当前拦截器
prehandle返回为true,则执行下一个拦截器的preHandle - 如果当前拦截器返回为
false,直接倒序执行所有已经执行了的拦截器的afterCompletion
- 如果当前拦截器
- 如果任何一个拦截器返回
false,直接跳出不执行目标方法 - 所有拦截器都返回
true,则继续执行目标方法 - 目标方法执行完成之后,倒序执行所有拦截器的
postHandle方法 - 前面的步骤有任何异常都会直接倒序触发
afterCompletion - 页面成功渲染完成以后,也会倒序触发
afterCompletion
使用范围不同
过滤器(Filter) 实现的是 javax.servlet.Filter 接口,而这个接口是在Servlet规范中定义的,也就是说过滤器Filter 的使用要依赖于Tomcat等容器,导致它只能在web程序中使用
而拦截器(Interceptor) 它是一个Spring组件,并由Spring容器管理,并不依赖Tomcat等容器,是可以单独使用的。不仅能应用在web程序中,也可以用于Application、Swing等程序中
触发时机不同

过滤器Filter是在请求进入容器后,但在进入servlet之前进行预处理,请求结束是在servlet处理完以后;拦截器 Interceptor 是在请求进入servlet后,在进入Controller之前进行预处理的,Controller 中渲染了对应的视图之后请求结束
拦截范围不同
过滤器 Filter 几乎可以拦截所有进入容器的请求
拦截器 Interceptor 只会对 Controller 请求或访问static目录下的静态资源请求起作用
初始化时机不同
过滤器 Filter 是随着 Tomcat 等 web 容器启动时而进行初始化
拦截器 Interceptor 时随着 spring 启动而进行初始化
在实际的业务场景中,当使用到过滤器或拦截器时,难免会引入一些依赖的service服务。下面就通过例子进行简要说明:
Filter 中注入service
1 | |
Interceptor 中注入service
直接采用注解 @Autowired,但是在将拦截器注入到 spring 容器中时,不能自己通过 new 来进行创建。需要将拦截器当做一个普通的 bean 注入到spring容器中
这是因为加载顺序导致的问题,interceptor 加载的时间点在 springcontext 之前,而 Bean 又是由 spring 进行管理,所以 interceptor 中的 service 还是 Null
解决方法在注册拦截器之前,先将Interceptor 手动进行注入:
1 | |
控制执行顺序不同
实际开发过程中,会出现多个过滤器或拦截器同时存在的情况,不过,有时我们希望某个过滤器或拦截器能优先执行,就涉及到它们的执行顺序
Filter 用@Order注解控制执行顺序,通过@Order控制过滤器的级别,值越小级别越高越先执行
1 | |
Interceptor 默认的执行顺序,就是它的注册顺序,也可以通过Order手动设置控制,值越小越先执行
1 | |
看到输出结果发现,先声明的拦截器 preHandle() 方法先执行,而postHandle()方法会后执行
postHandle() 方法被调用的顺序跟 preHandle() 是相反的,这点在实现原理不同中也有解释,postHandle()是根据 preHandle() 的顺序倒叙执行
1 | |