【小家Spring】Spring MVC执行流程 FrameworkServlet、DispatcherServlet源码分析(processRequest、doDispatch)(中)

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 【小家Spring】Spring MVC执行流程 FrameworkServlet、DispatcherServlet源码分析(processRequest、doDispatch)(中)

DispatcherServlet#doService方法解析


  @Override
  protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    // 如果该请求是include的请求(请求包含) 那么就把request域中的数据保存一份快照版本
    // 等doDispatch结束之后,会把这个快照版本的数据覆盖到新的request里面去
    Map<String, Object> attributesSnapshot = null;
    if (WebUtils.isIncludeRequest(request)) {
      attributesSnapshot = new HashMap<>();
      Enumeration<?> attrNames = request.getAttributeNames();
      while (attrNames.hasMoreElements()) {
        String attrName = (String) attrNames.nextElement();
        if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
          attributesSnapshot.put(attrName, request.getAttribute(attrName));
        }
      }
    }
    // Make framework objects available to handlers and view objects.
    // 说得很清楚,把一些常用对象放进请求域  方便Handler里面可以随意获取
    request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext()); //这个是web子容器哦
    request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
    request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
    request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
    // 如果是重定向,放置得更多一些~~~~
    if (this.flashMapManager != null) {
      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 {
      // DispatcherServlet最重要的方法,交给他去分发请求你、找到handler处理等等
      doDispatch(request, response);
    } finally {
      if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
        // Restore the original attribute snapshot, in case of an include.
        //如果是include请求  会上上面的数据快照,重新放置到request里面去
        if (attributesSnapshot != null) {
          restoreAttributesAfterInclude(request, attributesSnapshot);
        }
      }
    }
  }

DispatcherServlet#doDispatch方法解析


首先根据请求的路径找到HandlerMethod(带有Method反射属性,也就是对应Controller中的方法),

然后匹配路径对应的拦截器,有了HandlerMethod和拦截器构造个HandlerExecutionChain对象。HandlerExecutionChain对象的获取是通过HandlerMapping接口提供的方法中得到。

有了HandlerExecutionChain之后,通过HandlerAdapter对象进行处理得到ModelAndView对象,HandlerMethod内部handle的时候,使用各种HandlerMethodArgumentResolver实现类处理HandlerMethod的参数(非常重要),使用各种HandlerMethodReturnValueHandler实现类处理返回值。 最终返回值被处理成ModelAndView对象,这期间发生的异常会被HandlerExceptionResolver接口实现类进行处理。

  protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    // 此处用processedRequest  需要注意的是:若是处理上传,processedRequest 将和request不再指向同一对象
    HttpServletRequest processedRequest = request;
    // 异常链处理器
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    try {
      ModelAndView mv = null;
      Exception dispatchException = null;
      try {
        //checkMultipart 这个方法很重要,判断是否是上传需求。且看下面的具体分析:::
        //如果请求是POST请求,并且请求头中的Context-Type是以multipart/开头的就认为是文件上传的请求
        processedRequest = checkMultipart(request);
        // 标记一下:是否是文件上传的request了
        multipartRequestParsed = (processedRequest != request);
        // 找到一个处理器,如果没有找到对应的处理类的话,这里通常会返回404,如果throwExceptionIfNoHandlerFound属性值为true的情况下会抛出异常
        mappedHandler = getHandler(processedRequest);
        if (mappedHandler == null) {
          noHandlerFound(processedRequest, response);
          return;
        }
        // 根据实际的handler去找到一个合适的HandlerAdapter,方法详细逻辑同getHandler,因此不再解释
        HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
        // 如果是GET请求,如果内容没有变化的话,则直接返回
        String method = request.getMethod();
        boolean isGet = "GET".equals(method);
        if (isGet || "HEAD".equals(method)) {
          long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
          if (logger.isDebugEnabled()) {
            logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
          }
          if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
            return;
          }
        }
        // 这段代码很有意思:执行处理器连里的拦截器们,具体参阅下面详细:
        if (!mappedHandler.applyPreHandle(processedRequest, response)) {
          return;
        }
        // 真正执行我们自己书写的controller方法的逻辑。返回一个ModelAndView
        // 这也是一个很复杂的过程(序列化、数据绑定等等),需要后面专题讲解
        mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
        // 如果异步启动了,这里就先直接返回了,也就不会再执行拦截器PostHandle之类的
        if (asyncManager.isConcurrentHandlingStarted()) {
          return;
        }
        //意思是:如果我们没有设置viewName,就采用默认的。否则采用我们自己的
        applyDefaultViewName(processedRequest, mv);
        // 执行所有的拦截器的postHandle方法,并且把mv给他
        // 这里有一个小细节:这个时候拦截器是【倒序】执行的
        mappedHandler.applyPostHandle(processedRequest, response, mv);
      } catch (Exception ex) { // 这两个catcher什么都不做,只是把异常记录下来
        dispatchException = ex;
      } catch (Throwable err) {
        dispatchException = new NestedServletException("Handler dispatch failed", err);
      }
      //这个方法很重要,顾名思义,他是来处理结果的,渲染视图、处理异常等等的  下面详细分解
      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);
        }
      }
    }
  }


