博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
SpringFramework之HandlerInterceptor
阅读量:5772 次
发布时间:2019-06-18

本文共 5868 字,大约阅读时间需要 19 分钟。

hot3.png

    最近在使用Spring时,总感觉对HandlerInterceptor有点模糊,回头再来看看,记录下。

    SpringFramework的版本4.3.x.RELEASE.

    List-1

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {	HttpServletRequest processedRequest = request;	HandlerExecutionChain mappedHandler = null;	boolean multipartRequestParsed = false;	WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);	try {		ModelAndView mv = null;		Exception dispatchException = null;		try {			processedRequest = checkMultipart(request);			multipartRequestParsed = (processedRequest != request);			// Determine handler for the current request.			//1、得到handlerExecutionChain,里面含有对应的Controller,里面还有interceptor,			mappedHandler = getHandler(processedRequest);			if (mappedHandler == null || mappedHandler.getHandler() == null) {				noHandlerFound(processedRequest, response);				return;			}			// Determine handler adapter for the current request.			//2、会根据Controller方法上的注解,将request中的请求转换为对象,转换为对应的pathVariable、body等			HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());			// Process last-modified header, if supported by the handler.			String method = request.getMethod();			boolean isGet = "GET".equals(method);			if (isGet || "HEAD".equals(method)) {				long lastModified = ha.getLastModified(request, mappedHandler.getHandler());				if (logger.isDebugEnabled()) {					logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);				}				if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {					return;				}			}            //3、			if (!mappedHandler.applyPreHandle(processedRequest, response)) {				return;			}			// Actually invoke the handler.			//4、真正进行反射操作,调用方法			mv = ha.handle(processedRequest, response, mappedHandler.getHandler());			if (asyncManager.isConcurrentHandlingStarted()) {				return;			}			applyDefaultViewName(processedRequest, mv);            //5、			mappedHandler.applyPostHandle(processedRequest, response, mv);		}		catch (Exception ex) {			dispatchException = ex;		}		catch (Throwable err) {			// As of 4.3, we're processing Errors thrown from handler methods as well,			// making them available for @ExceptionHandler methods and other scenarios.			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) {	    //8		triggerAfterCompletion(processedRequest, response, mappedHandler,				new NestedServletException("Handler processing failed", err));	}

    如List-1所示,

  • 1位置,根据ServletRequest得到HandlerExecutionChain(具体怎么得到的有点复杂,后续再深入分析),它有点类似SpringSecurity中的FilterChainProxy。里面有HandlerInterceptor数组,和handler属性(通过debug可以看到是HandlerMethod)。
  • 2位置,会根据Controller方法上的注解,将request中的请求转换为对象,转换为对应的pathVariable、body等。
  • 3位置,调用HandlerExecutionChain的applyPreHandle,如下,逐个调用preHandle方法,只要preHandle返还false,就会你像调用afterCompletion,最后返还false。思考,通过源码可知在HandlerInterceptor中拦截后续preHandle中抛出的异常是拦截不到的。这个HandlerExceptionChain是线程安全的,每个请求对应一个HandlerExceptionChain实例对象,所以源码中通过类属性interceptorIndex来控制下标。

    List-2

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;}
  • 位置4,到位置4,已经执行了所有handlerInterceptor的preHandle,并且已经将ServletRequest中的参数转换为了Controller方法上标有注解的参数。所以在位置通过反射调用Controller的方法。通过源码可以看出,HandlerInterceptor可以看出,只要Controller里面的业务代码抛出异常,那么位置5就执行不到,就不会执行HandlerInterceptor的postHandle方法。
  • 位置5,如下List-3所示,会逆向执行HandlerInterceptor的postHandle方法。

    List-3

void applyPostHandle(HttpServletRequest request, HttpServletResponse response, 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);		}	}}
  • 位置6、如果之前没有抛出异常,就调用processDispatchResult方法,方法processDispatchResult的最后,有如下List-4,调用HandlerExecutionChain的triggerAfterCompletion,会逆序调用HandlerInterceptor的afterCompletion,由List-5可以看出,afterCompletion中抛出异常,会被Spring框架吞噬,每个HandlerInterceptor的afterCompletion方法中,拿到的异常都是null——看List-4中传入的参数。

    List-4

......	if (mappedHandler != null) {		mappedHandler.triggerAfterCompletion(request, response, null);	}}

    List-5

void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, 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);			}		}	}}
  • 位置7/8,如果之前的步骤抛出异常,就会到步骤7/8,调用triggerAfterCompletion,并将异常作为参数传入,由List-5可以看出,每个HandlerInterceptor的afterCompletion都能接收到该异常。

    

            

                                                              图1 

    如图1所示,

  1. 如果执行preHandle的链中抛出异常,那么逆向执行afterCompletion,不会执行postHandle;如果postHandle抛出异常,则逆向执行afterCompletion链。
  2. 如果没有抛出异常,正常情况下,先执行完preHandle链,之后调用controller的方法,之后调用逆向postHandle链,之后逆向执行afterCompletion链,如图1中右边的竖行方向调用所示。

    通过源码可知,用HandlerInterceptor拦截全局异常,有点不靠谱,要深入理解源码,不然有可能达不到预期的效果。

思考

  1. 我们自定义的,这些HandlerInterceptor是如何被接入到Spring中的?
  2. Filter、HandlerInterceptor、@ControllerAdvice的调用顺序?
  3. 定义多个HandlerIntercepto时,如何指定顺序?

Reference

  1. SpringFramework源码

转载于:https://my.oschina.net/u/2518341/blog/3028935

你可能感兴趣的文章
@Transient注解输出空间位置属性
查看>>
Ansible-playbook 条件判断when、pause(学习笔记二十三)
查看>>
5种你未必知道的JavaScript和CSS交互的方法(转发)
查看>>
线程进程间通信机制
查看>>
galera mysql 多主复制启动顺序及命令
查看>>
JS prototype 属性
查看>>
中位数性质——数列各个数到中位数的距离和最小
查看>>
WebApp之Meta标签
查看>>
添加Java文档注释
查看>>
Python3批量爬取网页图片
查看>>
iphone-common-codes-ccteam源代码 CCEncoding.m
查看>>
微信公众平台开发(96) 多个功能整合
查看>>
[转]MVC4项目中验证用户登录一个特性就搞定
查看>>
用Perl编写Apache模块续二 - SVN动态鉴权实现SVNAuth 禅道版
查看>>
Android 阴影,圆形的Button
查看>>
C++概述
查看>>
卡特兰数
查看>>
006_mac osx 应用跨屏幕
查看>>
nginx中配置文件的讲解
查看>>
MindNode使用
查看>>