SpringMVC请求流程及RequestMapping解析流程原理解析

流程图

注意,该流程图为核心!!!!!别错过喔
springmvc-@RequestMapping解析流程:https://www.processon.com/view/link/619e55030791295908f3bf76

springmvc-请求流程:https://www.processon.com/view/link/619e55235653bb136f812283

springmvc-大致请求流程:https://www.processon.com/view/link/61a5f5fa0e3e743d10494f23

核心组件

DispatcherServlet: 前端控制器 , 负责将请求拦截下来分发到各控制器方法中

HandlerMapping: 处理器映射器,负责根据请求的URL和配置@RequestMapping映射去匹配, 匹配到会返回Handler(具体控制器的方法)

Handler:处理器Handler又名Controller

HandlerAdaper: 处理器适配器,负责调用Handler-具体的方法- 返回视图的名字 Handler将它封装到ModelAndView(封装视图名,request域的数据)

ViewReslover: 视图解析器,根据ModelAndView里面的视图名地址去找到具体的jsp封装在View对象中

View:视图,进行视图渲染(将jsp转换成html内容 --这是Servlet容器的事情了) 最终response到的客户端

SpringMVC的具体执行流程

Spring MVC 是围绕前端控制器模式设计的,其中:* Servlet DispatcherServlet 为请求处理流程提供统一调度,实际工作则交给可配置组件执行。这个模型是灵活的且开放的,我们可以通过自己去定制这些组件从而进行定制自己的工作流。

  1. 用户发送请求至前端控制器DispatcherServlet
  2. DispatcherServlet收到请求调用处理器映射器HandlerMapping。
    1. 处理器映射器根据请求url找到具体的处理器,生成处理器执行链HandlerExecutionChain(包括处理器对象和处理器拦截器)一并返回给DispatcherServlet。
  3. DispatcherServlet根据处理器Handler获取处理器适配器HandlerAdapter,执行HandlerAdapter处理一系列的操作,如:参数封装,数据格式转换,数据验证等操作
  4. 执行处理器Handler(Controller,也叫页面控制器)。
    1. Handler执行完成返回ModelAndView
    2. HandlerAdapter将Handler执行结果ModelAndView返回到DispatcherServlet
  5. DispatcherServlet将ModelAndView传给ViewReslover视图解析器
    1. ViewReslover解析后返回具体View
  6. DispatcherServlet对View进行渲染视图(即将模型数据model填充至视图中)。
  7. DispatcherServlet响应用户。

整个调用过程其实都在doDispatch中体现了

  1. 用户发送请求至前端控制器DispatcherServlet
  • 由于它是个Servlet会先进入service方法——>doGet/doPost——>processRequestdoService——>doDispatch ↓
  • 这个doDispatch非常重要–体现了整个请求流程
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
   
   try {
      
      try {
          // 文件上传相关
         processedRequest = checkMultipart(request);
         multipartRequestParsed = (processedRequest != request);
         
        // DispatcherServlet收到请求调用处理器映射器HandlerMapping。
        // 处理器映射器根据请求url找到具体的处理器,生成处理器执行链HandlerExecutionChain(包括处理器对象和处理器拦截器)一并返回给DispatcherServlet。
         mappedHandler = getHandler(processedRequest);
         if (mappedHandler == null) {
            noHandlerFound(processedRequest, response);
            return;
         }

         //4.DispatcherServlet根据处理器Handler获取处理器适配器HandlerAdapter,
         HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

         // Process last-modified header, if supported by the handler.  HTTP缓存相关
         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)) {
            // 返回false就不进行后续处理了
            return;
         }

         // 执行HandlerAdapter处理一系列的操作,如:参数封装,数据格式转换,数据验证等操作
         // 执行处理器Handler(Controller,也叫页面控制器)。
         // Handler执行完成返回ModelAndView
         // HandlerAdapter将Handler执行结果ModelAndView返回到DispatcherServlet
         mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

         if (asyncManager.isConcurrentHandlingStarted()) {
            return;
         }
         // 如果没有视图,给你设置默认视图  json忽略
         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);
      }
      // DispatcherServlet将ModelAndView传给ViewReslover视图解析器
      // ViewReslover解析后返回具体View
      // DispatcherServlet对View进行渲染视图(即将模型数据model填充至视图中)。
      // DispatcherServlet响应用户。
      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);
         }
      }
   }

SpringMVC中三大组件详解

处理器映射器

它指的是:HandlerMapping是在 Spring 的 3.1 版本之后加入的。它的出现,可以让使用者更加轻松的去配置 SpringMVC 的请求路径映

