当前位置: 首页 > ops >正文

从源码角度结合详细图例剖析过滤器与拦截器

前言

在工作中,我们经常使用到过滤器与拦截器,但可能对他们的原理以及区别不是特别清楚。

今天的这篇文章,会介绍一下过滤器与拦截器的简单使用,从源码角度窥探两者的执行顺序与实现原理以及从不同角度阐述它们之间的区别。


过滤器使用

@Component
public class MyFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {System.out.println("Filter.init");}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {System.out.println("Filter.doFilter.pre");filterChain.doFilter(servletRequest, servletResponse);System.out.println("Filter.doFilter.post");}@Overridepublic void destroy() {System.out.println("Filter.destroy");}}

自定义一个过滤器,需要实现Filter接口,实现以下3个接口方法

  • init方法,接口中的默认方法。容器初始化过滤器时会被调用,且只会被调用一次。
  • doFilter方法,过滤器的核心方法,必须实现。在该方法内完成对请求的处理,并通过 filterChain.doFilter 调用下一个过滤器。
  • destroy方法,接口中的默认方法。过滤器在销毁前会执行,当然也只会被调用一次。

先简简单单创建一个Controller接口,观察一下调用链路:

    @GetMapping("/test")public String test() {System.out.println("进入Controller");return "SUCCESS";}


 拦截器使用

@Component
public class MyInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("Interceptor.preHandle");return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {System.out.println("Interceptor.postHandle");}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {System.out.println("Interceptor.afterCompletion");}}

实现一个自定义的拦截器,需要实现HandlerInterceptor接口,并实现以下3个方法(都是默认方法)

  • preHandle方法,在到达controller方法之前调用。需要注意的是,一旦preHandle返回false,则代表之后的所有拦截器与Controller都不会再执行。
  • postHandle方法,在controller方法执行之后调用,在DispatcherServlet渲染视图之前调用。
  • afterCompletion方法,在DispatcherServlet渲染视图之后调用。

当然,如果要使用该拦截器,还需要在指定路径上配置该拦截器,使得不同的拦截器作用于不同路径上。

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {@ResourceMyInterceptor myInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(myInterceptor).addPathPatterns("/**");}}

调用链路:


过滤器源码

通过多次调试发现,每一个请求,会进入到org.apache.catalina.core.StandardWrapperValve的invoke方法中。

