1、拦截器简介
- 拦截器:Intercepor拦截器是Handler(Controller)的拦路虎;在用户发起请求时如果请求的Controller下存在拦截器,那么拦截器会对一些请求进行拦截… 总而言之拦截器是围绕Controller做出的一些处理!
-
拦截器的作用:
-
拦截用户的请求,可以预先对请求做出处理;根据结果决定是否执行Controller。
-
如果拦截器执行,那么拦截器在Controller执行完成返回给DispatcherServlet前端控制器之前可以将执行返回的结果进行一些修改。
-
如果拦截器执行,在请求响应完成之后;此时拦截器依然可以做出一些修尾的工作,例如清理Session…
-
根据上述的描述,可以认为拦截器亦可以将Controller共同的功能定义到拦截器中!
-
-
拦截器特点:
-
拦截器可以分为MVC框架自带的拦截器和程序员自定义拦截器。
-
一个Controller可以有0个、若干个拦截器的存在。
-
拦截器测试拦截用户的请求Controller
-
- 拦截器思想:可以很容易 “猜到” 拦截器的核心思想就是aop;拦截器接口一共就三个方法,三个方法在aop中分别是不同的增强方式实现!
2、拦截器执行流程
前言:
-
首先明白JSP、Servlet是同一个东西、JSP最后也会转化为Servlet只不过是由Tomocat服务器做的。
-
SpringMVC中有且只有一个Servlet:前端控制器DispatcherServlet;而Controller只是一个Bean对象与Servlet没有半毛钱关系!
-
用户发起请求无论是访问Controller、还是JSP页面、还是Servlet都会经过Filter过滤器,因为访问Controller同样是需要经过DispatcherServlet的!它是MVC架构中的唯一一个Servlet。
-
此时Filter过滤器会处理一些东西:典型的设置编码集防止乱码、也可能会过滤一些垃圾请求(不在配置的URL之中)
-
现在假设用户请求的是Controller,那么一定会经过DispatcherServlet调度分发;此时这一步是非常重要的:
-
DispatcherServlet先交给HandlerMapping进行解析URI
-
其次HandlerMapping找到对应的Handler(Controller)之后,将该Handler下对应的MVC拦截器组成一个拦截器执行链,并且打包上Handler形成处理器执行器HandlerExcution,最后重新交给DispatcherServlet
-
DispatcherServlet现在手握处理器执行器HandlerExcution,将其找到适配的处理器适配器HandlerAdapter,由它完成一些解析参数…
-
处理器适配器解析完成之后,到此拦截器开始工作!
-
-
上面说过所有的拦截器组合成一个执行链,按照拦截器配置的顺序进行执行!其中拦截器中有个方法preHandler中是返回值极为重要:
-
返回值为true:放行继续执行下一个拦截器,或者是最后一个拦截器通过开始执行controller
-
返回值为false:拦截器是一个执行链,无论有几个拦截器,只要有一个拦截器返回值为false,将会直接拦截那么controller就无法执行。
-
-
如果preHandler的返回值是true,那么Controller执行完毕之后将会按照相反的顺序执行postHandler方法执行;可以将ModeAndView进行修改!
-
最后交给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);
}
}
}
}
保证顺序的机制特别简单,就是一个数组的顺序、逆序遍历。