天天看点

SpringMVC HandlerInterceptor拦截器解析及源码SpringMVC HandlerInterceptor拦截器源码解析一、实例二、源码 DispatcherServlet.class中寻找原因

SpringMVC HandlerInterceptor拦截器源码解析

文章目录

  • SpringMVC HandlerInterceptor拦截器源码解析
  • 一、实例
    • 1.单个拦截器
        • 单个拦截器的执行顺序
          • return true并且无异常
          • return false 后续内容将会被拦截,不再执行
          • return true 有异常 仍会执行afterCompletion,但不执行postHandle()
    • 2.多个拦截器
      • 多个拦截器执行顺序
  • 二、源码 DispatcherServlet.class中寻找原因
    • 1.找到preHandle()执行的位置
    • 2.找到preHandle()执行的位置
    • 3.afterCompletion相关

一、实例

写一个类继承HandlerInterceptor接口,重写其中的方法

1.单个拦截器

public class MyFirstInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle.....");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle......");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion......");
    }
}
           

注册

<!-- 拦截器  -->
    <mvc:interceptors>
        <bean class="edu.qust.controller.MyFirstInterceptor"></bean>
    </mvc:interceptors>
           

Controller

@Controller
public class InterceptorTestController {
    @RequestMapping("/test01")
    public String test01(){
        System.out.println("test01方法调用");
        return "success";
    }
}
           

执行顺序

SpringMVC HandlerInterceptor拦截器解析及源码SpringMVC HandlerInterceptor拦截器源码解析一、实例二、源码 DispatcherServlet.class中寻找原因

单个拦截器的执行顺序

return true并且无异常

preHandle ⇒ 调用方法 ⇒ postHandle ⇒ 到达页面 ⇒ afterCompletion

return false 后续内容将会被拦截,不再执行
@Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle.....");
        return false;
    }
           
SpringMVC HandlerInterceptor拦截器解析及源码SpringMVC HandlerInterceptor拦截器源码解析一、实例二、源码 DispatcherServlet.class中寻找原因
return true 有异常 仍会执行afterCompletion,但不执行postHandle()
SpringMVC HandlerInterceptor拦截器解析及源码SpringMVC HandlerInterceptor拦截器源码解析一、实例二、源码 DispatcherServlet.class中寻找原因
SpringMVC HandlerInterceptor拦截器解析及源码SpringMVC HandlerInterceptor拦截器源码解析一、实例二、源码 DispatcherServlet.class中寻找原因

2.多个拦截器

执行顺序取决于配置顺序

配置顺序,这里是先配置的2号拦截器,即preHandle…222

<!-- 拦截器  -->
    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/test01"/>
            <bean class="edu.qust.controller.MySecondInterceptor"></bean>
        </mvc:interceptor>
    </mvc:interceptors>

    <mvc:interceptors>
        <bean class="edu.qust.controller.MyFirstInterceptor"></bean>
    </mvc:interceptors>
           

新增的secondInterceptor

public class MySecondInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle.....222");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle......222");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion......222");
    }

}
           

两个preHandle都return true的情况

虽然名字是2号,但因为配置顺序是先2后1,所以会先执行2

SpringMVC HandlerInterceptor拦截器解析及源码SpringMVC HandlerInterceptor拦截器源码解析一、实例二、源码 DispatcherServlet.class中寻找原因

多个拦截器执行顺序

(先配置的)preHandle ⇒ (后配置的)preHandle ⇒ 调用方法 ⇒ (后配置的)postHandle ⇒ (先配置的)postHandle ⇒ 到达页面 ⇒ (后配置的)afterCompletion ⇒ (先配置的)afterCompletion

preHandle()==>谁配置在前谁先,post/after ⇒ 的谁配置在后谁先

上面是两个拦截器的handle都return true的情况,现在设置return false的情况

让2号return true ,1号 return false的情况

SpringMVC HandlerInterceptor拦截器解析及源码SpringMVC HandlerInterceptor拦截器源码解析一、实例二、源码 DispatcherServlet.class中寻找原因

发现2号的afterCompletion执行了 ==>放行preHandle的会执行自己的afterCompletion

所以没有放行的1号并没有执行自己的afterCompletion

那么为什么会这样执行,放行了preHandle的拦截器为什么会执行自身的afterCompletion

二、源码 DispatcherServlet.class中寻找原因

1.找到preHandle()执行的位置

找到在DispatcherServlet.class实际干活的doDispatch(),开始debug

SpringMVC HandlerInterceptor拦截器解析及源码SpringMVC HandlerInterceptor拦截器源码解析一、实例二、源码 DispatcherServlet.class中寻找原因

在执行getHandler中发现,拿到的不只是可以用来处理的类handler,还有拦截器集合interceptorList

SpringMVC HandlerInterceptor拦截器解析及源码SpringMVC HandlerInterceptor拦截器源码解析一、实例二、源码 DispatcherServlet.class中寻找原因

发现其中有3个拦截器

还有一个默认为-1的拦截器索引

SpringMVC HandlerInterceptor拦截器解析及源码SpringMVC HandlerInterceptor拦截器源码解析一、实例二、源码 DispatcherServlet.class中寻找原因

1和2是我们自己设计的拦截器

接着向下执行

执行到了下面这句时,控制台打印了

