专栏首页软件开发-青出于蓝SpringFramework之HandlerInterceptor

SpringFramework之HandlerInterceptor

    最近在使用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源码

(adsbygoogle = window.adsbygoogle || []).push({});

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • DispatcherServlet之getHandlerAdapter

                                                            图1 DispatcherServlet的doS...

    克虏伯
  • Kubernates之ingress方式部署springboot

        我们将这个springboot部署到k8s上,制作镜像就不描述了,k8s的yaml文件如下,之后执行"kubectl  apply  -f  k8s-d...

    克虏伯
  • Ribbon的RandomRule和RoundRobinRule 原

                                                                                    ...

    克虏伯
  • 原创:用zabbix api批量添加web监控

    用户1057912
  • Filter

    过滤器是实现了Filter接口的一个java类,是Servlet的高级应用,可以处理request和response,该接口有下面三种方法

    晚上没宵夜
  • GPU编程(四): 并行规约优化

    如果之前没有用过gdb, 可以速学一下, 就几个指令. 想要用cuda-gdb对程序进行调试, 首先你要确保你的gpu没有在运行操作系统界面, 比方说, 我...

    sean_yang
  • 如何在Laravel5.8中正确地应用Repository设计模式

    在本文中,我会向你展示如何在 Laravel 中从头开始实现 repository 设计模式。我将使用 Laravel 5.8.3 版,但 Laravel 版本...

    砸漏
  • 智能合约:Ethernaut题解(二)

    思路:首先贡献一点金额,来通过 require 触发 fallback 函数,来成为合约的所有者,然后 withdraw 函数转走合约中的所有钱

    yichen
  • xml基本知识点

    xml, Extensible Markup Language,可扩展的标记语言。 ? xml文档结构.jpg xml文档的规则 1.0 xml文档必须以一个...

    东风冷雪
  • YOLO论文翻译——中文版

    You Only Look Once: Unified, Real-Time Object Detection 摘要 我们提出了YOLO,一种新的目标检测方法。...

    Tyan

扫码关注云+社区

领取腾讯云代金券