射。去掉了早期繁琐的 xml 的配置,它的配置有两种方式:都是在 springmvc.xml 中加入配置。

第一种方式:

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>

第二种方式:

在<mvc:annotation-driven /> 对应的解析器,自动向ioc 里面注册了两个BeanDefinition。分别是:RequestMappingHandlerMapping与BeanNameUrlHandlerMapping

<mvc:annotation-driven></mvc:annotation-driven>

处理器适配器

要清晰的认识 SpringMVC 的处理器适配器,就先必须知道适配器以及它的作用。

把不同的控制器,最终都可以看成是适配器类型,从而执行适配器中定义的方法。更深层次的是,可以把公共的功能都定义在适配器中,从而减少每种控制器中都有的重复性代码。在SpringMVC 的执行过程中,最终调用的是前端控制器 DispatcherServlet 的 doDispatch 方法,而该方法中的 HandlerAdapter 的 handle 方法实际调用了我们自己写的Controller方法。Controller内部方法名称各不一样,它是通过 handle 方法反射调用的。

其实 SpringMVC 中处理器适配器也有多个。这里Spring mvc 采用适配器模式来适配调用指定Handler,根据Handler的不同种类采用不同的Adapter,其Handler与 HandlerAdapter 对应关系如下:

Handler类别 对应适配器 描述 使用方式
Controller SimpleControllerHandlerAdapter 标准控制器,返回ModelAndView 使用此适配器,适用的控制器,要求实现 Controller 接口
HttpRequestHandler HttpRequestHandlerAdapter 业务自行处理 请求,不需要通过modelAndView 转到视图 使用此适配器的控制器,要求实现 HttpRequestHandler 接口
Servlet SimpleServletHandlerAdapter 基于标准的servlet 处理
HandlerMethod RequestMappingHandlerAdapter 基于@requestMapping对应方法处理 这种方式也是我们实际开发中采用最多的。它的要求是我们用注解@Controller 配置控制器

视图解析器

视图的作用是渲染模型数据,将模型里的数据以某种形式呈现给客户。为了实现视图模型和具体实现技术的解耦,Spring 在org.springframework.web.servlet 包中定义了一个高度抽象的 View 接口。视图是无状态的,所以他们不会有线程安全的问题。无状态是指对于每一个请求,都会创建一个 View对象。在 SpringMVC 中常用的视图类型:

分类 视图类型 说明
URL 视图 InternalResourceView 将 JSP 或者其他资源封装成一个视图,是InternaleResourceViewResolver默认使用的视图类型。
JstlView 它是当我们在页面中使用了 JSTL 标签库的国际化标签后,需要采用的类型。
文档类视图 AbstractPdfView PDF 文档视图的抽象类
AbstarctXlsView Excel 文档视图的抽象类,该类是 4.2版 本 之 后 才 有 的 。 之 前 使 用 的 是AbstractExcelView。
JSON 视图 MappingJackson2JsonView 将模型数据封装成Json格式数据输出。它需要借助 Jackson 开源框架。
XML 视图 MappingJackson2XmlView 将模型数据封装成 XML 格式数据。它是从 4.1 版本之后才加入的。

View Resolver 负责将处理结果生成 View 视图,View Resolver 首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成View视图对象,最后对View进行渲染将处理结果通过页面展示给用户。视图对象是由视图解析器负责实例化。视图解析器的作用是将逻辑视图转为物理视图,所有的视图解析器都必须实现 ViewResolver 接口。SpringMVC 为逻辑视图名的解析提供了不同的策略,可以在 Spring WEB 上下文中配置一种或多种解析策略,并指定他们之间的先后顺序。每一种映射策略对应一个具体的视图解析器实现类。可以选择一种视图解析器或混用多种视图解析器。可以通过 order 属性指定解析器的优先顺序,order 越小优先级越高,SpringMVC 会按视图解析器顺序的优先顺序对逻辑视图名进行解析,直到解析成功并返回视图对象,否则抛出 ServletException异常。

分类 解析器类型 说明
解析为 Bean 的名称 BeanNameViewResolver Bean 的 id 即为逻辑视图名称。
解析为 URL 文件 InternalResourceViewResolver 将视图名解析成一个 URL 文件,一般就是一个 jsp 或者 html 文件。文件一般都存放在 WEB-INF 目录中。
解析指定 XML 文件 XmlViewResolver 解析指定位置的 XML 文件,默认在/WEB-INF/views.xml
解析指定属性文件 ResourceBundleViewResolver 解析 properties 文件。

不需要视图解析器的场景分析

此处都是以注解@Controller 配置控制器为例,控制器的方法返回值其实支持三种方式:

