【Spring MVC】Spring MVC 源码分析

Spring MVC 执行流程

Spring MVC 原理概述:在Tomcat中注册配置了一个DispatcherServlet,该Servlet的默认请求映射路径为 "/",当客户端收到符合该路径的请求时,将执行该Servlet的doService()方法,其都会执行一个doDispatch()方法,在其内进行请求映射、参数解析、返回值处理、内容协商、页面解析跳转等操作。

image-20220312203816076

可以将整个流程分成三个阶段:

  • 准备阶段
  • 匹配阶段
  • 执行阶段

准备阶段

  1. 在 Web 容器第一次用到 DispatcherServlet 的时候,会创建其对象并执行 init 方法

  2. init 方法内会创建 Spring Web 容器,并调用容器 refresh 方法

  3. refresh 过程中会创建并初始化 SpringMVC 中的重要组件, 例如 MultipartResolver,HandlerMapping,HandlerAdapter,HandlerExceptionResolver、ViewResolver 等

  4. 容器初始化后,会将上一步初始化好的重要组件,赋值给 DispatcherServlet 的成员变量,留待后用

image-20210903140657163

匹配阶段

  1. 用户发送的请求统一到达前端控制器 DispatcherServlet

  2. DispatcherServlet 遍历所有 HandlerMapping ,找到与路径匹配的处理器

    ① HandlerMapping 有多个,每个 HandlerMapping 会返回不同的处理器对象,谁先匹配,返回谁的处理器。其中能识别 @RequestMapping 的优先级最高

    ② 对应 @RequestMapping 的处理器是 HandlerMethod,它包含了控制器对象和控制器方法信息

    ③ 其中路径与处理器的映射关系在 HandlerMapping 初始化时就会建立好

image-20210903141017502
  1. 将 HandlerMethod 连同匹配到的拦截器,生成调用链对象 HandlerExecutionChain 返回
image-20210903141124911
  1. 遍历HandlerAdapter 处理器适配器,找到能处理 HandlerMethod 的适配器对象,开始调用
image-20210903141204799

调用阶段

  1. 执行拦截器 preHandle
image-20210903141445870
  1. 由 HandlerAdapter 调用 HandlerMethod

    ① 调用前处理不同类型的参数

    ② 调用后处理不同类型的返回值

image-20210903141658199
  1. 第 2 步没有异常

    ① 返回 ModelAndView

    ② 执行拦截器 postHandle 方法

    ③ 解析视图,得到 View 对象,进行视图渲染

image-20210903141749830
  1. 第 2 步有异常,进入 HandlerExceptionResolver 异常处理流程
image-20210903141844185
  1. 最后都会执行拦截器的 afterCompletion 方法
  2. 如果控制器方法标注了 @ResponseBody 注解,则在第 2 步,就会生成 json 结果,并标记 ModelAndView 已处理,这样就不会执行第 3 步的视图渲染

Spring MVC 的九大组件

DispatchServlet组件中有九个组件,SpringMVC在工作时,关键功能都是由这些组件完成的。其共同点:九大组件全部都是接口。接口就是规范。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/** 文件上传解析器 */
@Nullable
private MultipartResolver multipartResolver;

/** 区域信息解析器(国际化) */
@Nullable
private LocaleResolver localeResolver;

/** 主题解析器:主题效果更换 */
@Nullable
private ThemeResolver themeResolver;

/** HandlerMapping 处理器映射器 */
@Nullable
private List<HandlerMapping> handlerMappings;

/** 处理器对应的适配器 */
@Nullable
private List<HandlerAdapter> handlerAdapters;

/** 处理器异常解析器:强大的异常解析功能 */
@Nullable
private List<HandlerExceptionResolver> handlerExceptionResolvers;

/** 若方法返回值为void,把请求的地址转换为视图名 */
@Nullable
private RequestToViewNameTranslator viewNameTranslator;

/** 允许重定向携带数据的功能 */
@Nullable
private FlashMapManager flashMapManager;

/** 视图解析器 */
@Nullable
private List<ViewResolver> viewResolvers;

这些组件在容器启动时进行初始化:

image-20210720161209851

组件初始化的过程简单来说就是去容器中找这个组件,如果找不到就用默认的配置策略。有些组件在容器中是使用类型查找,有些组件是使用id查找。示例:

image-20210720161841815

DispatcherServlet

DispatcherServlet类继承自FrameworkServlet类,该类最终继承自HttpServlet类。

image-20210715163040938

FrameworkServlet类中有多个处理请求的方法(doGet/doPost/doPut/doDelete),他们都调用了同一个方法this.processRequest(request, response)。(DispatcherServlet类中并没有重写这些doXxx方法和processRequest()方法,因此调用的是FrameworkServlet类里的这些方法)

image-20210715162521522

processRequest(request, response)方法中最关键的一步为调用this.doService(request, response)方法。但该方法在FrameworkServlet类中为抽象方法,需要子类实现,因此调用的是DispatcherServlet类里的 this.doService(request, response) 方法。

image-20210715162814683

FrameworkServlet类重写的 doService() 方法:

image-20210715163548801

该方法调用了FrameworkServlet中的 doDispatch(request, response) 方法:

image-20210715163935578

因此分析清楚 doDispatch(request, response) 方法即可理解SpringMVC的大致原理。上述关系树见下图:

image-20210715164354367

Spring MVC 详细执行流程

客户端发来请求后,经过上述分析的流程,会执行FrameworkServlet类里的 doDispatch(request, response) 方法。下面按顺序分析该方法完成的工作:

image-20210715165846578

image-20210719204334058

image-20210719205128323

  1. this.checkMultipart() :判断当前请求是否为文件上传请求,若是,则将其进行包装
  2. this.getHandler():根据当前的url请求地址("/hello")判断哪个控制器@Controller)里的映射方法@RequestMapping("/hello"))与之对应(在HandlerMapping中找到这个请求的映射信息),返回一个mappedHandler
  3. this.noHandlerFound():如果没找到对应的处理器(控制器)能处理当前请求,则404抛异常
  4. this.getHandlerAdapter():根据当前处理器mappedHandler获取相应的适配器(反射工具)HandlerAdapter
  5. ha.handle():此处真正执行目标方法。使用适配器执行目标方法,并将目标方法执行完毕后的返回值("sucess")作为视图名保存到一个ModelAndView对象中。(此时执行目标方法,但还未转发页面)
  6. this.applyDefaultViewName():如果目标方法返回值类型为void,即没有指定返回的视图名,则执行默认的视图名(转发到请求地址本身)
  7. this.processDispatchResult():根据目标方法最终执行完成后封装的ModelAndView内的信息转发到相应的页面(页面信息保存在view里),并且可以从请求域中取出ModelAndView中保存的数据。

下面详细分析上述步骤中的重要细节。

更多详细源码分析见文章【Spring Boot】Spring Boot2 源码分析