过滤器 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 |
|