SpringMVC拦截器介绍与源码剖析

本博文基于:

Spring MVC 5.3.1
JDK1.8



SpringMVC拦截器的作用与应用场景

在处理用户请求之前或者之后,通常需要执行一些行为,如检测用户是否可以登录,或者将请求的信息记录到日志中,时刻的记录当前用户请求的日志信息。Spring MVC提供了拦截器机制,用于请求的预处理和后处理。

假设可能存在这样一个场景,可能本网站注册账号谁都可以注册,但是有些内容需要VIP才能进行访问。这样的需求,就可以通过SpringMVC的拦截器来实现。

通常看到这个需求会想到利用 Java Servlet 的过滤器(Filter)来做,本文中会介绍拦截器和Filter的区别。

拦截器的定义

SpringMVC中的拦截器用于拦截控制器方法的执行,如果要在SpringMVC中定义一个拦截器需要对拦截器进行定义和配置,有两种常用方式:

  1. 实现HandlerInterceptor接口
  2. 继承类HandlerInterceptorAdapter

要实现SpringMVC的拦截器,必须在springmvc.xml文件中进行配置:

<!--springmvc.xml文件中配置拦截器-->
  <mvc:interceptors>
        <!--bean ref方法会拦截所有的请求 一下的两种方式会拦截所有的请求,包括/-->
<!--        <bean class="com.fangshaolei.interceptors.FirstInterceptor"/>-->
<!--        <ref bean="firstInterceptor"/>-->
		<!--配置第一个拦截器-->
        <mvc:interceptor>
            <!--配置拦截的路径 但是 /* 只会拦截一层路径,如果要拦截多层,还需要 /** 来进行配置-->
            <mvc:mapping path="/**"/>
            <!--需要排除不进行拦截的路径-->
            <mvc:exclude-mapping path="/"/>
            <!--注入拦截器-->
            <ref bean="firstInterceptor"/>
        </mvc:interceptor>
        <!--配置第二个拦截器-->
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <mvc:exclude-mapping path="/"/>
            <ref bean="secondInterceptor"/>
        </mvc:interceptor>
    </mvc:interceptors>

当设置多个拦截器时,先按顺序调用preHandle方法,然后逆序调用每个拦截器的postHandle和afterCompletion方法

两个拦截器实现类:

@Component
public class FirstInterceptor implements HandlerInterceptor 
@Component
public class SecondInterceptor implements HandlerInterceptor

拦截器三个方法

  • preHandle

控制器方法执行之前执行preHandle(),其boolean类型的返回值表示是否拦截或放行,返回true为放行,即调用控制器方法;返回false表示拦截,即不调用控制器方法

  • postHandle

控制器方法执行之后执行postHandle()

  • afterCompletion

处理完视图和模型数据,渲染视图完毕之后执行afterComplation()


多个拦截器的执行顺序

若每个拦截器的preHandle()都返回true

此时多个拦截器的执行顺序和拦截器在SpringMVC的配置文件的配置顺序有关:

preHandle()会按照配置的顺序执行,而postHandle()和afterComplation()会按照配置的反序执行

b>若某个拦截器的preHandle()返回了false

preHandle()返回false和它之前的拦截器的preHandle()都会执行,postHandle()都不执行,返回false的拦截器之前的拦截器的afterComplation()会执行

演示:
在这里插入图片描述
意味着执行的顺序和图 1- 1类似:
在这里插入图片描述

图 1-1

在这里插入图片描述

如果是SecondInterceptorpreHandle()拦截器不放行,则执行的结果如下:
在这里插入图片描述


Servlet Filter 和 SpringMVC 拦截器的区别

由于整个Spring MVC是基于一个DispatcherServlet来进行一系列的操作,可以跳出来看,就是一个Servlet和Filter的区别,在JAVAWEB基础阶段得知,Filter的执行一定是在DispatcherServlet之前的。
而对于Interceptor是对每个映射的Controller来处理和放行操作的。下面是简图,主要体现的是两者的作用位置区别。
在这里插入图片描述


源码解析

在观察拦截器的实现和为什么会出现如此的执行流程:主要涉及到两个类:DispatcherServletHandlerExecutionChain这两个类:
我把DispatcherServlet中主要的方法提出来:

doDispatch{
// applyPreHandle 实现的就是preHandler链的调用
	 if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                        return;
      }
      
      // 获取到ModelAndView 对象  方法handle中 有对postHandle方法链进行调用
      mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
      
	// 进行的就是afterCompletion方法调用
	mappedHandler.applyPostHandle(processedRequest, response, mv);
}

HandlerExecutionChain中主要方法提出来:

要注意 private int interceptorIndex;这个成员变量,这是实现拦截器不放行时执行流程的主要。


// =========================执行preHandle=====================================
    boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
        for(int i = 0; i < this.interceptorList.size(); this.interceptorIndex = i++) {
            HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);
            if (!interceptor.preHandle(request, response, this.handler)) {
                this.triggerAfterCompletion(request, response, (Exception)null);
                return false;
            }
        }

        return true;
    }
    
    
// ========================执行PostHandle======================================
    void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {
        for(int i = this.interceptorList.size() - 1; i >= 0; --i) {
            HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);
            interceptor.postHandle(request, response, this.handler, mv);
        }

    }
    
    
// ==========================执行afterCompletion====================================
     void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
        for(int i = this.interceptorIndex; i >= 0; --i) {
            HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);

            try {
                interceptor.afterCompletion(request, response, this.handler, ex);
            } catch (Throwable var7) {
                logger.error("HandlerInterceptor.afterCompletion threw exception", var7);
            }
        }

    }

applyPreHandle方法内部:
在这里插入图片描述
其中interceptor就是从集合中取出各个拦截器的操作,而interceptor所调用的方法,就是自己定义的preHandler方法的执行,如果方法返回false,则会执行if中的方法。

其中this.triggerAfterCompletion(request, response, (Exception)null);执行interceptor中的方法afterCompletion.在执行每一步操作,都会通过interceptorIndex记录下上一次的索引。

比如,以例子为例,applyPreHandle在正常执行之后,interceptorIndex为 3. 返回为true,不会执行DispatcherServletreturn方法,所以会继续执行返回的handle方法。 handle方法调用HandlerExecutionChain中的applyPostHandle.
而方法applyPostHandle执行的是:

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

interceptorList.size倒叙执行,所以,在正常的情况下,postHandle方法逆向输出。

对于afterCompletion为什么不是通过interceptorList.size,而是通过 interceptorIndex来作为起始执行索引呢?

这是由于考虑到preHandle中不放行,而interceptorIndex由上文分析得,记录的是上次访问拦截器的索引,所以在 第一个FirstInterceptor不放行的情况下,会出现如下这种现象。原因是interceptorIndex
在这里插入图片描述

     void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
        for(int i = this.interceptorIndex; i >= 0; --i) {
            HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);

            try {
                interceptor.afterCompletion(request, response, this.handler, ex);
            } catch (Throwable var7) {
                logger.error("HandlerInterceptor.afterCompletion threw exception", var7);
            }
        }

    }

推荐阅读

Servlet Filter 和 SpringMVC 拦截器的区别
SpringMVC拦截器执行流程
SpringMVC拦截器简介
【参考博文】

https://blog.csdn.net/drdongshiye/article/details/88583967
https://www.cnblogs.com/yuxiaole/p/9969360.html
https://blog.csdn.net/weixin_44751434/article/details/119358203?spm=1001.2014.3001.5501

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值