Spring MVC更多家族成员---框架内处理流程拦截与HandlerInterceptor---08

Posted 大忽悠爱忽悠

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring MVC更多家族成员---框架内处理流程拦截与HandlerInterceptor---08相关的知识,希望对你有一定的参考价值。

Spring MVC更多家族成员---框架内处理流程拦截与HandlerInterceptor---08


引言

前面已经讲述了,HandlerMapping返回的用于处理具体Web请求的Handler对象,是通过一个HandlerExecutionChain对象进行封装的(这在HandlerMapping的接口定义上可以看出来)。

我们却一直没有对这个HandlerExecutionChain做进一步的解释,现在是彻底揭开这个谜团的时候了。

说白了,HandlerExecutionchain就是一个数据载体,它包含了两方面的数据,一个就是用于处理Web请求的Handler,另一个则是一组随同Handler一起返回的HandlerInterceptor。

这组HandlerInterceptor可以在Handlerl的执行前后对处理流程进行拦截操作。

HandlerInterceptor定义了如下三个拦截方法:

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 
	


下面是对这三个拦截方法的简单说明。

拦截器方法调用时机都体现在DispathcerServlet的doDispatch方法流程中:

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.
				//利用handlerMapping获得对应的handler
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) 
				//进行handler没有的处理---404
					noHandlerFound(processedRequest, response);
					return;
				

				// Determine handler adapter for the current request.
				//获取当前handler的适配器
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				// Process last-modified header, if supported by the handler.
				//处理last-modified请求头
				String method = request.getMethod();
				boolean isGet = HttpMethod.GET.matches(method);
				if (isGet || HttpMethod.HEAD.matches(method)) 
					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) 
						return;
					
				
                //前置处理    
				if (!mappedHandler.applyPreHandle(processedRequest, response)) 
					return;
				

				//适配器被调用
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				if (asyncManager.isConcurrentHandlingStarted()) 
					return;
				

				applyDefaultViewName(processedRequest, mv);
				//适配器方法结束后,调用方法后处理拦截
				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);
			
			//上面目标handle中抛出的异常都会被捕获,然后交给processDispatchResult方法进行全局异常处理
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		
		//如果在进行视图渲染过程中依旧抛出了异常,那么就触发对应的后处理逻辑
		catch (Exception ex) 
			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		
		catch (Throwable err) 
			triggerAfterCompletion(processedRequest, response, mappedHandler,
					new NestedServletException("Handler processing failed", err));
		
		finally 
		//清理工作
			if (asyncManager.isConcurrentHandlingStarted()) 
				// Instead of postHandle and afterCompletion
				if (mappedHandler != null) 
					mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
				
			
			else 
				// Clean up any resources used by a multipart request.
				if (multipartRequestParsed) 
					cleanupMultipart(processedRequest);
				
			
		
	            

preHandle

  • boolean preHandle: 该拦截方法将在相应的HandlerAdaptor调用具体的Handler处理Web请求之前执行。如果想在此之前阻断后继处理流程,preHandle方法将是最合适也是我们唯一的选择。preHandle通过boolean返回值表明是否继续执行后继处理流程。
    • true表明允许后继处理流程继续执行。如果当前HandlerInterceptor位于所在HandlerInterceptor链之前或者中间位置,那么后继HandlerInterceptor的preHandle将继续执行。如果HandlerInterceptor是所在HandlerInterceptor链的最后一个,那么处理Web请求的Handler将允许执行。
    • false表明preHandle方法不允许后继流程的继续执行,包括HandlerInterceptor链中的其他HandlerInterceptor以及其后的Handler。在这种情况下,通常认为preHandle方法内部已经自行处理掉了当前的Wb请求。当然,通过抛出相应的异常的方式,也可以达到与返回false同样的阻断效果。

一般来说,preHandle将是我们使用最多的拦截方法。我们也可以在这里进行一些必要条件检查,如果没能通过检查,通过preHandle可以阻断后继处理流程的执行。

	boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception 
	//调用HandlerExecutionChain中所有拦截器的preHandle
		for (int i = 0; i < this.interceptorList.size(); i++) 
			HandlerInterceptor interceptor = this.interceptorList.get(i);
			//如果有一个拦截器返回false,那么就进入后置处理环节
			if (!interceptor.preHandle(request, response, this.handler)) 
				triggerAfterCompletion(request, response, null);
				return false;
			
			//记录当前拦截器执行的索引
			this.interceptorIndex = i;
		
		return true;
	