checkMultipart


multipartResolver 在上篇博文初始化的时候讲过了,值是有可能为null的哦(如果你没有配置对应的Bean的话)


    protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
      // 配置了multipartResolver,并且是文件上传的请求  才会继续往下走
        if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
          // 如果该请求已经是MultipartHttpServletRequest 那就输出一个日志走人
            if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
                logger.debug("日志。。。");
            } else if (hasMultipartException(request) ) { // 判断是否有MultipartException 一般没有
                logger.debug("Multipart resolution failed for current request before - " +
                        "skipping re-resolution for undisturbed error rendering");
            } else {
                try {
                  // 这里特别注意,不管是哪种multipartResolver的实现,内部都是new了一个新的MultipartHttpServletRequest的实现类,所以不再指向原来的request了,所以一定要注意
                    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;
                    }
                }
            }
        }
        // If not returned before: return original request.
        // 如果前面没有返回,就原样返回,相当于啥都不做
        return request;
    }


这里需要注意的是:org.springframework.web.multipart.support.MultipartFilter,如果在web.xml中配置这个过滤器的话,则会在过滤器中提前判断是不是文件上传的请求,并将请求转换为MultipartHttpServletRequest类型。这个过滤器中默认使用的MultipartResolver为StandardServletMultipartResolver。


在CommonsMultipartResolver中有一个属性叫resolveLazily

private boolean resolveLazily = false;

这个属性值代表是不是延迟解析文件上传,默认为false。最终返回的是一个DefaultMultipartHttpServletRequest的类。这里有一个重要的方法是:parseRequest,这个方法干的事是解析文件上传请求。它的底层是commons-fileupload那一套,不同的是Spring在获取FileItem之后,又进行了一下封装,封装为便于Spring框架整合。tHandler**

  @Nullable
  protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    if (this.handlerMappings != null) {
      // 会把配置的所有的HandlerMapping 都拿出来查找,只要找到一个就返回
      for (HandlerMapping hm : this.handlerMappings) {
        if (logger.isTraceEnabled()) {
          logger.trace(
              "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
        }
        HandlerExecutionChain handler = hm.getHandler(request);
        if (handler != null) {
          return handler;
        }
      }
    }
    return null;
  }



相关文章
|
19天前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
28 1
|
20天前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
25 1
|
1月前
|
JSON 前端开发 Java
SSM:SpringMVC
本文介绍了SpringMVC的依赖配置、请求参数处理、注解开发、JSON处理、拦截器、文件上传下载以及相关注意事项。首先,需要在`pom.xml`中添加必要的依赖,包括Servlet、JSTL、Spring Web MVC等。接着,在`web.xml`中配置DispatcherServlet,并设置Spring MVC的相关配置,如组件扫描、默认Servlet处理器等。然后,通过`@RequestMapping`等注解处理请求参数,使用`@ResponseBody`返回JSON数据。此外,还介绍了如何创建和配置拦截器、文件上传下载的功能,并强调了JSP文件的放置位置,避免404错误。
|
16天前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
21 0
|
1月前
|
前端开发 Java 应用服务中间件
【Spring】Spring MVC的项目准备和连接建立
【Spring】Spring MVC的项目准备和连接建立
53 2
|
1月前
|
XML 前端开发 Java
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
本文阐述了Spring、Spring Boot和Spring MVC的关系与区别,指出Spring是一个轻量级、一站式、模块化的应用程序开发框架,Spring MVC是Spring的一个子框架,专注于Web应用和网络接口开发,而Spring Boot则是对Spring的封装,用于简化Spring应用的开发。
111 0
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
|
2月前
|
XML 缓存 前端开发
springMVC02,restful风格,请求转发和重定向
文章介绍了RESTful风格的基本概念和特点,并展示了如何使用SpringMVC实现RESTful风格的请求处理。同时,文章还讨论了SpringMVC中的请求转发和重定向的实现方式,并通过具体代码示例进行了说明。
springMVC02,restful风格,请求转发和重定向
|
6月前
|
设计模式 前端开发 Java
了解 Spring MVC 架构、Dispatcher Servlet 和 JSP 文件的关键作用
Spring MVC 是 Spring 框架的一部分,是一个 Web 应用程序框架。它旨在使用 Model-View-Controller(MVC) 设计模式轻松构建Web应用程序。
101 0
|
1月前
|
存储 设计模式 前端开发
什么是SpringMVC?简单好理解!什么是应用分层?SpringMVC与应用分层的关系? 什么是三层架构?SpringMVC与三层架构的关系?
文章解释了SpringMVC的概念和各部分功能,探讨了应用分层的原因和具体实施的三层架构,以及SpringMVC与三层架构之间的关系和联系。
22 1
什么是SpringMVC?简单好理解!什么是应用分层?SpringMVC与应用分层的关系? 什么是三层架构?SpringMVC与三层架构的关系?
|
5月前
|
安全 前端开发 Java
挑战5分钟内基于Springboot+SpringMVC+Mybatis-plus快速构建web后端三层架构
挑战5分钟内基于Springboot+SpringMVC+Mybatis-plus快速构建web后端三层架构
52 1