SpringMVC拦截器详解

1、拦截器简介

  • 拦截器:Intercepor拦截器是Handler(Controller)的拦路虎;在用户发起请求时如果请求的Controller下存在拦截器,那么拦截器会对一些请求进行拦截… 总而言之拦截器是围绕Controller做出的一些处理!

  • 拦截器的作用:

    1. 拦截用户的请求,可以预先对请求做出处理;根据结果决定是否执行Controller。

    2. 如果拦截器执行,那么拦截器在Controller执行完成返回给DispatcherServlet前端控制器之前可以将执行返回的结果进行一些修改。

    3. 如果拦截器执行,在请求响应完成之后;此时拦截器依然可以做出一些修尾的工作,例如清理Session…

    4. 根据上述的描述,可以认为拦截器亦可以将Controller共同的功能定义到拦截器中!


  • 拦截器特点:

    1. 拦截器可以分为MVC框架自带的拦截器和程序员自定义拦截器。

    2. 一个Controller可以有0个、若干个拦截器的存在。

    3. 拦截器测试拦截用户的请求Controller


  • 拦截器思想:可以很容易 “猜到” 拦截器的核心思想就是aop;拦截器接口一共就三个方法,三个方法在aop中分别是不同的增强方式实现!


2、拦截器执行流程

前言:

  • 首先明白JSP、Servlet是同一个东西、JSP最后也会转化为Servlet只不过是由Tomocat服务器做的。

  • SpringMVC中有且只有一个Servlet:前端控制器DispatcherServlet;而Controller只是一个Bean对象与Servlet没有半毛钱关系!

在这里插入图片描述

  1. 用户发起请求无论是访问Controller、还是JSP页面、还是Servlet都会经过Filter过滤器,因为访问Controller同样是需要经过DispatcherServlet的!它是MVC架构中的唯一一个Servlet。

  2. 此时Filter过滤器会处理一些东西:典型的设置编码集防止乱码、也可能会过滤一些垃圾请求(不在配置的URL之中)

  3. 现在假设用户请求的是Controller,那么一定会经过DispatcherServlet调度分发;此时这一步是非常重要的:

    • DispatcherServlet先交给HandlerMapping进行解析URI

    • 其次HandlerMapping找到对应的Handler(Controller)之后,将该Handler下对应的MVC拦截器组成一个拦截器执行链,并且打包上Handler形成处理器执行器HandlerExcution,最后重新交给DispatcherServlet

    • DispatcherServlet现在手握处理器执行器HandlerExcution,将其找到适配的处理器适配器HandlerAdapter,由它完成一些解析参数…

    • 处理器适配器解析完成之后,到此拦截器开始工作!

  4. 上面说过所有的拦截器组合成一个执行链,按照拦截器配置的顺序进行执行!其中拦截器中有个方法preHandler中是返回值极为重要:

    • 返回值为true:放行继续执行下一个拦截器,或者是最后一个拦截器通过开始执行controller

    • 返回值为false:拦截器是一个执行链,无论有几个拦截器,只要有一个拦截器返回值为false,将会直接拦截那么controller就无法执行。

  5. 如果preHandler的返回值是true,那么Controller执行完毕之后将会按照相反的顺序执行postHandler方法执行;可以将ModeAndView进行修改!

  6. 最后交给DispatcherServlet进行视图解析,最后完成响应!



3、拦截器的使用

拦截器是SpringMVC提供的一个接口,当需要用到拦截器时需要实现HandlerInterceptor接口、并且在配置文件中声明拦截器;有点Spring声明式事务的感觉!该接口下主要有3个方法,分别是不同的执行顺序。

public interface HandlerInterceptor {

	default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {

		return true;
	}

	default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			@Nullable ModelAndView modelAndView) throws Exception {
	}

	
	default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
			@Nullable Exception ex) throws Exception {
	}
}

3.1、preHandle方法
  • 这是拦截器最核心的方法,该方法直接决定整个请求的拦截与否;后序另外两个方法是否执行也与它的返回值有关联!

  • 作用:既然是前置拦截、那么可以常常被用来做权限拦截、session是否合法的检查…

/**
 *
 *  preHandler: 预先处理的请求方法
 *  Object handler : 被拦截的控制器对象(Controller)
 *  返回值: boolean
 *  true: 放行     false: 驳回
 *  特点:
 *  1. 预处理方法它的执行时间: 在控制器方法之前先执行的。
 *  2. 可以对请求做处理, 可以做登录的检查、权限的判断、 统计数据...
 *  3. 决定请求是否执行。
 */
@Override
public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception {
    System.out.println("===========preHandle处理前!===========");
    return true;
}

3.2、postHandle方法
  • postHandle属于后置的方法当controller处理完毕之后(前提是preHandle放行);因为拿到的ModelAndView对象,所以可以进行一些数据的更改,可以将它看做小controller(二次处理)

  • 作用:修改请求的结果、响应的视图对象…