postHandle

该拦截方法的执行时机为HandlerAdaptor调用具体的Handler处理完Web请求之后,并且在视图的解析和渲染之前。

通过该方法我们可以获取Handler执行后的结果,即ModelAndview。

我们可以在原处理结果的基础上对其进行进一步的后处理,比如添加新的统一的模型数据,或者对ModelAndView中的数据进行变更等。

postHandle返回类型为void,不可以阻断后继处理流程。

				//调用handlerAdapter的handle方法
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                ... 
				mappedHandler.applyPostHandle(processedRequest, response, mv);

applyPostHandle方法如果执行过程中抛出异常,会直接进入triggerAfterCompletion流程

	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

在框架内整个处理流程结束之后,或者说视图都渲染完了的时候,不管是否发生异常,afterCompletion拦截方法将被执行。

如果处理是异常结束的话,我们可以在该方法中获得异常(Exception)的引用并对其进行统一处理。

		catch (Exception ex) 
			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		
		catch (Throwable err) 
			triggerAfterCompletion(processedRequest, response, mappedHandler,
					new NestedServletException("Handler processing failed", err));
		
       
        private void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response,
			@Nullable HandlerExecutionChain mappedHandler, Exception ex) throws Exception 

		if (mappedHandler != null) 
		//调用HandlerExecutionChain的后置处理方法
			mappedHandler.triggerAfterCompletion(request, response, ex);
		
		throw ex;
	
    
    	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);
			
		
	

另外,如果Web请求处理过程中有相应资源需要清理的话,也可以在这里完成。

不用说也知道,afterCompletion的返回值为void,并且到它执行的时候,处理流程已经是尾声了,根本没有阻断执行流程的必要。

processDispatchResult方法进行视图渲染时,如果正常返回,也会调用triggerAfterCompletion:

		processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
	private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
			@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
			@Nullable Exception exception) throws Exception 

		boolean errorView = false;

		if (exception != null) 
			if (exception instanceof ModelAndViewDefiningException) 
				logger.debug("ModelAndViewDefiningException encountered", exception);
				mv = ((ModelAndViewDefiningException) exception).getModelAndView();
			
			else 
			//spring mvc提供的统一异常处理
				Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
				mv = processHandlerException(request, response, handler, exception);
				errorView = (mv != null);
			
		

		// Did the handler return a view to render?
		//正常的视图渲染逻辑
		if (mv != null && !mv.wasCleared()) 
			render(mv, request, response);
			if (errorView) 
				WebUtils.clearErrorRequestAttributes(request);
			
		
		else 
			if (logger.isTraceEnabled()) 
				logger.trace("No view rendering, null ModelAndView returned.");
			
		

		if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) 
			// Concurrent handling started during a forward
			return;
		
         //正常结束或者异常在上面被处理了,触发后置处理
		if (mappedHandler != null) 
			// Exception (if any) is already handled..
			mappedHandler.triggerAfterCompletion(request, response, null);
		
	

还有就是在prehandle中返回false时,会触发调用


HandlerExecutionChain 源码概览

public class HandlerExecutionChain 

	private static final Log logger = LogFactory.getLog(HandlerExecutionChain.class);
    //目标handler对象
	private final Object handler;
    //handler对象关联的拦截器  
	private final List<HandlerInterceptor> interceptorList = new ArrayList<>();
   //记录前置通知中拦截器执行的下标
	private int interceptorIndex = -1;
    ...
    //应用前置拦截
	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;
			
			//如果某个拦截器前置通知返回了false,那么对应interceptorIndex记录到的就是最后一个返回true的拦截器的下标
			this.interceptorIndex = i;
		
		return true;
	


	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);
		
	


	void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) 
	    //从interceptorIndex开始,往前执行后置通知
		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);
			
		
	
     ....

正常拦截器的执行流程如下:

如果某个拦截器的preHandle方法返回false,那么执行流程会向下面这样:


可用的HandlerInterceptor实现

做任何事情之前我们都会先去找一下有没有现成的“锤子”。

对于HandlerInterceptor:来说,情况同样如此。在实现自定义的HandlerInterceptor.之前,我们先看一下Spring MVC都准备了哪些现成的HandlerInterceptor实现。