SpringMVC HandlerInterceptor拦截器解析及源码SpringMVC HandlerInterceptor拦截器源码解析一、实例二、源码 DispatcherServlet.class中寻找原因
SpringMVC HandlerInterceptor拦截器解析及源码SpringMVC HandlerInterceptor拦截器源码解析一、实例二、源码 DispatcherServlet.class中寻找原因

看英文能看出来 applyPreHandle 就是 执行preHandle嘛

此时我们找到了拦截器执行preHandle()的位置

这下得进去看看为什么放行了preHandle就会执行他的afterCompletion

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

这时候之前拿到的拦截器集合就出现了,可以发现是做遍历拦截器的操作,如果当前拦截器的preHandle返回的是false则会执行if语句里的内容 ===> 调用triggerAfterCompletion,然后return false直接结束

这里要注意 :源码中的 triggerAfterCompletion ≠ afterCompletion

这里很容易误会成返回false才执行afterCompletion,这不就成了返回false才执行afterCompletion了吗? 然而他们俩并不是一个方法,上面的例子很明显说明了

preHandle返回了true才执行自身的afterCompletion
           

看for循环中,出现了之前的拦截器索引 interceptorIndex ,之前这个数值为 -1 ,现在做自增循环,很明显,这是在记录个数,以本身为下标,一直+1 ,直到出现了第一个return false的拦截器出现。

遍历的顺序就是配置的顺序

SpringMVC HandlerInterceptor拦截器解析及源码SpringMVC HandlerInterceptor拦截器源码解析一、实例二、源码 DispatcherServlet.class中寻找原因

在执行到第3个(下标为2)的MyFirstInterceptor时,因为设置了return false,所以执行了if中的语句

SpringMVC HandlerInterceptor拦截器解析及源码SpringMVC HandlerInterceptor拦截器源码解析一、实例二、源码 DispatcherServlet.class中寻找原因

进入this.triggerAfterCompletion(request, response, (Exception)null);

void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) throws Exception {
        HandlerInterceptor[] interceptors = this.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 var8) {
                    logger.error("HandlerInterceptor.afterCompletion threw exception", var8);
                }
            }
        }

    }
           

发现还是在遍历之前的拦截器集合,但这次看到了真正的afterCompletion

看这次的for循环,i是基于之前interceptorIndex做自减循环,而interceptorIndex则是用来记录有多少return true的拦截器,本身就是数组的下标。

这下就清楚了,这是把interceptorIndex作为下标,把集合中interceptorIndex之前的拦截器的afterCompletion都执行一遍。所以就实现了return true的会执行afterCompletion的效果

所以return true的2号会打印afterCompletion,而return false的1号则没有执行

现在把两个都改成return true再debug,不然下面会被拦截不执行

找完问题的原因后继续向下走,接着执行

SpringMVC HandlerInterceptor拦截器解析及源码SpringMVC HandlerInterceptor拦截器源码解析一、实例二、源码 DispatcherServlet.class中寻找原因

(mv的源码分析补在以后)

2.找到preHandle()执行的位置

继续执行

SpringMVC HandlerInterceptor拦截器解析及源码SpringMVC HandlerInterceptor拦截器源码解析一、实例二、源码 DispatcherServlet.class中寻找原因
SpringMVC HandlerInterceptor拦截器解析及源码SpringMVC HandlerInterceptor拦截器源码解析一、实例二、源码 DispatcherServlet.class中寻找原因

找到了执行postHandle的位置

进去看看如何执行postHandle()

void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {
        HandlerInterceptor[] interceptors = this.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()

回到doDispatch(),继续走,进入有渲染页面方法的processDispatchResult()中

3.afterCompletion相关

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception {
        boolean errorView = false;
        if (exception != null) {
            if (exception instanceof ModelAndViewDefiningException) {
                this.logger.debug("ModelAndViewDefiningException encountered", exception);
                mv = ((ModelAndViewDefiningException)exception).getModelAndView();
            } else {
                Object handler = mappedHandler != null ? mappedHandler.getHandler() : null;
                mv = this.processHandlerException(request, response, handler, exception);
                errorView = mv != null;
            }
        }

        if (mv != null && !mv.wasCleared()) {
            this.render(mv, request, response);
            if (errorView) {
                WebUtils.clearErrorRequestAttributes(request);
            }
        } else if (this.logger.isTraceEnabled()) {
            this.logger.trace("No view rendering, null ModelAndView returned.");
        }

        if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
            if (mappedHandler != null) {
                mappedHandler.triggerAfterCompletion(request, response, (Exception)null);
            }

        }
    }
           

执行到

SpringMVC HandlerInterceptor拦截器解析及源码SpringMVC HandlerInterceptor拦截器源码解析一、实例二、源码 DispatcherServlet.class中寻找原因

进行页面渲染,控制台打印

SpringMVC HandlerInterceptor拦截器解析及源码SpringMVC HandlerInterceptor拦截器源码解析一、实例二、源码 DispatcherServlet.class中寻找原因

若this.render正常执行,接着则执行triggerAfterCompletion去执行afterCompletion

if (mappedHandler != null) {
    mappedHandler.triggerAfterCompletion(request, response, (Exception)null);}
           

若this.render有问题,下面的catch也会捕获,接着执行triggerAfterCompletion,这就是为什么i=1/0这种异常程序依旧会执行afterCompletion的原因

SpringMVC HandlerInterceptor拦截器解析及源码SpringMVC HandlerInterceptor拦截器源码解析一、实例二、源码 DispatcherServlet.class中寻找原因

继续阅读