第一种:String 类型。借助视图解析器,可以在指定位置找到指定扩展名的视图。视图可以是 JSP,HTML 或者其他的控制器方法上的 RequestMapping 映射地址。前往指定视图的方式,默认是请求转发,可以通过redirect:前缀控制其使用重定向。

第二种:void,即没有返回值。因为在控制器方法的参数中可以直接使用原始 SerlvetAPI 对象HttpServletRequest 和 HttpServletResponse 对象,所以无论是转发还是重定向都可以轻松实现,而无需使用返回值。

第三种:ModelAndView 类型。在 DispatcherServlet 中的 doDispatch 方法执行时,HandlerAdapter 的 handle 方法的返回值就是 ModelAndView,只有控制器方法定义为 void时,才不会返回此类型。当返回值是 String 的时候也会创建 ModelAndView 并返回。

通过上面三种控制器方法返回值,可以再深入的剖析一下请求之后接收响应的方式,其实无外乎就三种。

第一种:请求转发

第二种:重定向

第三种:直接使用 Response 对象获取流对象输入。可以是字节流也可以是字符流。

这三种方式的本质区别:其中请求转发和重定向的区别就是都会引发页面的跳转。在实际开发中,如果不需要页面跳转,即基于 ajax 的异步请求,用 json 数据交互时,即可不配置任何视图解析器。前后端交互是通过 json 数据的,利用@RequestBody 和@ResponseBody 实现数据到 java对象的绑定(当然还要借助类似 Jackson 开源框架)。

请求参数封装的实现原理

在使用 SpringMVC 实现请求参数封装时,它支持基本类型,POJO 类型和集合类型。其封装原理其实就是使用原始的 ServletAPI 中的方法,并且配合反射实现的封装。

常用注解的使用场景及实现思路分析

RequestParam

在请求体的 MIME 类型为 application/x-www-form-urlencoded 或者 application/json 的情况下,无论 get/post/put/delete 请求方式,参数的体现形式都是 key=value。SpringMVC 是使用我们控制器方法的形参作为参数名称,再使用 request 的getParameterValues 方法获取的参数。所以才会有请求参数的 key 必须和方法形参变量名称保持一致的要求。但是如果形参变量名称和请求参数的 key 不一致呢?此时,参数将无法封装成功。此时 RequestParam 注解就会起到作用,它会把该注解 value 属性的值作为请求参数的 key 来获取请求参数的值,并传递给控制器方法。

RequestBody

SpringMVC 在封装请求参数的时候,默认只会获取参数的值,而不会把参数名称一同获取出来,这在使用表单提交的时候没有任何问题。因为表单提交,请求参数是key=value 的。但是当使用 ajax 进行提交时,请求参数可能是 json 格式的:{key:value},在此种情况下,要想实现封装以前面的内容是无法实现的。此时需要我们使用@RequestBody 注解。

PathVariable

它是 SpringMVC 在 3.0 之后新加入的一个注解,是 SpringMVC 支持 Restful 风格 URL 的一个重要标志。该注解的作用就是把藏在请求 URL 中的参数,给控制器方法的形参赋值。SpringMVC 在实现请求 URL 使用占位符传参并封装到控制器方法的形参中,是通过请求域来实现的。最后把请求域转成一个 Map,再根据形参的名称作为 key,从 map 中获取 value,并给形参赋值。当然如果使用了 PathVariable 注解的 value 属性,则不会以形参名称为 key,而是直接使用 value属性的值作为 key 了。

拦截器的AOP思想

AOP 思想是 Spring 框架的两大核心之一,是解决方法调用依赖以及提高方便后期代码维护的重要思想。它是把代码中高度重复的部分抽取出来,并在适当的时机,通过代理机制来执行,从而做到不修改源码对已经写好的方法进行增强。而拦截器正式这种思想的具体实现。

Spring整合SpringMvc注意事项

Spring 的 IOC 容器不应该扫描 SpringMVC 中的 bean, 对应的SpringMVC 的 IOC 容器不应该扫描 Spring 中的 bean

 <context:component-scan base-package="com.baiqi.springmvc" use-default-filters="false">  
      <context:include-filter type="annotation"    expression="org.springframework.stereotype.Controller"/>
       <context:include-filter type="annotation"    expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
  </context:component-scan>
      
<context:component-scan base-package="com.baiqi.springmvc">  
    <context:exclude-filter type="annotation"    expression="org.springframework.stereotype.Controller"/>
    <context:exclude-filter type="annotation"    expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
 </context:component-scan>

若文章有误或有不明白的地方可联系作者喔
QQ:1654362787
wx:YierGs
笔者:弋尔

上一篇:Java的常识


下一篇:JDK,JRE,JVM