实际上,通过查看HandlerInterceptor的继承层次,我们可以发现很多HandlerInterceptor实现类:

不过,鉴于在稍后介绍LocalResolver和ThemeResolver的时候会再次接触LocaleChangeInterceptor和ThemeChangeInterceptor,我们先将它们放置一边。

重点看一下UserRoleAuthorizationInterceptor和WebContentInterceptori这两个可用的HandlerInterceptor实现类。


UserRoleAuthorizationInterceptor

UserRoleAuthorizationInterceptor允许我们通过HttpServletRequest的isUserInRole方法,使用指定的一组用户角色(UserRoles)对当前请求进行验证。

如果验证通不过,UserRoleAuthorizationInterceptor将默认返回HTTP的403状态码,即forbidden。我们可以通过覆写handleNotAuthorized方法改变这种默认行为,比如将请求导向一个信息页面。

UserRoleAuthorizationInterceptor的使用极其简单,只需要指定验证用的一组用户角色(UserRoles)即可,如下所示:

<bean id="userRolesAuthHandlerInterceptor"
class="org.springframework.Web.servlet.handler.UserRoleAuthorizationInterceptor">
   <property name="authorizedRoles">
      <list>
          <value>Admin</value>
      </list>
   </property>
</bean>

UserRoleAuthorizationInterceptor将循环遍历这组指定的用户角色(UserRoles).对当前请求进行验证。


WebContentInterceptor

WebContentInterceptor对处理流程的拦截主要做如下几件事情。

  • 检查请求方法类型是否在支持方法之列。如果当前请求的方法类型超出我们通过setSupportedMethods方法指定的范围,那么WebContentInterceptor将抛出HttpRequestMethodNotSupportedException从而阻断后继处理流程。这通常用于进一步限定请求的方法类型,比如,我们可以通过setSupportedMethods方法设置supportedMethods.只为POST一种,不支持GET或者其他请求方法类型。
  • 检查必要的Session实例。如果我们设置requiresession属性为true,同时又发现当前请求不能返回一个已经存在的Session实例,WebContentInterceptor将抛出HttpSessionRequiredException阻断后继处理流程。
  • 检查缓存时间并通过设置相应HTTP头(Header)的方式控制缓存行为。WebContentInterceptor允许我们通过setCacheSeconds方法设置请求内容的缓存时间。它将通过设置用于缓存管理的HTTP头(HTTP Header)的形式,对请求内容对应的缓存行为进行管理。我们可以通过useCacheControlHeader或者useExpiresHeader属性,进一步明确是使用的HTTP1.I的Cache-Control指令还是HTTP1.0的Expires指令。

通常,WebContentInterceptor使用方式如下所示:

<bean id="WebContentInterceptor"
class="org.springframework.Web.servlet.mvc.WebContentInterceptor"
p:cacheSeconds="30" p:supportedMethod="POST">
</bean>

除此之外,我们还可以通过setCacheMappings方法,进一步明确指定不同请求与其缓存时间之间的细粒度的映射关系。

注意

UserRoleAuthorizationInterceptor和WebContentInterceptor:都是只在preHandle拦截方法中实现了相应的拦截逻辑。

我想,你应该已经从它们能够“阻断后继处理流程”的功能上看出这一点。


自定义HandlerInterceptor实现

Spring为我们提供了现成的HandlerInterceptor固然不错,但这并不足以满足广大群众的各种需求。

单就HandlerInterceptor作为一个扩展点而存在的意义来讲,如果拦截Web请求处理逻辑的需求就那么几种的话,完全没有必要设置这么一个角色。

而实际上,我们所要面对的系统和场景却是繁杂多变的,所以,大部分时间,我们不得不根据应用的需求提供我们的自定义HandlerInterceptor实现类。

这里给出一个简单的例子,通过校验当前请求是否携带了pass=true这对键值对,来决定是否放行:

以上是关于Spring MVC更多家族成员---框架内处理流程拦截与HandlerInterceptor---08的主要内容,如果未能解决你的问题,请参考以下文章

Spring MVC更多家族成员--国际化视图与LocalResolver---10

Spring MVC更多家族成员----文件上传---06

Spring MVC更多家族成员----Handler与HandlerAdaptor---07

Spring MVC更多家族成员--主题(Theme)与ThemeResolver

Spring MVC 以及 Spring Boot

spring boot与spring mvc的区别是什么?