在使用SpringMVC拦截器的时候,我们接触的最多的便是HandlerInterceptor接口,因为我们所有的自定义拦截器都必须要实现HandlerInterceptor接口,那么就先从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 {
}
根据注释部分我们知道如下内容:
preHandle是在找到处理handler对象的HandlerMapping之后,HandlerAdapter调度handler之前执行。
postHandle是在HandlerAdapter调度handler之后,DispatcherServlet渲染视图之前执行,可以通过ModelAndView来向视图中添加一些信息等。
afterCompletion是在渲染视图结束后执行,主要可以用来进行事后的资源清理。
其中
postHandle和afterCompletion方法是反顺序执行的。也就是说第一个拦截器会最后一个执行。
关于HandlerInterceptor的执行顺序我们可以在HandlerExecutionChain类中找到。
这个类由一个handler和若干的HandlerInterceptor构成。那么这个类的作用就显而易见了,就是将拦截器和handle组合起来执行。就是对handle进行了包装。
这个类中有几个主要的方法:
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
//按顺序执行
for (int i = 0; i < interceptors.length; i++) {
HandlerInterceptor interceptor = interceptors[i];
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
}
return true;
}
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
throws Exception {
HandlerInterceptor[] interceptors = 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);
}
}
}
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex)
throws Exception {
HandlerInterceptor[] interceptors = 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 ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
}
}
}
}
从函数名中我们可以看出这些方法分别是做什么的,分别是执行interceptorList中所有interceptor的preHandle、postHandle和afterCompletion方法
。
先从applyPreHandle()看起,我们发现这个方法就是做的这样一个工作,
按照列表中interceptor的顺序来执行它们的preHandle方法,直到有一个返回false。
再看一下返回false后这个方法所做的工作,这时会调用triggerAfterCompletion方法,此时this.interceptorIndex指向上一个返回true的interceptor的位置,所以它会按逆序执行所有返回true的interceptor的afterCompletion方法
。换言之,也就是对于任意的返回false的interceptor都不会执行afterCompletion方法。而且是中断之前所有的preHandle执行完成之后才会执行afterCompletion方法。接下来是applyPostHandle(),这个方法较为简单,就是按照逆序执行所有interceptor的postHandle方法。最后的triggerAfterCompletion()也是一样,就是从最后一次preHandle成功的interceptor处逆序执行afterCompletion。
HandlerExecutionChain是通过HandlerMapping的getHandler方法返回的
。继承该接口的类是来实现请求和handler对象的映射关系
的。
这个接口中只有这样一个方法
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
根据函数名,参数及返回值我们不难猜出这个接口的作用,就是根据request返回HandlerExecutionChain。
至于HandlerMapping在springMVC中有多种实现,我们此处就不深究了。
对于getHandler最后的调度部分便是springMVC的最外层DispatcherServlet类了
DispatcherServlet类中调用HandlerMapping的getHandler的方法为getHandler(同名)
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace(
"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
}
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
从代码中不难看出整个逻辑就是依次判断servlet中的每个handlerMapping是否能够匹配该请求,直到找到那个匹配的然后返回处理结果。
对于HandlerExecutionChain的调用我们可以在doDispatch()中找到
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 此处用processedRequest 需要注意的是:若是处理上传,processedRequest 将和request不再指向同一对象
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
//主要用来管理异步请求的处理。什么时候要用到异步处理呢?就是业务逻辑复杂(或者其他原因),为了避免请求线程阻塞,需要委托给另一个线程的时候。
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
//检查是否为上传文件请求
//checkMultipart 这个方法很重要,判断是否是上传需求。且看下面的具体分析:
//如果请求是POST请求,并且请求头中的Context-Type是以multipart/开头的就认为是文件上传的请求
processedRequest = checkMultipart(request);
//标记一下:是否是文件上传的request
multipartRequestParsed = (processedRequest != request);
// 步骤1,获取执行链,重要
// 找到一个处理器,如果没有找到对应的处理类的话,这里通常会返回404,如果throwExceptionIfNoHandlerFound属性值为true的情况下会抛出异常
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// 步骤2,获取适配器,重要
// 根据实际的handler去找到一个合适的HandlerAdapter,方法详细逻辑同getHandler,因此不再解释
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
//如果是GET请求,如果内容没有变化的话,则直接返回
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 步骤3,执行拦截器pre方法,重要
// 这段代码很有意思:执行处理器连里的拦截器们,具体参阅下面详细:
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
//步骤4,真正处理逻辑,重要
// 真正执行我们自己书写的controller方法的逻辑。返回一个ModelAndView
// 这也是一个很复杂的过程(序列化、数据绑定等等),需要后面专题讲解
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 如果异步启动了,这里就先直接返回了,也就不会再执行拦截器PostHandle之类的
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
//意思是:如果我们没有设置viewName,就采用默认的。否则采用我们自己的
applyDefaultViewName(processedRequest, mv);
//步骤5,执行拦截器post方法,重要
// 执行所有的拦截器的postHandle方法,并且把mv给他
// 这里有一个小细节:这个时候拦截器是【倒序】执行的
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
//步骤6,处理视图,重要
//这个方法很重要,顾名思义,他是来处理结果的,渲染视图、处理异常等等的 下面详细分解
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
//步骤7,执行拦截器收尾方法,重要
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
从上面代码中我们可以验证第一部分所说的HandlerInterceptor接口中三个方法的执行顺序:
- preHandle是
在找到处理handler对象的HandlerMapping之后,HandlerAdapter调度handler之前执行。
- postHandle是
在HandlerAdapter调度handler之后,DispatcherServlet渲染视图之前执行
。- afterCompletion是
在渲染视图结束后执行
。
@Component
public class A implements HandlerInterceptor {
//在Controller执行之前调用,如果返回false,controller不执行
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("---------A.preHandle--------");
return true;
}
//controller执行之后,且页面渲染之前调用
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("---------A.postHandle--------");
}
//页面渲染之后调用,一般用于资源清理操作
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("---------A.afterCompletion--------");
}
}
@Configuration
public class MvcConfiguration implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new A());
registry.addInterceptor(new B());
registry.addInterceptor(new C());
registry.addInterceptor(new D());
WebMvcConfigurer.super.addInterceptors(registry);
}
}