SpringMVC 源码分析(五)中我们大概看了下GET请求在Spring MVC中处理的整个链路,前面都是进行一些属性的设置等操作,最终进入到DispatcherServlet类的doDispatch方法中进行处理,其实POST、PUT、DELETE这些请求最终也是在doDispatch中进行处理,本篇就主要从doDispatch开始分析,看下DispatcherServlet在最终执行我们的Controller前都做了写什么。
首先是getHandler去找一个该请求适合的处理器,在我们普遍的Spring Web项目中就是根据请求的URL找到一个能处理该请求的Controller和其对应的@RequestMapping标记的方法
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 {
//检查是否是MultipartContent的请求,如果是的话就会转换为MultipartHttpServletRequest
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
//为本次request请求寻找一个具体的Handler,并封装成一个执行器链
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// 为当前的handler寻找一个具体的HandlerAdapter,因为HandlerMapping有多个实现类,存储的Handler类型也各不相同
// 需要适配器来提供一个统一供外部调用的方法入口
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
//如果支持last-modified,则处理
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
//拦截器前置处理
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 真正调用Controller的地方
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);
}
//处理程序调用的结果,即ModelAndView或要解析为ModelAndView的异常
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);
}
}
}
}
进入getHandler方法中,可以看到具体的匹配逻辑委托给DispatcherServlet的成员变量HandlerMapping来处理的。handlerMappings是在第一次请求的时候初始化的。可以在SpringMVC 源码分析(四)中看到DispatcherServlet的初始化流程。

HanderMapping有四个具体的实现类:
RequestMappingHandlerMapping(在SpringMVC 源码分析(二)和SpringMVC 源码分析(三)中讲过url映射初始化过程,可以了解一下),主流的匹配模式。
BeanNameUrlHandlerMapping,顾名思义是根据beanName来匹配的,首先要实现Controller接口,并且要向SpringMVC容器注册对应的beanName,然后直接根据beanName路径请求就可以了,不是主流,了解下就可以。

SimpleUrlHandlerMapping,也不常用,具体可以参看该文章:https://iowiki.com/springmvc/springmvc_simpleurlhandlermapping.html
WelcomePageHandlerMapping,就是处理欢迎页的处理器映射,随着前后端分离,实际上用处不大。
下面我们重点看下RequestMappingHandlerMapping的匹配逻辑:

getHandlerInternal由父类AbstractHandlerMethodMapping实现,进入getHandlerInternal中,可以看到先解析出请求路径,然后加上读写锁,再去调用lookupHandlerMethod方法,根据请求路径寻找可以匹配的处理器映射

继续进入lookupHandlerMethod方法中,首先会根据请求路径去注册表中获取映射,如果存在就加入到匹配的集合中,没有则遍历注册表中的所有请求路径映射,最后如果已匹配的集合大于1,再获取最优的匹配并返回。如果最终都没有找到匹配的处理器,就会根据用户的请求给出与最接近的该请求处理器提示信息。

继续进入到getMappingByUrl方法中就会发现这个就是我们在SpringMVC 源码分析(二)最后分析到的几个map集合中获取对应的处理器。

接着我们再看getHandlerExecutionChain方法来获取执行器链。该执行器链是Spring MVC层面的拦截器集合,主要在Controller方法执行前后触发调用,可以看到与filter不同的是,filter是web容器级别的调用。该执行器链获取方法其实很简单,是遍历并判断注册进去的拦截器链是否符合拦截条件来拦截调用的。在最上面的代码块中可以看到在执行ha.handle前后分别调用了mappedHandler.applyPreHandle、mappedHandler.applyPostHandle、mappedHandler.triggerAfterCompletion,

该拦截器链也是Spring MVC的一个开放设计,也是一个非常不错的设计,在开发中可以模仿这种代码模式,编写出更加通用的代码。言归正传,我们要实现这一部分的功能很简单,供开发者自己去配置,只需要实现HandlerInterceptor接口就可以,下面给一个示例给大家了解一下


上面这两个类其实都一样,类名不同在于我们在注入Spring MVC容器的时候,一个是全部拦截,一个是根据条件拦截,比如我访问localhost:8080/hello/hi这个链接的时候,两个拦截器都会触发,但是当我访问localhost:8080/nihao这个链接的时候,就只有MyInterceptor这个拦截器会被触发。如下图所示:

如果有兴趣的话可以自行去了解下他的原理,基本上跟我们之前的RequestMappingHandlerMapping一个套路。
继续回到最上面的代码中,当获取到执行器链之后,Spring MVC又进行getHandlerAdapter调用,Adapter是装饰器模式,因为HandlerMapping有多个实现类,存储的Handler类型也各不相同,为了不让用户去重点关注这些区别,需要适配器来提供一个统一供外部调用的方法入口。
好了,本篇分析就结束了,下一篇我们再一起分析下DispatcherServlet是如何执行我们的Controller的。