springmvc更多家族成员---框架内处理流程拦截与handlerinterceptor---08(代码片段)

大忽悠爱忽悠 大忽悠爱忽悠     2022-11-30     157

关键词:

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这对键值对,来决定是否放行:

springmvc更多家族成员--国际化视图与localresolver---10(代码片段)

SpringMVC更多家族成员--国际化视图与LocalResolver---10引言可用的LocaleResolverLocaleResolver的足迹LocaleResolver在初始化流程中的使用processRequest处理请求的核心方法DispatcherServlet的doservice方法小结LocaleResolver后继使用时机体会Locale的变更与L... 查看详情

springmvc更多家族成员----文件上传---06(代码片段)

SpringMVC更多家族成员----文件上传---06本节导读文件上传与MultipartResolver使用MultipartResolver进行文件上传的简单分析StandardServletMultipartResolver概览注意StandardMultipartHttpServletRequest概览StandardMultipartFile概览CommonsMultipartReso 查看详情

springmvc更多家族成员----handler与handleradaptor---07(代码片段)

SpringMVC更多家族成员----Handler与HandlerAdaptor---07问题的起源深入了解Handler自定义Handler近看HandlerAdaptor的奥秘告知Handler与HandlerAdaptor的存在案例问题的起源最初为了降低理解的难度,我们说,HandlerMapping将会通过HandlerExecutionc... 查看详情

springmvc更多家族成员--主题(theme)与themeresolver(代码片段)

SpringMVC更多家族成员--Theme与ThemeResolver引言提供主题资源的ThemeSource管理主题的ThemeResolver切换主题的ThemeChangeInterceptor引言不管是使用Windows操作系统还是使用Linux操作系统,当我们对某种风格的桌面主题感到厌烦的时候,... 查看详情

springmvc处理流程总结

SpringMVC是一个模块,或者说是一种流程,一个套路。是Spring框架基于MVC设计思想实现的一个模块,用于处理Web请求。SpringMVC 与  MVC设计思想的关系类似于: MVC是一个接口,是规范,而SpringMVC是它的一个实现模块。... 查看详情

struts和spring分别是啥?

...ring分别是什么?二者有什么区别?通常更多的我们对比 SpringMVC与Struts把这张图放在这里,我是想说SpringMVC和Struts2真的是不一样的,虽然在都有着核心分发器等相同的功能组件(这些由MVC模式本身决定的)。 为什么SpringMVC... 查看详情

ssm框架springmvc笔记---汇总

SpringMVC是基于MVC开发模式的框架,用来优化控制器,是Spring家族的一员,同时它也具备IOC和AOPSpringMVC是基于MVC开发模式的框架,用来优化控制器,是Spring家族的一员,同时它也具备IOC和AOP 查看详情

springmvc控制框架的流程及原理1:总概及源码分析

 主要介绍springmvc控制框架的流程及原理SpringWebMVC处理请求的流程具体执行步骤如下:首先用户发送请求————>前端控制器,前端控制器根据请求信息(如URL)来决定选择哪一个页面控制器进行处理并把请求... 查看详情

springboot与springmvc的区别

参考技术ASpringMVC和SpringBoot都是Spring家族的重要成员。Spring家族的使命就是为了简化而生。SpringMVC简化日常Web开发的,后来随着自身的发展,SpringMVC变得臃肿复杂,而SpringBoot则进一步简化了SpringMVC开发。SpringMVC为JavaWeb而生。Sprin... 查看详情

springmvc主要组件说明

...vlet(不需要开发,由框架提供【核心】)DispatcherServlet是SpringMVC的入口函数。接收请求,响应结果,相当于转发器,中央处理器。有了DispatcherServlet,可以大大减少其它组件之间的耦合度。用户请求到达前端控制器,就相当于mvc... 查看详情

springmvc控制框架的流程及原理1:总概及源码分析

      主要介绍springmvc控制框架的流程及原理SpringWebMVC处理请求的流程具体执行步骤如下:首先用户发送请求————>前端控制器,前端控制器根据请求信息(如URL)来决定选择哪一个页面控... 查看详情

springmvc执行流程

...分享技术、知识和生活*各种干货,记得关注哦!*/ 1.3SpringMVC执行流程​  从接收请求到响应,SpringMVC框架的众多组件通力配合,各司其职,有条不紊的完成分内工作!在整个 查看详情

springmvc常见面试题总结

1、什么是SpringMVC?简单介绍下你对springMVC的理解?Spring MVC是一个基于MVC架构的用来简化web应用程序开发的应用开发框架,它是Spring的一个模块,无需中间整合层来整合 ,它和Struts2一样都属于表现层的框架。在web模型中,MV... 查看详情

springmvc控制框架的流程及原理2:例子说明

springmvc运行步骤:首先用户发送请求http://localhost:8080/hello——>web容器,web容器根据“/hello”路径映射到DispatcherServlet(url-pattern为/)进行处理;DispatcherServlet——>BeanNameUrlHandlerMappi 查看详情

springmvc入门第1天--框架说明与快速入门

...工具测试平台工程名字日期作者备注V1.02016.06.29lutianfeinonespringmvc框架springmvc业务流程框架springmvc框架组件说明SpringMVC入门程序配置前端控制器配置处理器适配器开发Handler视图编写配置Handler配置处理器映射器配置视图解析器部署... 查看详情

springmvc工作流程(超级详细版)

目录一:springMVC常用基本组件二:SpringMVC执行的流程一:springMVC常用基本组件1.DispatcherServlet是SpringMVC框架了里面的前端控制器作用:统一处理用户发来的请求并和响应,相当于一个中间转换器,减少了各... 查看详情

springmvc框架整理

SpringMVC框架整理第二发,数据绑定流程,数据校验(错误信息国际化),拦截器,异常处理。数据绑定流程(数据转换,数据格式化,数据校验)1.SpringMVC主框架将ServletRequest 对象及目标方... 查看详情

springboot与springmvc的区别是啥?

...在此两者的基础上实现了其他延伸产品的高级功能。2、SpringMVC是基于Servlet的一个MVC框架主要解决WEB开发的问题因为Spring的配置非常复杂各种XML、JavaConfig、hin处理起来比较繁琐于是为了简化开发者的使用,从而创造性地推出了Spr... 查看详情