本博文基于:
Spring MVC 5.3.1
JDK1.8
SpringMVC拦截器与源码分析
SpringMVC拦截器的作用与应用场景
在处理用户请求之前或者之后,通常需要执行一些行为,如检测用户是否可以登录,或者将请求的信息记录到日志中,时刻的记录当前用户请求的日志信息。Spring MVC提供了拦截器机制,用于请求的预处理和后处理。
假设可能存在这样一个场景,可能本网站注册账号谁都可以注册,但是有些内容需要VIP才能进行访问。这样的需求,就可以通过SpringMVC的拦截器来实现。
通常看到这个需求会想到利用 Java Servlet 的过滤器(Filter)来做,本文中会介绍拦截器和Filter的区别。
拦截器的定义
SpringMVC中的拦截器用于拦截控制器方法的执行,如果要在SpringMVC中定义一个拦截器需要对拦截器进行定义和配置,有两种常用方式:
- 实现
HandlerInterceptor
接口 - 继承类
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
类似:
如果是SecondInterceptor
中preHandle()
拦截器不放行,则执行的结果如下:
Servlet Filter 和 SpringMVC 拦截器的区别
由于整个Spring MVC是基于一个DispatcherServlet来进行一系列的操作,可以跳出来看,就是一个Servlet和Filter的区别,在JAVAWEB基础阶段得知,Filter的执行一定是在DispatcherServlet之前的。
而对于Interceptor是对每个映射的Controller来处理和放行操作的。下面是简图,主要体现的是两者的作用位置区别。
源码解析
在观察拦截器的实现和为什么会出现如此的执行流程:主要涉及到两个类:DispatcherServlet
和HandlerExecutionChain
这两个类:
我把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,不会执行DispatcherServlet
中return
方法,所以会继续执行返回的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