Spring MVC 请求执行流程的源码深度解析【两万字】

  基于最新Spring 5.x,详细介绍了Spring MVC 请求的执行流程源码,给出了更加详细的Spring MVC请求执行流程步骤总结,以及详细的执行流程图。

  我正在参与CSDN《新程序员》有奖征文,活动地址:https://marketing.csdn.net/p/52c37904f6e1b69dc392234fff425442。

  此前,我们学习了Spring MVC项目启动初始化过程中的部分重要源码,Spring MVC项目启动之后,便能够接受请求、处理请求。
  此前,我们已经学习了Spring MVC的请求执行流程,但实际上在Spring MVC请求执行流程过程中,要做的事儿有很多,比图CORS配置、参数转换等等,现在让我们从源码的角度再一次深入了解Spring MVC 请求的执行流程。
  在文章的最后我们也给出了更加详细的Spring MVC请求执行流程步骤总结,以及详细的执行流程图,嫌弃源码太长的小伙伴可以直接跳到末尾。
  下面的源码版本基于5.2.8.RELEASE

Spring MVC源码 系列文章

Spring MVC 初始化源码(1)—ContextLoaderListener与父上下文容器的初始化

Spring MVC 初始化源码(2)—DispatcherServlet与子容器的初始化以及MVC组件的初始化【一万字】

Spring MVC 初始化源码(3)—配置标签的源码解析

Spring MVC 初始化源码(4)—@RequestMapping注解的源码解析

Spring MVC 请求执行流程的源码深度解析【两万字】

文章目录

  • Spring MVC源码 系列文章
  • 1 源码入口
  • 2 FrameworkServlet#service入口
  • 3 processRequest处理请求
    • 3.1 doService处理请求
    • 3.2 publishRequestHandledEvent发布请求处理完毕事件
  • 4 doDispatch分发请求
    • 4.1 checkMultipart检查文件上传请求
    • 4.2 getHandler获取HandlerExecutionChain
      • 4.2.1 mapping#getHandler获取HandlerExecutionChain
        • 4.2.1.1 getHandlerInternal获取内部处理器
          • 4.2.1.1.1 lookupHandlerMethod查找匹配的HandlerMethod
        • 4.2.1.2 CORS配置
          • 4.2.1.2.1 hasCorsConfigurationSource是否具有CORS配置
          • 4.2.1.2.2 getCorsHandlerExecutionChain配置CORS
    • 4.3 noHandlerFound没找到handler的处理
    • 4.4 getHandlerAdapter获取HandlerAdapter
      • 4.4.1 adapter#supports是否支持handler
    • 4.5 applyPreHandle执行拦截器的预处理
    • 4.6 handle实际处理请求
      • 4.6.1 invokeHandlerMethod执行HandlerMethod
        • 4.6.1.1 invokeAndHandle执行请求并处理
          • 4.6.1.1.1 invokeForRequest执行请求
          • 4.6.1.1.2 handleReturnValue处理返回值
        • 4.6.1.2 getModelAndView获取ModelAndView
    • 4.7 processDispatchResult处理异常或渲染视图
      • 4.7.1 render渲染视图
        • 4.7.1.1 resolveViewName解析视图名
        • 4.7.1.2 view#render渲染视图
          • 4.7.1.2.1 renderMergedOutputModel执行渲染
  • 5 总结

1 源码入口

  Spring MVC是对原始Servlet的封装,虽然我们在开发过程中不会再接触到Servlet级别的API,但是我们知道Spring MVC中有一个核心的Servlet实现,那就是DispatcherServlet,它作为核心控制器,用于接收任何的请求并将请求转发给对应的处理组件。因此,Spring MVC的请求处理入口仍然可以从DispatcherServlet中找到。
  DispatcherServlet的uml类图如下:
Spring MVC 请求执行流程的源码深度解析【两万字】_第1张图片
  可以看到,从它间接的继承了HttpServlet,因此Spring MVC的请求源码入口同样是HttpServlet#service()方法!
Spring MVC 请求执行流程的源码深度解析【两万字】_第2张图片
  这个service方法中并没有做太多的事情,主要是将ServletRequest和ServletResponse强转转换为HttpServletRequest和HttpServletResponse,最后会调用另一个service方法,该方法才是真正的核心处理请求的方法。
  HttpServlet自己的service方法源码如下。其内部时一系列的以do开头的模版方法,回顾一下,在早期原始的Servlet项目中,我们所要开发的就是这些doGet、doPost方法:
Spring MVC 请求执行流程的源码深度解析【两万字】_第3张图片
  并且,如果我们足够心细,我们能发现FrameworkServlet直接重写了HttpServlet的整个service方法,此前的原始Servlet开发的我们都是开发的do开头的方法,比如doGet、doPost,但是Spring MVC将整个service方法都重写了,我们一起来看看。

2 FrameworkServlet#service入口

  FrameworkServlet#service方法可以看作Spring MVC一次请求的处理入口方法。
  FrameworkServlet重写父类HttpServlet的service方法的原因在注释上说的很明白了,就是为了支持PATCH请求。
  重写的方法的逻辑比较简单,首先解析出当前请求的方法,如果是PATCH方法或者没有请求方法,则直接调用processRequest方法处理该请求,否则,对于其他请求方法,则还是会调用父类HttpServlet的service来处理,最终,对于不同的请求方法,还是会调用各自对应的do开头的模版方法来处理。

/**
 * FrameworkServlet的方法
 * 

* 重写父类HttpServlet的service实现用以拦截PATCH请求。 */ @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //获取当前请求的请求方法,可能是:GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE HttpMethod httpMethod = HttpMethod.resolve(request.getMethod()); //如果是PATCH请求,或者请求方法为null,则另外处理 //PATCH方法是新引入的,是对PUT方法的补充,用来对已知资源进行局部更新,目前用的比较少 if (httpMethod == HttpMethod.PATCH || httpMethod == null) { //直接调用processRequest方法处理 processRequest(request, response); } else { //对于其他请求方法,则还是会调用父类HttpServlet的service来处理 //最终,对于不同的请求方法,还是会调用各自对应的do开头的模版方法来处理 super.service(request, response); } }

  很明显,do开头的一系列方法也都由DispatcherServlet及其父类帮我们都实现好了,因为在使用Spring MVC框架的时候我们并没有编写这些底层方法的实现。实际上,do开头的一系列模版方法,也是由FrameworkServlet帮我们实现的,并且我们能够发现它实现的这些方法最终都会指向processRequest方法
Spring MVC 请求执行流程的源码深度解析【两万字】_第4张图片
  从这里就能看出processRequest方法的重要性了,继续向下看!

3 processRequest处理请求

  processRequest方法位于FrameworkServlet中,提供了处理请求方法的骨架实现,并且留出了一系列模版方法让子类去实现自己的逻辑,这是模版方法模式的应用。
  该方法会将本次请求的LocaleContext和RequestAttributes绑定到LocaleContextHolder和RequestContextHolder的线程本地变量属性中,这样在当前请求处理过程中就可以在其他地方直接获取本次请求的request、response等对象,比如:HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();,注意需要在当前请求线程中才能获取到。
  请求处理完毕之后,无论成功与否,都会调用publishRequestHandledEvent方法发布一个ServletRequestHandledEvent事件,表示该请求处理完毕,我们可以监听该事件,并做出不同的处理。

  该方法中的核心处理请求的方法就是doService模版方法,该方法留给子类比如DispatcherServlet实现。

/**
 * FrameworkServlet的方法
 * 

* 处理此请求,发布事件,无论结果如何。 */ protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //当前时间毫秒 long startTime = System.currentTimeMillis(); //失败原因(异常) Throwable failureCause = null; //获取之前可能存在的LocaleContext,可能在Filter中设置的 //LocaleContext中可以获取当前的语言环境Locale LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext(); //根据当前request构建LocaleContext,就是new 一个SimpleLocaleContext LocaleContext localeContext = buildLocaleContext(request); //获取之前可能存在的RequestAttributes,可能在Filter中设置的 RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes(); //根据当前request、response、previousAttributes构建新的ServletRequestAttributes //如果此前的previousAttributes不为null,那么就还是使用这个对象 //否则就通过request和response直接new 一个ServletRequestAttributes,因此RequestAttributes中可以获取request和response ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes); //获取异步请求管理器,新建一个WebAsyncManager对象,并且通过setAttribute存入request的属性中 //属性名为org.springframework.web.context.request.async.WebAsyncManager.WEB_ASYNC_MANAGER WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); //注册RequestBindingInterceptor这个拦截器,该拦截器可以初始化或者重置LocaleContext和RequestAttributes asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor()); //初始化LocaleContext和RequestAttributes绑定到LocaleContextHolder和RequestContextHolder的线程本地变量属性中 //这样在当前请求处理过程中就可以在其他地方直接获取本次请求的request、response等对象,比如: //HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); initContextHolders(request, localeContext, requestAttributes); try { /* * 处理请求的核心模版方法,留给子类比如DispatcherServlet实现 */ doService(request, response); } catch (ServletException | IOException ex) { //获取处理请求过程中抛出的异常 failureCause = ex; throw ex; } catch (Throwable ex) { //获取处理请求过程中抛出的异常 failureCause = ex; throw new NestedServletException("Request processing failed", ex); } finally { //最终,将LocaleContext和RequestAttributes从LocaleContextHolder和RequestContextHolder的线程本地变量属性中解除绑定 resetContextHolders(request, previousLocaleContext, previousAttributes); if (requestAttributes != null) { //发出请求已完成的信号。 //将会执行所有请求销毁回调,并更新在请求处理期间已访问的会话属性,最后将requestActive标志改为false requestAttributes.requestCompleted(); } logResult(request, response, failureCause, asyncManager); /* * 发布一个事件,表示该请求处理完毕,无论成功与否 * 我们可以监听该事件,并做出不同的处理 */ publishRequestHandledEvent(request, response, startTime, failureCause); } }