/**
 *  postHandler: controller执行后返回处理的方法
 *  Object handler: 被拦截的控制器对象(Controller)
 *  参数:mv控制器方法的返回值(响应的结果)
 *
 *  特点:
 *  1. 在控制器方法之后执行的。
 *  2. 能获取到控制器放的执行结果,可以修改原来的数据、视图...
 *  3. 可以对请求进行二次处理。
 *
 */

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView mv) throws Exception {
    System.out.println("===========postHandle处理后!===========");
}

3.3、afterCompletion方法
  • 该方法是整个请求 - 响应一体完成之后才进行执行,可以进行一些修尾的工作。

  • 作用:例如当用户成功修改密码之后,可以在这里将其session过期一下使其重新登录。(个人想法)

/**
 *   Object handler: 被拦截的控制器对象(Controller)
 *   Exception ex : 异常对象
 *
 *   特点:
 *   1. 在请求-响应完成后进行执行
 *   2. 进行一些清理工作、释放内存、清理临时变量。
 *
 */

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

3.4、拦截器的声明

不仅仅需要实现HanlderInterceptor接口,重写这几个方法还需要在配置文件中进行声明拦截器;此时只是定义了一个拦截器,需要使用的时候还需要针对拦截器进行指定拦截哪些controller。

<!-- 声明拦截器 -->
<mvc:interceptors>
    <mvc:interceptor>
        <!-- 拦截所有的controler -->
        <mvc:mapping path="/**"/>
        <!-- 使用哪些拦截器进行拦截-->
        <bean class="com.Interceptor.MyInterceptor"/>
    </mvc:interceptor>
    <mvc:interceptor>
    	<!-- 拦截user/tomain请求-->
        <mvc:mapping path="/user/tomain"/>
        <bean class="com.Interceptor.LoginInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>


4、多个拦截器组合

当出现多个拦截器时执行顺序里面大有文章,大概有以下几点:

  • 每个拦截器的prehandle都能直接决定整个请求的成功与否,只要有一个返回值是false那么controller都无法执行。

  • 多个拦截器是组合成一条拦截器执行链,按照配置文件声明的顺序进行执行。(准确的说并不是每个方法都是这样)

  • preHandle方法是按照声明的顺序执行的,postHandle方法是按照声明的顺序逆序执行,根据上图也能够容易理解(就近原则)

  • afterCompletion方法比较特殊,它需要preHandle执行才有可能执行;这个和多拦截的顺序有关系!


4.1、定义两个拦截器

按照顺序先声明A、再声明B拦截器;执行顺序是跟这里直接挂钩的!

<!-- 声明拦截器 -->
<mvc:interceptors>
    <mvc:interceptor>
        <!-- A拦截器 -->
        <mvc:mapping path="/**"/>
        <bean class="com.Interceptor.InterceptorA"/>
    </mvc:interceptor>
    <mvc:interceptor>
        <!-- B拦截器 -->
        <mvc:mapping path="/**"/>
        <bean class="com.Interceptor.InterceptorB"/>
    </mvc:interceptor>
</mvc:interceptors>

4.2、同时返回true

在这里插入图片描述

结论:可以看到preHandle根据定义顺序执行,而postHandle是恰好相反的;afterCompletion方法正常情况下也是倒序执行


4.3、同时返回false

在这里插入图片描述
A拦截器先执行的,由于没有放行所以B拦截器无论是否为true都没有机会执行!

结论:每个拦截器都有一票否决权利,直接决定拦截与否;而afterCompletion无法执行是因为preHandle没有返回true导致。


4.4、A返回true,B返回false

在这里插入图片描述
A成功执行preHandle,而B的preHandle返回false同样是一票否决请求的成功;

结论:afterCompletion执行需要preHandle先执行;可以不执行postHandle。


4.5、A返回false,B返回true

在这里插入图片描述
结论:与上面相同



5、拦截器的执行顺序

要了解拦截器的执行顺序首先要知道拦截器是如何组合生成的,其次需要了解拦截器的执行链;组合拦截器执行链的过程会使用HandlerExecutionChain类,将所有的拦截器加入其中。

public class HandlerExecutionChain {
	//处理器controller
	private final Object handler;	
			
	//拦截器数组
	private final List<HandlerInterceptor> interceptorList = new ArrayList<>();	

	//获取执行链中的handler处理器
	public Object getHandler() {
		return this.handler;
	}
	
	/**
	 * postHandle执行
	 */
	boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
		for (int i = 0; i < this.interceptorList.size(); i++) {
			HandlerInterceptor interceptor = this.interceptorList.get(i);
			if (!interceptor.preHandle(request, response, this.handler)) {
				triggerAfterCompletion(request, response, null);
				return false;
			}
			this.interceptorIndex = i;
		}
		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 = 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 = this.interceptorList.get(i);
			try {
				interceptor.afterCompletion(request, response, this.handler, ex);
			}
			catch (Throwable ex2) {
				logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
			}
		}
	}
}

保证顺序的机制特别简单,就是一个数组的顺序、逆序遍历。

在这里插入图片描述



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值