invoke方法,省略了亿点点无关内容(以下源码部分,为了缩减篇幅,都省略了部分代码):

    public final void invoke(Request request, Response response) throws IOException, ServletException {//为当前请求创建filterChainApplicationFilterChain filterChain =ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);//执行filterChain的doFilter方法if ((servlet != null) && (filterChain != null)) {filterChain.doFilter(request.getRequest(), response.getResponse());} }

invoke方法中,会为每一个请求创建ApplicationFilterChain实例,接着执行该实例的doFilter方法。

进入到createFilterChain:

    public static ApplicationFilterChain createFilterChain(ServletRequest request, Wrapper wrapper, Servlet servlet) {//初始化ApplicationFilterChain实例ApplicationFilterChain filterChain = new ApplicationFilterChain();//设置ServletfilterChain.setServlet(servlet);//获取容器中所有的过滤器//filterMaps中存放的是来自于web.xml中配置的过滤器以及Spring所管理的过滤器StandardContext context = (StandardContext) wrapper.getParent();FilterMap filterMaps[] = context.findFilterMaps();String servletName = wrapper.getName();//按照路径匹配,匹配成功则添加到filterChain中for (int i = 0; i < filterMaps.length; i++) {if (!matchDispatcher(filterMaps[i], dispatcher)) {continue;}if (!matchFiltersURL(filterMaps[i], requestPath))continue;ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)context.findFilterConfig(filterMaps[i].getFilterName());filterChain.addFilter(filterConfig);}//按servlet名称匹配,匹配成功则添加到filterChain中for (int i = 0; i < filterMaps.length; i++) {if (!matchDispatcher(filterMaps[i], dispatcher)) {continue;}if (!matchFiltersServlet(filterMaps[i], servletName))continue;ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)context.findFilterConfig(filterMaps[i].getFilterName());filterChain.addFilter(filterConfig);}return filterChain;}

createFilterChain方法中,为该请求创建了一个ApplicationFilterChain 实例,并为其设置servlet、添加符合匹配规则的过滤器。

接着invoke方法调用ApplicationFilterChain实例的doFilter方法,而doFilter内部仅调用了internalDoFilter方法:

    private void internalDoFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {//pos为filterChain中正在执行的过滤器的下标//n为filterChain中过滤器的个数if (pos < n) {ApplicationFilterConfig filterConfig = filters[pos++];Filter filter = filterConfig.getFilter();//执行过滤器的doFilter方法,并传入当前filterChain实例filter.doFilter(request, response, this);return;}//filterChain中所有的过滤器执行完毕后,执行servlet的service方法servlet.service(request, response);}

internalDoFilter首先会执行第一个过滤器的doFilter方法,传入request、response与当前filterChain实例。

此过滤器在doFilter在结束之前,会执行filterChain.doFilter,于是又回到了internalDoFilter方法中。

internalDoFilter在执行完所有的过滤器后,会执行servlet.service,service方法最终会进入到DispatcherServlet的doDispatch内。

假设当前一共有两个过滤器,则执行流程为:

过滤器其实是责任链模式的一种典型实现,通过滤器对filterChain的回调,来把控制权继续交给filterChain。由filterChain选择是继续执行下一个过滤器,还是执行service方法。

值得注意的是,如果某个过滤器没有执行filterChain.doFilter方法,即没有执行回调,则该请求就不会被其他过滤器处理,也不会进入到Controller中。


拦截器源码

当执行完所有的过滤器后,filterChain会引导进入servlet的service方法中,service最终会调用DispatcherServlet的doDispatch方法。

在doDispatch方法中,同样省略部分无关代码:

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {HttpServletRequest processedRequest = request;HandlerExecutionChain mappedHandler = null;//....ModelAndView mv = null;Exception dispatchException = null;processedRequest = checkMultipart(request);multipartRequestParsed = (processedRequest != request);//1.从处理器映射中获取处理器执行链,包含一个主要的处理器以及拦截器mappedHandler = getHandler(processedRequest);//2.由处理器获得处理器适配器HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());//3.调用拦截器的preHandleif (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}//4.真正处理请求的逻辑,返回ModelAndView对象mv = ha.handle(processedRequest, response, mappedHandler.getHandler());//5.调用拦截器的postHandlemappedHandler.applyPostHandle(processedRequest, response, mv);//6.将逻辑视图转化为物理视图,渲染视图后,调用拦截器的afterCompletionprocessDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);}

在第3步真正处理请求前,会先调用applyPreHandle方法:

	boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {HandlerInterceptor[] interceptors = getInterceptors();if (!ObjectUtils.isEmpty(interceptors)) {for (int i = 0; i < interceptors.length; i++) {HandlerInterceptor interceptor = interceptors[i];if (!interceptor.preHandle(request, response, this.handler)) {triggerAfterCompletion(request, response, null);return false;}this.interceptorIndex = i;}}return true;}

applyPreHandle中的逻辑比较简单,按照顺序挨个调用拦截器的preHandle方法。

值得注意的是,当其中某个拦截器的preHandle返回false,则执行所有拦截器的afterCompletion。接着返回false,使得doDispatch方法直接返回。剩余的拦截器与Controller方法就不会再执行。

第4步为真正处理请求的逻辑,内部会调用Controller方法。

第6步执行applyPostHandle方法:

	void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {HandlerInterceptor[] interceptors = getInterceptors();if (!ObjectUtils.isEmpty(interceptors)) {for (int i = interceptors.length - 1; i >= 0; i--) {HandlerInterceptor interceptor = interceptors[i];interceptor.postHandle(request, response, this.handler, mv);}}}

比较有趣的是,这里会反过来执行所有拦截器的postHandle方法。也就是说,最先调用preHandle的拦截器,将最后调用其postHandle方法。

第6步在渲染视图后,会执行triggerAfterCompletion方法:

	void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) throws Exception {HandlerInterceptor[] interceptors = getInterceptors();if (!ObjectUtils.isEmpty(interceptors)) {for (int i = this.interceptorIndex; i >= 0; i--) {HandlerInterceptor interceptor = interceptors[i];try {interceptor.afterCompletion(request, response, this.handler, ex);}catch (Throwable ex2) {logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);}}}}

和applyPostHandle一样,调用拦截器的afterCompletion方法也是反着来的。

因此,如果对当前路径配置了两个拦截器,则执行的流程为:


 二者区别

从第一节的调用链路来看,如果我需要实现请求鉴权、请求日志记录或请求时间统计,则过滤器与拦截器都可以选择。

既然两者存在功能上的相似之处,那他们的区别到底在哪里呢?

使用的范围或规范不同

过滤器需要实现Filter接口,而该接口被定义javax.servlet包下。也就是说,过滤器是依赖Servlet容器的,因此过滤器只能在Web程序中使用。

拦截器需要实现HandlerInterceptor接口,而该接口被定义在org.springframework.web.servlet包下。也就是说,拦截器是受Spring管理的,能够使用Spring框架的项目,都能使用到拦截器。

使用到的资源不同

如果过滤器仅使用web.xml配置,而没使用@Component注解的话,且不做其他特殊处理,那么过滤器就无法使用Spring容器中各种Bean资源以及Service对象,而拦截器就可以使用。

这是因为过滤器被Tomcat容器所管理,在没有被Spring容器管理的前提下,就无法使用Spring中的资源。

实现前后置处理的原理不同

过滤器实现前置处理与后置处理的能力来自于职责链,通过filterChain.doFilter的摆放位置,将过滤逻辑一切为二。前面的代码为前置处理,后面的代码则为后置处理。先执行前置处理的过滤器,其后置处理将会最晚处理,和递归调用很像。

拦截器实现前置后置处理的能力则来自于在for循环中手动改变调用顺序,先按正序依次调用preHandle,再反序调用postHandle。

触发时机不同

过滤器在请求进入Servlet前进行前置处理,在请求离开Servlet后进行后置处理。

Controller层出现异常后,过滤器的后置处理将不会再执行。

拦截器在请求进入Servlet后,且在进入Controller进行前置处理,在离开Controller后进行后置处理。

不论Controller层是否出现异常,拦截器的afterCompletion一定会被执行,所以拦截器的处理能力相比过滤器更加全面与立体。

如果不考虑源码调用细节,触发时机如图所示:

如果有多个过滤器与拦截器,并且考虑源码调用细节:

如果结合SpringMVC的核心流程doDispatch来看:

http://www.xdnf.cn/news/11190.html

相关文章:

  • 百度分享代码
  • Java 串口通信(RS232/485)
  • 批处理简单教程
  • Java基础入门知识
  • 3D建模的4种方式,你都知道吗?
  • GGhost一键恢复09.03.08
  • 【web前端期末大作业】学生个人网页设计作品 学生个人网页设计作品 学生个人网页模板 简单个人主页成品
  • HTML中的标签textarea的属性及用法
  • 2008春晚,赵本山之《火炬手》(现场版最新完整台词)
  • 《前沿》(Frontiers)系列英文学术期介绍
  • 极简教程|小白也能快速搭建个人网站
  • VMware虚拟机中的常用文件介绍
  • 分享103个PHP源码,总有一款适合您
  • 获取ACCESS2000密码 [C#]
  • 图形学中的不变矩方法及其matlab实现
  • 推荐一款基于Java的音视频处理开源项目--JAVE
  • 转贴:网友【原创·教程】 SRT外挂字幕时间轴调整及合并中英文同步字幕制作方法
  • C++ QT结合FFmpeg实战开发视频播放器-08播放器项目的整体UI架构
  • 当你在浏览器输入www.xxx.com的时候会发生什么?
  • linux 防火墙firewall详解
  • MSDE2000安装
  • 信息论:熵与互信息
  • 分享一个简洁、优雅且高效的 Hugo 博客主题 - FixIt
  • CSS基础学习--9 边框(Border)
  • Oracle数据库入门教程(作者原创)
  • Linux内核(十)WIFI BT电路解析 对应设备树配置解析
  • Ubuntu安装eclipse
  • 补丁(patch)的制作与应用
  • 什么是原码、反码和补码
  • reviewboard环境搭建(3):创建站点