3.1 doService处理请求

  doService是处理请求的核心模版方法,该方法留给子类比如DispatcherServlet实现。
  DispatcherServlet的doService方法主要是将DispatcherServlet的一些属性放置在当前请求的属性中,方便后续直接从request中获取,并委托doDispatch方法进行实际请求处理,因此实际的真正请求处理还得看doDispatch方法(是不是觉得调用层次很深呢?)。

//DispatcherServlet的属性

/**
 * 包含请求执行完毕后执行请求属性的清理吗,默认清理
 */
private boolean cleanupAfterInclude = true;

/**
 * DispatcherServlet的默认策略属性以其开头的公共前缀。
 */
private static final String DEFAULT_STRATEGIES_PREFIX = "org.springframework.web.servlet";

/**
 * 保存当前的Web应用程序上下文的请求属性
 */
public static final String WEB_APPLICATION_CONTEXT_ATTRIBUTE = DispatcherServlet.class.getName() + ".CONTEXT";

/**
 * 保存当前的LocaleResolver的请求属性,可通过视图检索。
 */
public static final String LOCALE_RESOLVER_ATTRIBUTE = DispatcherServlet.class.getName() + ".LOCALE_RESOLVER";

/**
 * 保存当前的ThemeResolver的请求属性,可由视图检索。
 */
public static final String THEME_RESOLVER_ATTRIBUTE = DispatcherServlet.class.getName() + ".THEME_RESOLVER";

/**
 * 保存当前的ThemeSource的请求属性,可通过视图检索。
 */
public static final String THEME_SOURCE_ATTRIBUTE = DispatcherServlet.class.getName() + ".THEME_SOURCE";

