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更多家族成员----Handler与HandlerAdaptor---07