关键词:
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... 查看详情