/**
 * DispatcherServlet的方法
 * 

* 公开特定于DispatcherServlet的请求属性,并委托doDispatch进行实际请求处理。 */ @Override protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception { logRequest(request); //如果是包含请求(include,即请求包含),那么保存request 属性快照,以便能够在包含请求处理完毕之后恢复原始属性 //也就是说,在包含请求处理过程中设置的request 属性将不会被保留 Map<String, Object> attributesSnapshot = null; //判断是否是请求包含,尝试从request的属性中获取名为javax.servlet.include.request_uri的属性 //如果存在该属性,那么就是包含请求,否则就不是 if (WebUtils.isIncludeRequest(request)) { attributesSnapshot = new HashMap<>(); //获取属性名 Enumeration<?> attrNames = request.getAttributeNames(); while (attrNames.hasMoreElements()) { String attrName = (String) attrNames.nextElement(); //默认将所有属性存入快照map中 if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) { attributesSnapshot.put(attrName, request.getAttribute(attrName)); } } } //将一些对象存入request的属性中,方便handler和view中使用 request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext()); request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver); request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver); request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource()); //flashMap管理器,默认为SessionFlashMapManager //FlashMap可用于将属性从一个请求传递到另一个请求,通常是用在重定向中。 if (this.flashMapManager != null) { //查找由与当前请求匹配的先前请求保存的FlashMap,将其从基础存储中删除,还删除其他过期的FlashMap实例。 FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response); if (inputFlashMap != null) { request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap)); } request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap()); request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager); } try { /* * 分发、处理请求的真正核心方法 */ doDispatch(request, response); } finally { if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { //如果是包含请求,那么还原原始属性快照。 if (attributesSnapshot != null) { restoreAttributesAfterInclude(request, attributesSnapshot); } } } }

3.2 publishRequestHandledEvent发布请求处理完毕事件

  在processRequest方法的finally块的最后,将会通过当前DispatcherServlet关联的webApplicationContext发布一个ServletRequestHandledEvent事件,表示该请求处理完毕,无论成功与否。
  ServletRequestHandledEvent中包含了本次请求的各种信息,我们可以监听该事件,并做出不同的处理。

//FrameworkServlet的属性

/**
 * 我们是否应该在每个请求的末尾发布ServletRequestHandledEvent?
 * 默认是应该的,但可以通过该参数关闭
 */
private boolean publishEvents = true;
/**
 * 此Servlet关联的WebApplicationContext,在此前的initServletBean方法中被初始化
 */
@Nullable
private WebApplicationContext webApplicationContext;


/**
 1. FrameworkServlet的方法
 2. 

3. 发布一个事件,表示该请求处理完毕,无论成功与否 4. 5. @param request 当前request 6. @param response 当前response 7. @param startTime 请求处理开始时间戳毫秒 8. @param failureCause 失败原因(抛出的异常,可能为null) */ private void publishRequestHandledEvent(HttpServletRequest request, HttpServletResponse response, long startTime, @Nullable Throwable failureCause) { //如果允许发布事件并且关联的IoC容器不为null,那么无论我们是否成功,都发布一个事件。 if (this.publishEvents && this.webApplicationContext != null) { //计算请求处理花费的时间 long processingTime = System.currentTimeMillis() - startTime; //通过IoC容器发布ServletRequestHandledEvent事件,包含各种请求信息 //这样我们就可以使用Spring的事件监听机制来监听这个事件进而来监听这个请求了 this.webApplicationContext.publishEvent( //this:事件源,当前DispatcherServlet对象 new ServletRequestHandledEvent(this, //请求路径、ip地址 request.getRequestURI(), request.getRemoteAddr(), //请求方法、ServletName request.getMethod(), getServletConfig().getServletName(), //SessionId、username WebUtils.getSessionId(request), getUsernameForRequest(request), //请求处理时间、失败原因、响应状态码 processingTime, failureCause, response.getStatus())); } }

4 doDispatch分发请求

  将请求实际分派给对应的handler并且调用不同的组件进行请求处理。该方法的逻辑实际上就是Spring MVC请求处理的主体流程。

  大概逻辑为:

  1. 调用getHandler方法确定当前请求的处理器——handler。
  2. 调用getHandlerAdapter方法根据handler确定当前请求的处理器适配器——HandlerAdapter。
  3. 调用applyPreHandle方法,顺序应用拦截器链中的此前找到的所有拦截器的PreHandle预处理方法。全部通过则执行后续步骤,不通过则倒序执行已通过的拦截器的afterCompletion方法,随后直接返回。
  4. 调用handle方法,通过HandlerAdapter使用给定的handler实际处理请求,返回一个ModelAndView结果对象。
  5. handler正常处理完毕时,调用applyPostHandle方法,倒序应用拦截器链中的此前找到的所有拦截器的postHandle后处理方法。
  6. 调用processDispatchResult方法,处理执行handler的结果。主要目的是处理执行过程中的异常,或者根据返回的ModelAndView结果渲染视图。
  7. 最后无论请求处理成功还是失败抛出异常,都会调用triggerAfterCompletion方法,倒序应用拦截器链中的此前找到的所有拦截器的afterCompletion处理方法,表示请求处理完毕。
/**
 * DispatcherServlet的方法
 * 

* 将请求实际分派给对应的handler进行处理。 *

* 该handler将通过依次应用servlet的HandlerMappings来获得,并且通过查询Servlet的 * handlerAdapters来查找支持该handler的第一个HandlerAdapter,从而获得HandlerAdapter。 * 所有HTTP方法都由该方法处理,由HandlerAdapters或handler本身来决定可接受的方法。 * * @param request 当前 HTTP request * @param response 当前 HTTP response */ protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { //processedRequest初始化为当前request HttpServletRequest processedRequest = request; //已匹配的handler,也就是Handler执行链 HandlerExecutionChain mappedHandler = null; //是否是已解析的多部件请求,即文件上传请求 boolean multipartRequestParsed = false; //异步请求管理器 WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { //模型和视图对象,handler执行的返回结果,内部包含了model和view对象 ModelAndView mv = null; //记录异常 Exception dispatchException = null; try { /* * 将请求使用多部分解析器转换为多部分请求,如果未设置多部分解析器或者当前请求不是多部分请求,则返回原始请求。 * 对于CommonsMultipartResolver,它底层走的是ApacheCommons FileUpload的文件上传逻辑,因此需要引入依赖 * 它会检查如果是POST请求并且content-type是"multipart/"前缀,则算作文件上传请求 * 则原始HttpServletRequest(实际上是tomcat中的RequestFacade对象)被解析为一个DefaultMultipartHttpServletRequest对象 * * 对于StandardServletMultipartResolver,它是走的Servlet 3.0多部分请求解析的逻辑,不需要引入额外依赖 * 它会检查请求的content-type如果是"multipart/"前缀,则算作文件上传请求 * 则原始HttpServletRequest(实际上是tomcat中的RequestFacade对象)对象被解析为一个StandardMultipartHttpServletRequest对象 */ processedRequest = checkMultipart(request); //判断是否是多部件请求,即文件上传请求 multipartRequestParsed = (processedRequest != request); /* * 1 确定当前请求的处理器——handler * * 通过遍历handlerMappings,依次调用每个HandlerMapping的getHandler方法获取HandlerExecutionChain对象 * 只要有一个HandlerMapping的getHandler方法返回值不为null,就返回该返回值,就不会继续向后查找 * 如果在所有的handlerMapping中都没找到,则返回null。 * * 对于RequestMappingHandlerMapping,他返回的handler就是HandlerMethod */ mappedHandler = getHandler(processedRequest); //如果没有找到任何handler,那么执行noHandlerFound的逻辑 if (mappedHandler == null) { //通常返回404响应码或者抛出NoHandlerFoundException异常 noHandlerFound(processedRequest, response); //直接返回,doDispatch方法结束 return; } /* * 2 根据handler确定当前请求的处理器适配器——HandlerAdapter * * 通过遍历handlerAdapters,依次调用每个HandlerAdapter的supports方法 * 只要有一个HandlerAdapter的supports方法返回true,就返回该HandlerAdapter,就不会继续向后查找 * 如果在所有的HandlerAdapter中都没找到,则直接抛出ServletException。 * * 对于HandlerMethod,它的适配器就是RequestMappingHandlerAdapter */ HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); /* * 如果handler支持,则处理last-modified头 */ String method = request.getMethod(); boolean isGet = "GET".equals(method); //如果是GET或者HEAD请求 if (isGet || "HEAD".equals(method)) { //获取last-modified头信息,这是对应的文档的最近一次更新时间 long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); //如果没有任何改变并且是GET请求,则直接返回 //这是对于不经常改变的文档的缓存机制的支持,如果此前获取过某个文档,并且此次访问时该文档仍然没有改变,则服务器不会再次渲染该文档 if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } /* * 3 顺序应用拦截器链中的此前找到的所有拦截器的PreHandle预处理方法 * 只要有一个拦截器不通过,那么该请求就不会继续处理,而是直接返回 */ if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } /* * 4 通过HandlerAdapter使用给定的handler实际处理请求,返回一个ModelAndView结果对象 * * 不同的handler的实际工作流程差别非常大,其中还包括序列化、数据绑定、检验等等步骤 * 对于HandlerMethod来说,就会尝试执行对应的@ReuqestMapping方法,也就是业务逻辑 */ mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } //如果存在mv并且没有view,则设置view为默认的viewName applyDefaultViewName(processedRequest, mv); /* * handler正常处理完毕 * 5 倒序应用拦截器链中的此前找到的所有拦截器的postHandle后处理方法 */ 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); } /* * 6 处理执行handler返回的结果 * 主要目的是处理执行过程中的异常,或者根据返回的ModelAndView结果渲染视图,最后倒序应用拦截器的afterCompletion方法 */ processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { /* * 倒序应用拦截器链中的此前找到的所有拦截器的afterCompletion处理方法,表示请求处理完毕 * * 某个afterCompletion方法在执行中即使抛出异常,也会被catch掉,不会影响其他方法的执行 */ triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } catch (Throwable err) { /* * 倒序应用拦截器链中的此前找到的所有拦截器的afterCompletion处理方法,表示请求处理完毕 * * 某个afterCompletion方法在执行中即使抛出异常,也会被catch掉,不会影响其他方法的执行 */ 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 { //清理多部分请求使用的所有资源。 if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } } }

4.1 checkMultipart检查文件上传请求

  使用多部分解析器将请求转换为多部分请求,如果未设置多部分解析器,则只需使用现有请求。多部分解析器来自于此Servlet关联的IoC容器bean,beanName指定为multipartResolver。
  对于CommonsMultipartResolver,它底层走的是ApacheCommons FileUpload的文件上传逻辑,因此需要引入依赖,它会检查如果是POST请求并且content-type是"multipart/"前缀,则算作文件上传请求,则原始HttpServletRequest(实际上是tomcat中的RequestFacade对象)被解析为一个DefaultMultipartHttpServletRequest对象。
  对于StandardServletMultipartResolver,它是走的Servlet 3.0多部分请求解析的逻辑,不需要引入额外依赖,它会检查请求的content-type如果是"multipart/"前缀,则算作文件上传请求,则原始HttpServletRequest(实际上是tomcat中的RequestFacade对象)对象被解析为一个StandardMultipartHttpServletRequest对象。

//DispatcherServlet的属性

/**
 * 此servlet使用的MultipartResolver
 * 来自于此Servlet关联的IoC容器bean,beanName指定为multipartResolver
 */
@Nullable
private MultipartResolver multipartResolver;


/**
 * DispatcherServlet的方法
 * 

* 使用多部分解析器将请求转换为多部分请求,如果未设置多部分解析器,则只需使用现有请求。 * * @param request 当前 HTTP request * @return 已处理的请求(如果需要,则使用多部分请求包装) */ protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException { //如果设置了multipartResolver并且此请求是多部分请求,即文件上传请求 if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) { //如果该请求已经是MultipartHttpServletRequest 那仅仅打印日志 //如果我们设置了MultipartFilter这个过滤器,那么在请求到达Servlet之前就会被判断是否是多部分请求 //如果是,则会被被解析为MultipartHttpServletRequest,MultipartFilter默认使用StandardServletMultipartResolver来解析 if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) { if (request.getDispatcherType().equals(DispatcherType.REQUEST)) { logger.trace("Request already resolved to MultipartHttpServletRequest, e.g. by MultipartFilter"); } } else if (hasMultipartException(request)) { logger.debug("Multipart resolution previously failed for current request - " + "skipping re-resolution for undisturbed error rendering"); } else { try { //调用多部分解析器的resolveMultipart方法将原始HttpServletRequest请求解析为MultipartHttpServletRequest的实现请求 //对于CommonsMultipartResolver,会创建一个DefaultMultipartHttpServletRequest并返回,其内部包装了原始的的请求 //对于StandardServletMultipartResolver则会创建一个StandardMultipartHttpServletRequest并返回,其内部包装了原始的的请求 return this.multipartResolver.resolveMultipart(request); } catch (MultipartException ex) { if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) { logger.debug("Multipart resolution failed for error dispatch", ex); // Keep processing error dispatch with regular request handle below } else { throw ex; } } } } //返回原始请求 return request; }

4.2 getHandler获取HandlerExecutionChain

  按顺序尝试调用所有的HandlerMapping,返回此请求的HandlerExecutionChain,如果找不到handler,则返回null
  该方法就是常见的Spring MVC执行流程的第一步了,即“通过HandlerMapping处理器映射器找到对应的Handler处理器,返回一个HandlerExecutionChain执行链对象,该对象包含一个拦截器链,链的尾部就是当前的handler处理器,对于基于@RequestMapping注解方法注册的控制器来说,handler就是HandlerMethod”。
  SpringMVC默认加载三个请求处理映射类:RequestMappingHandlerMapping、SimpleUrlHandlerMapping和BeanNameUrlHandlerMapping。如果配置了标签,或者@EnableWebMvc注解,则不会加载默认配置,但至少会加载两个:RequestMappingHandlerMapping和BeanNameUrlHandlerMapping,还会加上我们自定义配置的HandlerMapping的bean。

//DispatcherServlet的属性

/**
 * 此servlet使用的HandlerMappings列表
 */
@Nullable
private List<HandlerMapping> handlerMappings;

/**
 1. DispatcherServlet的方法
 2. 

3. 按顺序尝试调用所有的HandlerMapping,返回此请求的HandlerExecutionChain。 4. 5. @param request 当前 HTTP request 6. @return HandlerExecutionChain;如果找不到handler,则返回null */ @Nullable protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { if (this.handlerMappings != null) { //遍历所有的HandlerMapping,依次调用getHandler方法尝试获取HandlerExecutionChain //如果某个返回结果不为null,则返回该返回值,不会继续向后查找 for (HandlerMapping mapping : this.handlerMappings) { HandlerExecutionChain handler = mapping.getHandler(request); if (handler != null) { return handler; } } } return null; }

4.2.1 mapping#getHandler获取HandlerExecutionChain

  Spring MVC通过调用HandlerMapping#getHandler方法来获取该请求对应的HandlerExecutionChain
  通常RequestMappingHandlerMapping排在handlerMappings集合的首位,将首先被调用。我们主要讲解RequestMappingHandlerMapping的getHandler方法实现。
  不同的HandlerMapping实现,它的getHandler方法也不一样。我们能够很容易的猜到Spring对此使用了模板方法模式,有一个HandlerMapping的抽象实现类,该类实现了getHandler接口并提供了骨架实现,然后有各个子类提供细节的实现,这个抽象类就是AbstractHandlerMapping
  该方法的主要逻辑为:

  1. 调用getHandlerInternal方法尝试获取匹配的handler处理器,对于RequestMappingHandlerMapping就是返回HandlerMethod
  2. 如果没有获取到处理器,那么获取默认处理器,这个默认handler是我们自己配置的,默认也是为null。
  3. 通过上面的查找,如果handler还是为null,那么直接返回null。
  4. 如果找到了handler,判断handler是Bean名称或已解析的handler实例,如果是beanName,那么就从IoC容器中查找对应的handler实例。这里的逻辑主要是BeanNameUrlHandlerMapping用到。
  5. 调用getHandlerExecutionChain方法,为给定的handler构建一个HandlerExecutionChain对象,其中还包括所有适用的拦截器形成的拦截器链属性interceptorList
  6. 如果当前handler实现了CorsConfigurationSource接口或者具有全局CORS配置(比如通过标签或者WebConfig#addCorsMappings),或者说当前handler属于HandlerMethod,并且存在对于当前HandlerMethod的局部CORS配置(比如通过@CrossOrigin注解),或者说当前请求是CORS预检请求。满足以上三个条件之一,那么就可以对当前请求进行CORS配置,以用于后续CORS处理
  7. 返回最终的HandlerExecutionChain
/**
 * AbstractHandlerMapping的属性
 * 全局的CORS配置
 */
@Nullable
private CorsConfigurationSource corsConfigurationSource;

/**
 * AbstractHandlerMapping的方法
 * 

* 查找给定请求的handler,如果未找到特定的handler,则退回到使用默认handler。 * * @param request 当前 HTTP request * @return 相应的handler处理程序实例或默认handler处理程序 */ @Override @Nullable public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { /* * 1 查找给定请求的handler处理程序,如果未找到特定请求,则返回null,随后将使用默认handler. * * 对于CORS的预检请求,此方法从"Access-Control-Request-Method"头部字段中获取实际请求所使用的HTTP方法 * 从"Access-Control-Request-Headers"头部字段中获取实际请求所携带的自定义首部字段与此方法的CORS配置进行匹配 * * 该方法是一个抽象方法,留给子类实现。对于RequestMappingHandlerMapping,则会尝试返回HandlerMethod * 过程中会匹配@ReuqestMapping设置的各种条件,比如path、Method、params、headers、consumes、produces、patterns…… */ Object handler = getHandlerInternal(request); if (handler == null) { //如未未找到特定请求,则使用默认handler。这个默认handler是我们自己配置的,默认也是为null handler = getDefaultHandler(); } //通过上面的查找,如果handler还是为null,那么返回null if (handler == null) { return null; } //判断handler是Bean名称或已解析的handler实例 if (handler instanceof String) { //如果是beanName,那么就从IoC容器中查找对应的handler实例 String handlerName = (String) handler; handler = obtainApplicationContext().getBean(handlerName); } /* * 2 为给定的handler构建一个HandlerExecutionChain对象,其中还包括所有适用的拦截器形成的拦截器链interceptorList * * 默认实现使用给定的handler,公共拦截器以及与当前请求URL匹配的任何MappedInterceptors来构建标准的HandlerExecutionChain。 */ HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request); if (logger.isTraceEnabled()) { logger.trace("Mapped to " + handler); } else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) { logger.debug("Mapped to " + executionChain.getHandler()); } /* * 3 如果当前handler实现了CorsConfigurationSource接口或者具有全局CORS配置(比如通过标签或者WebConfig#addCorsMappings), * 或者说当前handler属于HandlerMethod,并且存在对于当前HandlerMethod的局部CORS配置(比如通过@CrossOrigin注解) * 或者说当前请求是CORS预检请求 * 满足以上三个条件之一,那么就可以对当前请求进行CORS配置,以用于后续CORS处理 */ if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) { //获取全局CORS配置,比如基于标签配置 CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null); //获取针对当前handler的CORS配置,比如类和方法上的@CrossOrigin注解配置 CorsConfiguration handlerConfig = getCorsConfiguration(handler, request); //CORS配置合并,对于单个字段的配置,方法上的@CrossOrigin注解优先级更高,方法和类上的@CrossOrigin注解的值将组合(java中是list集合) config = (config != null ? config.combine(handlerConfig) : handlerConfig); /* * 如果是CORS预检请求,则使用当前的config配置构建一个PreFlightHandler,并且使用此前的 * chain中的拦截器链构建一个新的HandlerExecutionChain对象返回 * * 否则,在当前chain的拦截器链的头部添加一个CorsInterceptor, * 用于后面执行拦截器时处理CORS请求,最后还是返回当前chain对象 */ executionChain = getCorsHandlerExecutionChain(request, executionChain, config); } return executionChain; }

4.2.1.1 getHandlerInternal获取内部处理器

  查找给定请求的handler处理程序,如果未找到特定请求,则返回null,随后将使用默认handler。
  对于CORS的预检请求,此方法从"Access-Control-Request-Method“头部字段中获取实际请求所使用的HTTP方法,从”Access-Control-Request-Headers"头部字段中获取实际请求所携带的自定义首部字段,将会获取此方法的CORS配置进行匹配。
  该方法是一个抽象方法,留给子类实现,并且比较复杂,需要详细讲解,我们主要看RequestMappingHandlerMapping的实现。RequestMappingHandlerMapping的getHandlerInternal在它的父类RequestMappingInfoHandlerMapping和父类的父类AbstractHandlerMethodMapping中均有实现。
  RequestMappingHandlerMapping的uml类图如下:
Spring MVC 请求执行流程的源码深度解析【两万字】_第5张图片

/**
 * HandlerMapping中的常量
 * 

* HttpServletRequest属性的名称,该属性包含适用于映射处理程序的可产生MediaType的集合。 */ String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes"; /** * RequestMappingInfoHandlerMapping的方法 * * @param request 当前 request * @return 内部的handler处理器对象,实际上就是HandlerMethod */ @Override protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception { //移除该属性 request.removeAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE); try { //调用父类AbstractHandlerMethodMapping的实现方法 return super.getHandlerInternal(request); } finally { ProducesRequestCondition.clearMediaTypesAttribute(request); } } /** * HttpServletRequest属性的名称,该属性包含用于查找匹配handler的路径 */ String LOOKUP_PATH = HandlerMapping.class.getName() + ".lookupPath"; /** * AbstractHandlerMethodMapping的方法 *

* 查找给定请求的HandlerMethod */ @Override protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception { //获取请求路径,将会通过该路径匹配handler String lookupPath = getUrlPathHelper().getLookupPathForRequest(request); //设置到当前request的属性中 request.setAttribute(LOOKUP_PATH, lookupPath); this.mappingRegistry.acquireReadLock(); try { //调用lookupHandlerMethod()方法根据给定的path和request尝试查找HandlerMethod HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request); return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null); } finally { this.mappingRegistry.releaseReadLock(); } }

4.2.1.1.1 lookupHandlerMethod查找匹配的HandlerMethod

  该方法将查找查找当前请求的最佳匹配handler的HandlerMethod,如果找到多个匹配项,则选择最佳匹配项。将会匹配path、Method、params、headers、consumes、produces、patterns、customCondition等等条件。
  如果是匹配的CORS预检请求,将返回一个特殊的空HandlerMethod

/**
 * AbstractHandlerMethodMapping的属性
 * 

* 用于CORS预检请求的特殊HandlerMethod * 其内部的bean是一个EmptyHandler,方法也是该bean的handle方法 * 执行该方法将抛出异常 */ private static final HandlerMethod PREFLIGHT_AMBIGUOUS_MATCH = new HandlerMethod(new EmptyHandler(), ClassUtils.getMethod(EmptyHandler.class, "handle")); /** * HandlerMapping的属性 * 最佳匹配的handler的HttpServletRequest属性的名称。 */ String BEST_MATCHING_HANDLER_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingHandler"; /** * AbstractHandlerMethodMapping的方法 *

* 查找当前请求的最佳匹配handler的方法,如果找到多个匹配项,则选择最佳匹配项。 * * @param lookupPath 当前servlet映射内的映射查找路径 * @param request 当前 request * @return 最佳匹配的HandlerMethod;如果不匹配,则返回null */ @Nullable protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception { //匹配的结果集合 List<Match> matches = new ArrayList<>(); /* * 根据lookupPath取注册表中查找所有匹配的RequestMappingInfo,这里并没有模式匹配,而是精确匹配 * 这里只匹配路径,不会匹配其他的条件,因此可能找到多个RequestMappingInfo,比如它们之间使用请求方法来区分时 */ List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath); //对找到的精确路径匹配的RequestMappingInfo继续应用其他属性匹配 if (directPathMatches != null) { //比如Method、params、headers、consumes、produces、patterns、customCondition等等 //如果这些条件都匹配,那么对应的RequestMappingInfo和handlerMethod会被包装为一个Match对象存入matches集合 addMatchingMappings(directPathMatches, matches, request); } /* * 如果没有任何精确路径匹配,那么遍历所有的映射,再次尝试匹配 * 这里面会尝试模式匹配 */ if (matches.isEmpty()) { //该方法中再次匹配时实际上会使用模式匹配 addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request); } /* * 如果找到了至少一个匹配,那么查找最佳匹配 */ if (!matches.isEmpty()) { //默认第一个是最佳匹配 Match bestMatch = matches.get(0); //如果总匹配数大于1个,那么查找最佳匹配 if (matches.size() > 1) { //匹配比较器 Comparator<Match> comparator = new MatchComparator(getMappingComparator(request)); //比较并排序,最终,最佳匹配将位于集合首位 matches.sort(comparator); //获取第一个元素,就是最佳匹配的元素 bestMatch = matches.get(0); if (logger.isTraceEnabled()) { logger.trace(matches.size() + " matching mappings: " + matches); } //如果是CORS的预检请求,那么固定返回一个空的HandlerMethod实例 if (CorsUtils.isPreFlightRequest(request)) { return PREFLIGHT_AMBIGUOUS_MATCH; } //第二匹配 Match secondBestMatch = matches.get(1); //如果第二匹配和第一匹配的比较结果是相等的,这说明没找到最佳匹配,或者说有两个最佳匹配 //那么将抛出异常 if (comparator.compare(bestMatch, secondBestMatch) == 0) { Method m1 = bestMatch.handlerMethod.getMethod(); Method m2 = secondBestMatch.handlerMethod.getMethod(); String uri = request.getRequestURI(); throw new IllegalStateException( "Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}"); } } //将最佳匹配的HandlerMethod的存入到当前request的属性中公开 //属性名为org.springframework.web.servlet.HandlerMapping.bestMatchingHandler request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod); //在请求中继续公开lookupPath、URI模板变量,矩阵变量和可使用的媒体类型,将它们作为当前请求的属性 handleMatch(bestMatch.mapping, lookupPath, request); //返回最佳匹配的handlerMethod return bestMatch.handlerMethod; } else { //如果没有匹配任何一个,那么会放宽条件再次尝试全部匹配 //如果存在部分匹配的handlerMethod,那么会尝试使用该handlerMethod,否则将抛出异常或者返回null return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request); } }

4.2.1.2 CORS配置

  如果当前handler实现了CorsConfigurationSource接口或者具有全局CORS配置(比如通过标签或者WebConfig#addCorsMappings),或者说当前handler属于HandlerMethod,并且存在对于当前HandlerMethod的局部CORS配置(比如通过@CrossOrigin注解),或者说当前请求是CORS预检请求。满足以上三个条件之一,那么就可以对当前请求进行CORS配置,以用于后续CORS处理
  将会对全局CORS配置和局部CORS配置合并,对于只能接受单个值的属性(如allowCredentials和 maxAge),本地值将覆盖全局值,对于其他接受多个值的属性,比如origins,则全局和本地的CORS配置值会组合在一起(java中是list集合)!
  最后会调用getCorsHandlerExecutionChain配置CORS。

4.2.1.2.1 hasCorsConfigurationSource是否具有CORS配置

  该方法判断是否具有全局CORS配置或者对于当前HandlerMethod的局部CORS配置。如果有的话,那么当前请求就可以执行CORS配置处理。

/**
 * AbstractHandlerMethodMapping的方法
 * 是否具有适用于当前handler的CORS配置
 *
 * @param handler 当前handler
 * @return true——有
 */
@Override
protected boolean hasCorsConfigurationSource(Object handler) {
    //调用父类的同名方法校验
    return super.hasCorsConfigurationSource(handler) ||
            //当前handler属于HandlerMethod,并且存在对于当前HandlerMethod的局部CORS配置(比如通过@CrossOrigin注解),那么返回true
            (handler instanceof HandlerMethod && this.mappingRegistry.getCorsConfiguration((HandlerMethod) handler) != null);
}


/**
 1. AbstractHandlerMapping的方法
 2. 

3. 是否具有适用于当前handler的CORS配置 */ protected boolean hasCorsConfigurationSource(Object handler) { if (handler instanceof HandlerExecutionChain) { handler = ((HandlerExecutionChain) handler).getHandler(); } //如果handler属于CorsConfigurationSource类型或者具有全局的CORS配置,那么返回true return (handler instanceof CorsConfigurationSource || this.corsConfigurationSource != null); }

4.2.1.2.2 getCorsHandlerExecutionChain配置CORS

  该方法将会判断:

  1. 如果是CORS预检请求,则使用当前的config配置构建一个PreFlightHandler,并且使用此前的chain中的拦截器链构建一个新的HandlerExecutionChain对象返回。后面执行handler时,实际上是进行CORS校验
  2. 否则,在当前chain的拦截器链的头部添加一个CorsInterceptor,用于后面执行拦截器时校验CORS配置,最后还是返回当前chain对象。
/**
 * AbstractHandlerMapping的方法
 * 

* 获取具有CORS配置的HandlerExecutionChain * * @param request 当前request * @param chain 当前的handler chain * @param config 适用的CORS配置(可能为null) * @return 配置了CORS的HandlerExecutionChain */ protected HandlerExecutionChain getCorsHandlerExecutionChain(HttpServletRequest request, HandlerExecutionChain chain, @Nullable CorsConfiguration config) { /* * 如果是CORS预检请求,则使用当前的config配置构建一个PreFlightHandler, * 并且使用此前的chain中的拦截器链构建一个新的HandlerExecutionChain对象返回。 */ if (CorsUtils.isPreFlightRequest(request)) { HandlerInterceptor[] interceptors = chain.getInterceptors(); chain = new HandlerExecutionChain(new AbstractHandlerMapping.PreFlightHandler(config), interceptors); } else { /* * 否则,在当前chain的拦截器链的头部添加一个CorsInterceptor,用于后面执行拦截器时处理CORS请求,最后还是返回当前chain对象。 */ chain.addInterceptor(0, new AbstractHandlerMapping.CorsInterceptor(config)); } return chain; }

4.3 noHandlerFound没找到handler的处理

  找不到handler的时候,则调用该方法,通常是设置404的HTTP响应状态码,但可能抛出NoHandlerFoundException异常,需要手动设置DispatcherServlet的throwExceptionIfNoHandlerFound属性为true

/**
 * DispatcherServlet的属性
 * 

* 如果未找到处理该请求的handler,则抛出NoHandlerFoundException,默认不抛出 */ private boolean throwExceptionIfNoHandlerFound = false; /** * DispatcherServlet的方法 *

* 找不到handler->设置适当的HTTP响应状态码。 * * @param request 当前 HTTP request * @param response 当前 HTTP response */ protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception { if (pageNotFoundLogger.isWarnEnabled()) { pageNotFoundLogger.warn("No mapping for " + request.getMethod() + " " + getRequestUri(request)); } //如果该属性为true,则直接抛出NoHandlerFoundException异常 if (this.throwExceptionIfNoHandlerFound) { throw new NoHandlerFoundException(request.getMethod(), getRequestUri(request), new ServletServerHttpRequest(request).getHeaders()); } else { //否则,直接设置404相应状态吗,这是常见逻辑 response.sendError(HttpServletResponse.SC_NOT_FOUND); } }

4.4 getHandlerAdapter获取HandlerAdapter

  该方法根据上面找到的handler获取对应的处理器适配器——HandlerAdapter
  通过遍历handlerAdapters,依次调用每个HandlerAdapter的supports方法,只要有一个HandlerAdapter的supports方法返回true,就返回该HandlerAdapter,就不会继续向后查找,如果在所有的HandlerAdapter中都没找到,则直接抛出ServletException

/**
 * DispatcherServlet的属性
 * 

* 此servlet使用的HandlerAdapter列表 */ @Nullable private List<HandlerAdapter> handlerAdapters; /** * DispatcherServlet的方法 *

* 返回此handler对象的HandlerAdapter。 * * @param handler 用来查找适配器的handler对象,比如HandlerMethod */ protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { if (this.handlerAdapters != null) { //遍历所有的HandlerAdapter,依次调用supports方法尝试判断是否支持 //如果某个返回结果为true,则说明该adapter支持该handler,则返回该adapter,不继续向后查找 for (HandlerAdapter adapter : this.handlerAdapters) { if (adapter.supports(handler)) { return adapter; } } } //如果找不到处理程序的HandlerAdapter,则直接抛出ServletException异常 throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler"); }

4.4.1 adapter#supports是否支持handler

  不同的handler需要不同的HandlerAdapter来适配,对于HandlerMethod,它的适配器就是RequestMappingHandlerAdapter
  RequestMappingHandlerAdapter将会被默认配置,它的uml类图如下:
Spring MVC 请求执行流程的源码深度解析【两万字】_第6张图片
  它的supports方法是由它的父类AbstractHandlerMethodAdapter实现的,非常的简单,只要此handler是HandlerMethod类型那么就返回true,表示可以处理

/**
 * AbstractHandlerMethodAdapter的方法
 * 

* 此实现期望handler的类型为HandlerMethod。 * * @param handler 要检查的handler实例 * @return 此适配器是否可以适应给定的handler */ @Override public final boolean supports(Object handler) { //如果此handler是HandlerMethod类型并且,supportsInternal方法返回true(默认就返回true) //则此此适配器可以适应给定的handler,将返回true return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler)); } /** * RequestMappingHandlerAdapter的方法 *

* 始终返回true */ @Override protected boolean supportsInternal(HandlerMethod handlerMethod) { return true; }

4.5 applyPreHandle执行拦截器的预处理

  在实际执行请求之前,应用拦截器链中的此前找到的所有HandlerInterceptor拦截器的PreHandle预处理方法,这里的拦截器是Spring MVC的拦截器。如果执行链应该继续下一个拦截器或处理程序本身,则返回true。否则,DispatcherServlet假定此拦截器已经处理了响应本身,随后将直接返回,不会继续执行doDispatch方法。
  我们此前知道,如果存在CORS配置并且不是CORS预检请求,将会拦截器链的头部添加一个CorsInterceptor拦截器,此时就会执行该拦截器以校验CORS,如果不通过,那么preHandle返回false,后续handler方法也就不再执行,避免了由于CORS不通过但是业务代码执行的问题!而对于预检请求,它虽然没有添加这个拦截器,但是它的PreFlightHandler的handler仅仅调用DefaultCorsProcessor的processRequest方法而没有处理器方法的调用,这样就同样避免了业务代码的执行。注意,如果没有配置CorsConfiguration(无论是全局的还是局部的),或者请求不是预检请求,那么即使是CORS请求到来时,业务代码会按照正常请求流程来执行,也就不会有CORS校验,虽然浏览器不会呈现最终结果!

  每一个拦截器的preHandle方法返回true,则会使用interceptorIndex变量记录该拦截器在拦截器链中的下标索引。
  需要注意到,如果某个拦截器的preHandle方法返回false,则执行triggerAfterCompletion方法,triggerAfterCompletion方法将会对拦截器链中之前通过的(即preHandle方法返回true)的拦截器倒序的执行afterCompletion方法,最后返回false

//HandlerExecutionChain的属性

/**
 * 当前应用的拦截器在拦截器链中的索引位置
 */
private int interceptorIndex = -1;

/**
 * 通过构造器指定的拦截器数组,要应用的拦截器链数组
 */
@Nullable
private HandlerInterceptor[] interceptors;

/**
 * initInterceptorList方法初始化的拦截器集合
 */
@Nullable
private List<HandlerInterceptor> interceptorList;

/**
 * HandlerExecutionChain的方法
 * 

* 应用此Chain中注册的拦截器的preHandle方法。 * * @return 如果执行链应该继续下一个拦截器或处理程序本身,则返回true。否则,DispatcherServlet假定此拦截器已经处理了响应本身,随后将直接返回。 */ boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception { //获取此前找到的拦截器链 HandlerInterceptor[] interceptors = getInterceptors(); if (!ObjectUtils.isEmpty(interceptors)) { for (int i = 0; i < interceptors.length; i++) { HandlerInterceptor interceptor = interceptors[i]; //执行preHandle方法,如果某个拦截器的preHandle方法返回false则执行triggerAfterCompletion方法,随后返回false if (!interceptor.preHandle(request, response, this.handler)) { //执行triggerAfterCompletion方法 //即对拦截器链中之前通过的拦截器执行afterCompletion方法 triggerAfterCompletion(request, response, null); return false; } //改变当前执行的拦截器索引 this.interceptorIndex = i; } } //所有的拦截器都通过,那么返回true return true; }

4.6 handle实际处理请求

  如果此前的handler和HandlerAdapter都找到了,并且通过了所有拦截器的preHandle校验,那么通过HandlerAdapter使用给定的handler实际处理请求,返回一个ModelAndView结果对象。不同的handler的实际工作流程差别非常大,其中还包括序列化、数据绑定、检验、返回值处理等等步骤。
  对于HandlerMethod来说,它是通过RequestMappingHandlerAdapter来调用的,最终目的就会尝试执行对应的@ReuqestMapping方法,也就是业务逻辑
  我们直接看RequestMappingHandlerAdapter的handle方法的逻辑就行了,它的handle方法是由父类AbstractHandlerMethodAdapter实现的!

/**
 1. AbstractHandlerMethodAdapter的方法
 2. 

3. 使用给定的handler来处理此请求,不同的handler所需的工作流程可能相差很大。 4. 此实现期望handler为HandlerMethod类型。 5. 6. @return 具有视图名称和所需模型数据的ModelAndView对象;如果直接处理了该请求(比如application/json),则返回null */ @Override @Nullable public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //委托另一个handleInternal方法处理请求,该方法由子类RequestMappingHandlerAdapter实现 return handleInternal(request, response, (HandlerMethod) handler); }

  内部委托另一个handleInternal方法处理请求,该方法由子类RequestMappingHandlerAdapter实现。

  1. 首先会调用checkRequest方法,检查是否是支持的请求方法,检查是否必须session,如果不满足条件则抛出异常。
  2. 随后调用invokeHandlerMethod方法真正的处理请求,执行handlerMethod方法,返回ModelAndView
  3. 如果自己没有设置Cache-Control响应头,那么帮助处理。
/**
 * RequestMappingHandlerAdapter的方法
 * 

* 使用给定的HandlerMethod处理请求,真正的处理请求的入口 * * @param request 当前request * @param response 当前response * @param handlerMethod 当前handlerMethod * @return 具有视图名称和所需模型数据的ModelAndView对象;如果直接处理了请求(比如application/json),则为null */ @Override protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ModelAndView mav; //检查是否是支持的请求方法,检查是否必须session,如果不满足条件则抛出异常 checkRequest(request); //如果需要,在同步块中执行invokeHandlerMethod,默认为false if (this.synchronizeOnSession) { HttpSession session = request.getSession(false); if (session != null) { Object mutex = WebUtils.getSessionMutex(session); synchronized (mutex) { mav = invokeHandlerMethod(request, response, handlerMethod); } } else { // No HttpSession available -> no mutex necessary mav = invokeHandlerMethod(request, response, handlerMethod); } } else { //完全不需要session同步,则调用直接调用invokeHandlerMethod,这是常见逻辑 mav = invokeHandlerMethod(request, response, handlerMethod); } //如果自己没有设置Cache-Control响应头,那么帮助处理,一般都没有 if (!response.containsHeader(HEADER_CACHE_CONTROL)) { if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) { applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers); } else { prepareResponse(response); } } //返回ModelAndView return mav; }

4.6.1 invokeHandlerMethod执行HandlerMethod

  handleInternal方法中,最终是通过执行invokeHandlerMethod方法来执行HandlerMethod并处理请求的。该方法也是这个适配器中最重要的方法,它用于调用我们的目标方法,同时会进行数据装换、绑定、检验、设置返回值等工作。
  最终返回一个ModelAndView对象,可用于视图解析,因此对于不需要视图解析的请求,比如application/json,则返回的是null

/**
 * RequestMappingHandlerAdapter的方法
 * 

* 执行HandlerMethod,尝试返回ModelAndView以用于视图解析 */ @Nullable protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { //Request和Response包装为一个ServletWebRequest ServletWebRequest webRequest = new ServletWebRequest(request, response); try { /* * 1 获取WebDataBinderFactory,用于为当前请求创建一个 WebDataBinder实例,用于参数绑定 */ WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod); /* * 2 获取ModelFactory,用于在控制器方法调用之前协助初始化Model,并在调用之后协助对其进行更新。 */ ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory); /* * 3 根据给定的HandlerMethod定义创建一个ServletInvocableHandlerMethod,具有执行HandlerMethod的能力。 * 还扩展了InvocableHandlerMethod的能力,使其能够通过注册的HandlerMethodReturnValueHandler处理返回值, * 并且还支持基于方法级别的@ResponseStatus注解设置响应状态。 */ ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod); /* * 4 设置各种要用到的工具类属性 */ //设置参数解析器 if (this.argumentResolvers != null) { invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers); } //设置返回值解析器 if (this.returnValueHandlers != null) { invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers); } //设置DataBinderFactory invocableMethod.setDataBinderFactory(binderFactory); //设置ParameterNameDiscoverer invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer); /* * 4 创建ModelAndViewContainer并初始化Model对象的属性 */ ModelAndViewContainer mavContainer = new ModelAndViewContainer(); //将上个请求传递的FlashMap的值设置到mavContainer mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request)); /* * 初始化Model对象,并填充模型属性 * 1、从request中检索类上的@SessionAttributes的属性名对应的值并设置到mavContainer(只有没有的key才会存储) * 2、调用@ModelAttribute 注解标注的方法,并尝试将返回结果设置到mavContainer(只有没有的key才会存储) * 3、查找方法中标注了@ModelAttribute注解的方法参数并在@SessionAttributes中根据name 和type匹配查找属性名 * 如果找到了属性名,则从request中检索该属性名的属性值并设置到mavContainer(只有没有的key才会存储) */ modelFactory.initModel(webRequest, mavContainer, invocableMethod); /* * 设置默认model中的数据在渲染和重定向中是否都可使用,默认可以,通过ignoreDefaultModelOnRedirect参数关闭 * 或者如果控制器方法中存在RedirectAttributes参数,那么将使用该参数来确定在重定向中使用的参数 */ mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect); /* * 5 异步请求处理相关,后面单独讲 */ AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response); asyncWebRequest.setTimeout(this.asyncRequestTimeout); WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); asyncManager.setTaskExecutor(this.taskExecutor); asyncManager.setAsyncWebRequest(asyncWebRequest); asyncManager.registerCallableInterceptors(this.callableInterceptors); asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors); if (asyncManager.hasConcurrentResult()) { Object result = asyncManager.getConcurrentResult(); mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0]; asyncManager.clearConcurrentResult(); LogFormatUtils.traceDebug(logger, traceOn -> { String formatted = LogFormatUtils.formatValue(result, !traceOn); return "Resume with async result [" + formatted + "]"; }); invocableMethod = invocableMethod.wrapConcurrentResult(result); } /* * 6 核心方法,真正的执行handlerMethod方法并且进行处理,比如参数封装、校验、返回值处理成View对象或者JSON数据等等 * 执行的结果会被放在mavContainer中,可能只有Model或者只有VIew或者都没有(比如application/json请求) */ invocableMethod.invokeAndHandle(webRequest, mavContainer); if (asyncManager.isConcurrentHandlingStarted()) { return null; } /* * 7 根据mavContainer、modelFactory、webRequest内容获取ModelAndView对象 * ModelAndView对象中包含了Model、View、HttpStatus */ return getModelAndView(mavContainer, modelFactory, webRequest); } finally { //在finally块中,发出请求已完成的信号。 //将会执行所有请求销毁回调,并更新在请求处理期间已访问的会话属性,最后将requestActive标志改为false webRequest.requestCompleted(); } }

4.6.1.1 invokeAndHandle执行请求并处理

  调用控制器方法,并通过已配置的HandlerMethodReturnValueHandler其中之一处理返回值。它用于调用我们的目标方法,同时会进行数据装换、绑定、检验、返回值处理成View对象或者JSON数据等等工作。
  执行的结果会被放在mavContainer中,可能只有Model或者只有VIew或者都没有(比如application/json请求)

/**
 1. ServletInvocableHandlerMethod的方法
 2. 

3. 调用控制器方法并通过已配置的HandlerMethodReturnValueHandlers其中之一处理返回值。 4. 5. @param webRequest 当前 request 6. @param mavContainer 此请求的ModelAndViewContainer 7. @param providedArgs 按类型匹配的“给定”参数(未解析) */ public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { /* * 1 在给定请求的上下文中解析方法参数,后使用解析出来的参数值调用该方法,返回方法执行返回的原始值。 * 这一步的执行逻辑是:参数封装、校验——@Validated校验(如果存在)——调用真实方法——返回原始执行结果 */ Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); /* * 尝试设置响应状态码,这是为了支持方法上的@ResponseStatus注解 */ setResponseStatus(webRequest); //如果没有返回值 if (returnValue == null) { //如果请求已被处理 if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) { disableContentCachingIfNecessary(webRequest); //设置requestHandled属性为true,表示请求已被处理,方法结束 //实际上,如果具有void返回类型(或null返回值)的方法也具有ServletResponse、 //OutputStream参数或@ResponseStatus注解,那么requestHandled属性会被设置为true mavContainer.setRequestHandled(true); return; } //否则,如果存在responseStatusReason属性,即@ResponseStatus存在reason } else if (StringUtils.hasText(getResponseStatusReason())) { //设置requestHandled属性为true,表示请求已被处理,方法结束 mavContainer.setRequestHandled(true); return; } //到这里,表示请求未被处理完成,后面还要继续处理。一般没有@ResponseStatus注解并且方法正常完成的请求都会走这里 mavContainer.setRequestHandled(false); Assert.state(this.returnValueHandlers != null, "No return value handlers"); try { /* * 2 使用HandlerMethodReturnValueHandlerComposite中的HandlerMethodReturnValueHandler来处理返回值 * * 通过向Model模型添加属性并设置View视图或调用ModelAndViewContainer.setRequestHandled * 方法设置为true来处理给定的返回值,以指示已直接处理响应,后续不需要处理。 */ this.returnValueHandlers.handleReturnValue( returnValue, getReturnValueType(returnValue), mavContainer, webRequest); } catch (Exception ex) { if (logger.isTraceEnabled()) { logger.trace(formatErrorForReturnValue(returnValue), ex); } throw ex; } }

4.6.1.1.1 invokeForRequest执行请求

  首先在给定请求的上下文中解析方法参数,后使用解析出来的参数值调用该方法,返回方法执行返回的原始值。这一步的执行逻辑是:参数封装、校验——@Validated校验(如果存在)——调用真实方法——返回原始执行结果

  1. 首先调用getMethodArgumentValues方法获取方法参数,并为每个参数选择适当的参数解析器,解析、获取方法参数,最后按照参数顺序返回解析后的参数数组,Spring MVC的入参都是通过HandlerMethodArgumentResolver体系来解析的,其提供了几十个该类型的入参解析器。该过程中会进行依赖项的校验,比如某个参数有@RequestParam注解并且required=true,但是如果该参数不存在,那么就会抛出异常。该过程中还会执行@InitBinder方法
    1. 对于常见的@RequestBody注解标注的方法参数,将会使用RequestResponseBodyMethodProcessor这个解析器来解析,实际上是依赖其内部的messageConverters来执行解析的。对于application/json请求,默认将会使用MappingJackson2HttpMessageConverter进行JSON反序列化
  2. 随后调用doInvoke方法,根据解析出来的参数数组反射调用目标方法,返回原始结果,如果该方法还应用了@Validated校验,那么此时又会进行Validated校验规则,实际上此时的类对象是一个代理对象,它的控制器方法已被AOP增强,在方法实际调用之前将会应用Validated校验方法
/**
 * InvocableHandlerMethod的方法
 * 

* 在给定请求的上下文中解析方法参数,后使用解析出来的参数值调用该方法,返回原始结果 *

* 参数值通常通过HandlerMethodArgumentResolvers进行解析,但是,providedArgs参数可以提供要直接使用的参数值,即无需参数解析。 * 提供的参数值的示例包括WebDataBinder,SessionStatus或引发的异常实例,在参数解析之前检查提供的参数值。 * * @param request 当前 request * @param mavContainer 此请求的ModelAndViewContainer * @param providedArgs 按类型匹配的“给定”参数,未解析,可以不传 * @return 调用的方法返回的原始值 */ @Nullable public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { /* * 1 获取方法参数,并为每个参数选择适当的参数解析器,解析、获取方法参数,最后按照参数顺序返回解析后的参数数组 * * Spring MVC的入参都是通过HandlerMethodArgumentResolver体系来解析的,其提供了几十个该类型的入参解析器 * 该过程中会进行依赖项的校验,比如某个参数有@RequestParam注解并且required=true,但是如果该参数不存在,那么就会抛出异常 * 该过程中还会执行@InitBinder方法 * * 对于常见的@RequestBody标注的方法参数,将会使用RequestResponseBodyMethodProcessor这个解析器来解析 * 实际上是依赖其内部的messageConverters来执行解析的。 * 对于application/json请求,默认将会使用MappingJackson2HttpMessageConverter进行JSON反序列化 */ Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); if (logger.isTraceEnabled()) { logger.trace("Arguments: " + Arrays.toString(args)); } /* * 2 根据解析出来的参数数组反射调用目标方法,返回原始结果 * * 如果该方法还应用了@Validated校验,那么此时又会进行Validated校验规则,实际上此时的类对象是一个代理对象 * 它的控制器方法已被AOP增强,在方法实际调用之前将会应用Validated校验方法 */ return doInvoke(args); }

4.6.1.1.2 handleReturnValue处理返回值

  遍历已注册的HandlerMethodReturnValueHandler并调用支持该返回值的那个处理器来处理返回值
  对于常见的@ResponseBody标注的方法,将会使用RequestResponseBodyMethodProcessor这个解析器来解析,实际上是依赖其内部的messageConverters来执行解析的,对于application/json请求,默认将会使用MappingJackson2HttpMessageConverter进行JSON序列化,并将JSON字符串结果设置到response中,随后会调用mavContainer.setRequestHandled方法设置为true来指示已直接处理响应,后续无需解析、处理视图。

/**
 * HandlerMethodReturnValueHandlerComposite的方法
 * 

* 遍历已注册的HandlerMethodReturnValueHandlers并调用支持该返回值和返回值的那个处理器来处理返回值 */ @Override public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { /* * 1 根据返回的原始返回值和类型选择可以处理的HandlerMethodReturnValueHandler * * 对于常见的@ResponseBody标注的方法,将会使用RequestResponseBodyMethodProcessor这个解析器来解析 * 实际上是依赖其内部的messageConverters来执行解析的 * 对于application/json请求,默认将会使用MappingJackson2HttpMessageConverter进行JSON序列化, * 并将JSON字符串结果设置到response中 */ HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType); if (handler == null) { //没找到就抛出异常 throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName()); } /* * 2 通过HandlerMethodReturnValueHandler处理返回值 * 通过向Model模型添加属性并向mavContainer设置View视图 * 或调用mavContainer.setRequestHandled方法设置为true来指示已直接处理响应,通常application/json请求会执行该操作 */ handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest); }

4.6.1.2 getModelAndView获取ModelAndView

  获取ModelAndView,用以渲染视图,如果不需要渲染视图,则返回null,此情况通常是已将返回的数据写入response中了,比如application/json请求。
  这里的ModelAndView中通常包含的是model和viewNamemodel实际上是一个ModelMap,保存着一系列的数据,而在前后端不分离的项目中,controller方法所要跳转时返回的String字符串就是视图名,比如"/xx/xx"、"redirect:/xx"、"forward:/xx"。随后将会根据视图名解析为View视图对象。

/**
 1. RequestMappingHandlerAdapter的方法
 2. 

3. 获取ModelAndView,用以渲染视图,如果不需要渲染视图 4. 则返回null,此情况通常是已将返回的数据写入response中了,比如application/json请求 */ @Nullable private ModelAndView getModelAndView(ModelAndViewContainer mavContainer, ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception { /* * 1 更新Model数据 * 也就是将列为@SessionAttributes的Model属性提升到session会话级别,还可能添加BindingResult属性。 */ modelFactory.updateModel(webRequest, mavContainer); //如果此请求已被处理完毕,比如application/json请求返回的JSON数据已被写入到了response中 //那么不需要返回ModelAndView,也不需要渲染视图,返回null即可 if (mavContainer.isRequestHandled()) { return null; } /* * 2 设置ModelAndView,用于视图渲染 */ //获取model ModelMap model = mavContainer.getModel(); /* * 创建一个ModelAndView对象,这里的view就是viewName,如果view是String类型,则算作视图名,这通常就是视图资源路径 * 在前后端不分离的项目中,controller方法返回的String字符串就是视图名,比如"/xx/xx"、"redirect:/xx"、"forward:/xx" */ ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus()); //判断是否不是视图名,如果不是视图名称,则直接设置为视图对象 if (!mavContainer.isViewReference()) { mav.setView((View) mavContainer.getView()); } /* * 3 重定向属性传递的处理 */ //如果model属于RedirectAttributes类型,即重定向,使用"redirect:"前缀进行重定向的都不是 if (model instanceof RedirectAttributes) { //获取flashAttributes,用于重定向时参数的传递 Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes(); // HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); if (request != null) { //存入OutputFlashMap中,这是为后续请求保存的属性 RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes); } } return mav; }

4.7 processDispatchResult处理异常或渲染视图

  处理执行过程中的异常,或者根据返回的ModelAndView结果渲染视图,最后倒序执行拦截器链中所有拦截器的afterCompletion处理方法。

  1. 如果此前的执行中抛出了异常,那么在这里处理执行过程中的异常,将会通过注册的HandlerExceptionResolvers确定处理错误的ModelAndView
  2. 随后根据的ModelAndView结果渲染视图。首先可能会调用viewResolvers中的全部ViewResolver尝试将给定的视图名称解析为一个View对象(待呈现),随后调用View本身的render方法,真正的执行渲染的工作。
  3. 最后倒序执行拦截器链中所有拦截器的afterCompletion处理方法。
/**
 1. DispatcherServlet的方法
 2. 

3. 处理执行过程中的异常,或者根据返回的ModelAndView结果渲染视图 4. 最后倒序执行拦截器链中所有拦截器的afterCompletion处理方法 5. 6. @param request 当前request 7. @param response 当前response 8. @param mappedHandler 当前mappedHandler 9. @param mv 执行返回的ModelAndView,可以为null 10. @param exception 此前处理过程中抛出的异常,可以为null */ private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception { //是否设置了异常视图 boolean errorView = false; /* * 1 如果此前抛出了异常,则处理异常 */ if (exception != null) { //如果属于ModelAndViewDefiningException异常,该异常表示此请求应转发给具有特定model的特定view //这个异常通常是在业务代码中手动抛出的,用于将请求转发到错误视图 if (exception instanceof ModelAndViewDefiningException) { logger.debug("ModelAndViewDefiningException encountered", exception); mv = ((ModelAndViewDefiningException) exception).getModelAndView(); } else { //其他的异常 //获取handler Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null); /* * 通过注册的HandlerExceptionResolvers确定处理错误的ModelAndView * * 尝试执行配置的全部HandlerExceptionResolver的resolveException方法,返回一个ModelAndView的结果 * 如果某个异常解析器的返回值不为null,则基于该ModelAndView返回结果,否则将抛出参数中的异常 */ mv = processHandlerException(request, response, handler, exception); //如果确定了处理错误的ModelAndView,则errorView为true errorView = (mv != null); } } /* * 2 渲染视图 */ //判断是否需要渲染视图,即mv不为null并且内部的View不为null并且内部的Model也不为空 if (mv != null && !mv.wasCleared()) { /* * 渲染给定的ModelAndView,即渲染视图 * 这是处理请求的最后阶段,它可能涉及按视图名称来解析视图。 */ 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) { /* * 3 倒序应用拦截器链中的此前找到的所有拦截器的afterCompletion处理方法 * * 某个afterCompletion方法在执行中即使抛出异常,也会被catch掉,不会影响其他方法的执行 */ mappedHandler.triggerAfterCompletion(request, response, null); } }

4.7.1 render渲染视图

  如果返回的ModelAndView不为null,并且内部的View不为null,并且内部的Model也不为空,那么就渲染给定的ModelAndView。这是处理请求的最后阶段,它可能涉及按视图名称解析为视图对象的逻辑。

  1. 如果视图是视图名,则调用viewResolvers中的全部ViewResolver尝试将给定的视图名称解析为一个View对象(待呈现)
  2. 随后调用View的render方法,真正的执行渲染的工作,不同的View实现执行不同的渲染逻辑,常见的InternalResourceView用于渲染JSP视图
/**
 * DispatcherServlet的方法
 * 

* 渲染给定的ModelAndView。这是处理请求的最后阶段,它可能涉及按名称解析视图。 * * @param mv 要渲染的ModelAndView * @param request 当前的HTTP Servlet请求 * @param response 当前的HTTP Servlet响应 */ protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception { //确定请求的语言环境并将其应用于响应,用于国际化 Locale locale = (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale()); response.setLocale(locale); View view; //获取取视图名,如果view是String类型,那么view就是viewName,否则返回null。 String viewName = mv.getViewName(); //大多数情况都是viewName都不为null if (viewName != null) { /* * 1 调用viewResolvers中的全部ViewResolver尝试将给定的视图名称解析为一个View对象(待呈现)。 */ view = resolveViewName(viewName, mv.getModelInternal(), locale, request); if (view == null) { throw new ServletException("Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" + getServletName() + "'"); } } else { // No need to lookup: the ModelAndView object contains the actual View object. view = mv.getView(); if (view == null) { throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " + "View object in servlet with name '" + getServletName() + "'"); } } /* * 委托给View对象进行渲染,也就是说具体的渲染工作是由View对象本身来完成的 */ if (logger.isTraceEnabled()) { logger.trace("Rendering view [" + view + "] "); } try { //设置相应状态码 if (mv.getStatus() != null) { response.setStatus(mv.getStatus().value()); } /* * 2 调用View的render方法,真正的执行渲染的工作 * * 渲染工作由View自己来完成,不同的View实现执行不同的渲染逻辑,常见的InternalResourceView用于渲染JSP视图 */ view.render(mv.getModelInternal(), request, response); } catch (Exception ex) { if (logger.isDebugEnabled()) { logger.debug("Error rendering view [" + view + "]", ex); } throw ex; } }

4.7.1.1 resolveViewName解析视图名

  遍历viewResolvers,依次调用每一个ViewResolver的resolveViewName方法,尝试将给定的视图名称解析为一个View视图对象(待呈现)。如果某个返回结果不为null,则使用该返回的View
  默认的viewResolver只有一个InternalResourceViewResolver,它将解析为viewNameInternalResourceView(请求转发或者包含)或者RedirectView(请求重定向)。这两个View类中的url属性就是viewName处理后的结果,也就是说viewName实际上就包含资源路径

/**
 * DispatcherServlet的属性
 * 

* 此servlet使用的ViewResolvers列表。 */ @Nullable private List<ViewResolver> viewResolvers; /** * DispatcherServlet的方法 *

* 调用viewResolvers中的全部ViewResolver尝试将给定的视图名称解析为一个View对象(待呈现)。 */ @Nullable protected View resolveViewName(String viewName, @Nullable Map<String, Object> model, Locale locale, HttpServletRequest request) throws Exception { if (this.viewResolvers != null) { /* * 遍历viewResolvers,依次调用每一个ViewResolver的resolveViewName方法,尝试获取View * 如果某个返回结果不为null,则使用该返回的View * * 默认的viewResolver只有一个InternalResourceViewResolver,它将解析为viewName为 * InternalResourceView或者RedirectView(具有重定向前缀"redirect:") */ for (ViewResolver viewResolver : this.viewResolvers) { View view = viewResolver.resolveViewName(viewName, locale); if (view != null) { return view; } } } //没有解析成功,返回null return null; }

4.7.1.2 view#render渲染视图

  真正的渲染视图是通过调用View自己的render方法来实现的。
  不同的View执行不同的渲染逻辑,常见的JSP等视图文件都是通过InternalResourceView/RedirectView来渲染的,而如果引入JSTL,则将使用JstlView,如果是FreeMarker视图则是通过FreeMarkerView渲染的。
Spring MVC 请求执行流程的源码深度解析【两万字】_第7张图片

/**
 * AbstractView的方法
 * 

* 渲染视图 */ @Override public void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception { if (logger.isDebugEnabled()) { logger.debug("View " + formatViewName() + ", model " + (model != null ? model : Collections.emptyMap()) + (this.staticAttributes.isEmpty() ? "" : ", static attributes " + this.staticAttributes)); } //创建包含动态值和静态属性的组合输出Map(绝不为null)。 Map<String, Object> mergedModel = createMergedOutputModel(model, request, response); //准备响应,主要是对于视图生成下载内容的处理,比如PDF视图 prepareResponse(request, response); /* * 委托renderMergedOutputModel方法进行实际渲染,该方法由子类提供的具体实现 */ renderMergedOutputModel(mergedModel, getRequestToExpose(request), response); }

4.7.1.2.1 renderMergedOutputModel执行渲染

  来看看子类InternalResourceView的renderMergedOutputModel方法,它用于执行请求转发和请求包含,最终会委托给RequestDispatcher的forward 或者include这两个原始方法。

/**
 * InternalResourceView的方法
 * 

* 根据指定的模型渲染内部资源,这包括将模型设置为请求属性。 */ @Override protected void renderMergedOutputModel( Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { //公开模型对象作为请求属性,即将model的属性设置到request中(通过request.setAttribute方法) exposeModelAsRequestAttributes(model, request); //公开每个渲染操作特有的帮助器,通常JSTL的渲染需要 exposeHelpers(request); // 准备渲染,并确定要转发到(或包括请求包含)的请求分派器路径,实际上此实现仅返回配置的url属性,也就是viewName处理后的结果。 String dispatcherPath = prepareForRendering(request, response); //获取目标资源(通常是JSP)的RequestDispatcher //实际上就是request.getRequestDispatcher(path)方法 RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath); if (rd == null) { throw new ServletException("Could not get RequestDispatcher for [" + getUrl() + "]: Check that the corresponding file exists within your web application archive!"); } //如果是包含请求或响应已经提交,则执行请求包含,否则执行请求转发。 if (useInclude(request, response)) { response.setContentType(getContentType()); if (logger.isDebugEnabled()) { logger.debug("Including [" + getUrl() + "]"); } //最终会执行RequestDispatcher#include这个原始方法 rd.include(request, response); } else { // Note: The forwarded resource is supposed to determine the content type itself. if (logger.isDebugEnabled()) { logger.debug("Forwarding to [" + getUrl() + "]"); } //最终会执行RequestDispatcher#forward这个原始方法 rd.forward(request, response); } }

  而对于RedirectView来说,最终会调用response.sendRedirect方法完成请求重定向。

5 总结

  上面我们详细介绍了Spring MVC处理请求的入口以及核心流程。
  源码入口就是FrameworkServlet#service方法,而核心的请求处理类就是DispatcherServlet

  在学习了源码之后,我们可以总结出Spring MVC处理请求大概流程:

  1. 请求到达DispatcherServlet。
  2. 确定当前请求的处理器——handler。通过遍历handlerMappings,依次调用每个HandlerMappinggetHandler方法获取HandlerExecutionChain对象,只要有一个HandlerMapping的getHandler方法返回值不为null,就返回该返回值,就不会继续向后查找,如果在所有的handlerMapping中都没找到,则返回null。对于RequestMappingHandlerMapping,他返回的handler就是HandlerMethod。该过程中还会进行CORS的配置,用于后续CORS请求的验证、处理。
    1. 找到的handler实际上是一个HandlerExecutionChain对象,它包含了真正的handler以及适用于当前handler的HandlerInterceptor拦截器链
    2. 如果没有找到任何handler(handler返回null),那么通常返回404响应码或者抛出NoHandlerFoundException异常
  3. 根据handler确定当前请求的处理器适配器——HandlerAdapter。通过遍历handlerAdapters,依次调用每个HandlerAdaptersupports方法,只要有一个HandlerAdapter的supports方法返回true,就返回该HandlerAdapter,就不会继续向后查找,如果在所有的HandlerAdapter中都没找到,则直接抛出ServletException。对于HandlerMethod,它的适配器就是RequestMappingHandlerAdapter
  4. 顺序应用拦截器链中所有拦截器的PreHandle预处理方法,只要有一个拦截器不通过,那么该请求就不会继续处理,而是对拦截器链中之前通过的(即此前的preHandle方法返回true)的拦截器倒序的执行afterCompletion方法,最后返回。如果都通过,那么继续后续处理。
  5. 通过HandlerAdapter来执行给定的handler,将返回一个ModelAndView结果对象(可能返回null)
    1. 不同的handler的实际工作流程差别非常大,对于HandlerMethod来说,它是通过RequestMappingHandlerAdapter来调用的,最终目的就会尝试执行对应的@ReuqestMapping方法,也就是业务逻辑,其中还包括参数反序列化、数据绑定、检验、返回值处理、序列化等等步骤。
    2. ModelAndView对象的Model部分是业务返回的模型数据,它是一个map,View部分为视图的逻辑视图名,即ViewName
    3. 返回的ModelAndView用以渲染视图,如果不需要渲染视图,则返回null,此情况通常是已将返回的数据序列化为JSON字符串写入response中了,比如application/json请求(通常是前后端分离的项目)
  6. handler正常处理完毕时,倒序应用拦截器链中的所有拦截器的postHandle后处理方法
  7. 处理handler执行过程中抛出的异常(如果有),将会通过注册的HandlerExceptionResolvers确定处理错误的ModelAndView,借此我们可以对异常转发到统一配置的错误页面,或者直接返回错误的JSON数据
  8. 根据ModelAndView(无论是正产返回的还是错误处理的)渲染视图,如果此前返回的ModelAndView为null,则这一步不会执行。
    1. 首先可能会调用ViewResolver尝试将给定的视图名称解析为一个View对象(待呈现,内部保存了经过逻辑视图名viewName解析出来的物理视图资源URL路径)
    2. 随后调用View本身的render方法,真正的执行渲染的工作。但是,如果更加严格的话,View 更多的是用于在将模型数据移交给特定视图技术之前进行数据准备,而不是真正的进行视图渲染,视图渲染仍然依靠其该类型视图本身的视图渲染技术!
      1. 对于一个简单的转发请求或者包含请求,如果目标是JSP视图,则View就是InternalResourceView,在渲染时首先就是将model数据存储到request域属性中,然后通过RequestDispatcher直接将请求include包含或者forward转发到物理视图路径URL对应的JSP资源,而include/forward方法则是我们在之前就见过的原始的Servlet API,最终还是通过response响应给用户的(对JSP来说就是将JSP渲染之后通过response输出为HTML文本)。
      2. 从上面JSP视图的渲染和输出可以看出来,View中仅仅是进行了数据的封装和准备(比如将model数据存储到request域属性中),并没有进行实际渲染视图的工作(仅仅是调用include/forward方法),对于JSP视图来说,真正的视图渲染和数据填充以及返回响应仍然是依赖最原始JSP技术,与Spring MVC无关,与View无关。
  9. 最后,无论请求处理成功还是失败抛出异常,都会倒序应用拦截器链中的所有拦截器的afterCompletion处理方法,表示请求处理完毕

  为此,我们给出一幅比较完整的Spring MVC请求执行流程图:
Spring MVC 请求执行流程的源码深度解析【两万字】_第8张图片

相关文章:
  https://spring.io/
  Spring Framework 5.x 学习
  Spring MVC 5.x 学习
  Spring Framework 5.x 源码

如有需要交流,或者文章有误,请直接留言。另外希望点赞、收藏、关注,我将不间断更新各种Java学习博客!

你可能感兴趣的:(Spring,MVC,5.x,源码,新星计划,springmvc请求执行流程,springmvc源码,java)