【Spring MVC】Spring MVC 源码分析
Spring MVC 执行流程
Spring MVC 原理概述:在Tomcat中注册配置了一个DispatcherServlet,该Servlet的默认请求映射路径为 "/"
,当客户端收到符合该路径的请求时,将执行该Servlet的doService()
方法,其都会执行一个doDispatch()
方法,在其内进行请求映射、参数解析、返回值处理、内容协商、页面解析跳转等操作。
可以将整个流程分成三个阶段:
- 准备阶段
- 匹配阶段
- 执行阶段
准备阶段
-
在 Web 容器第一次用到 DispatcherServlet 的时候,会创建其对象并执行 init 方法
-
init 方法内会创建 Spring Web 容器,并调用容器 refresh 方法
-
refresh 过程中会创建并初始化 SpringMVC 中的重要组件, 例如 MultipartResolver,HandlerMapping,HandlerAdapter,HandlerExceptionResolver、ViewResolver 等
-
容器初始化后,会将上一步初始化好的重要组件,赋值给 DispatcherServlet 的成员变量,留待后用
匹配阶段
-
用户发送的请求统一到达前端控制器 DispatcherServlet
-
DispatcherServlet 遍历所有 HandlerMapping ,找到与路径匹配的处理器
① HandlerMapping 有多个,每个 HandlerMapping 会返回不同的处理器对象,谁先匹配,返回谁的处理器。其中能识别 @RequestMapping 的优先级最高
② 对应 @RequestMapping 的处理器是 HandlerMethod,它包含了控制器对象和控制器方法信息
③ 其中路径与处理器的映射关系在 HandlerMapping 初始化时就会建立好
- 将 HandlerMethod 连同匹配到的拦截器,生成调用链对象 HandlerExecutionChain 返回
- 遍历HandlerAdapter 处理器适配器,找到能处理 HandlerMethod 的适配器对象,开始调用
调用阶段
- 执行拦截器 preHandle
-
由 HandlerAdapter 调用 HandlerMethod
① 调用前处理不同类型的参数
② 调用后处理不同类型的返回值
-
第 2 步没有异常
① 返回 ModelAndView
② 执行拦截器 postHandle 方法
③ 解析视图,得到 View 对象,进行视图渲染
- 第 2 步有异常,进入 HandlerExceptionResolver 异常处理流程
- 最后都会执行拦截器的 afterCompletion 方法
- 如果控制器方法标注了 @ResponseBody 注解,则在第 2 步,就会生成 json 结果,并标记 ModelAndView 已处理,这样就不会执行第 3 步的视图渲染
Spring MVC 的九大组件
DispatchServlet组件中有九个组件,SpringMVC在工作时,关键功能都是由这些组件完成的。其共同点:九大组件全部都是接口。接口就是规范。
1 | /** 文件上传解析器 */ |
这些组件在容器启动时进行初始化:
组件初始化的过程简单来说就是去容器中找这个组件,如果找不到就用默认的配置策略。有些组件在容器中是使用类型查找,有些组件是使用id查找。示例:
DispatcherServlet
DispatcherServlet类继承自FrameworkServlet类,该类最终继承自HttpServlet类。
FrameworkServlet类中有多个处理请求的方法(doGet/doPost/doPut/doDelete
),他们都调用了同一个方法this.processRequest(request, response)
。(DispatcherServlet类中并没有重写这些doXxx
方法和processRequest()
方法,因此调用的是FrameworkServlet类里的这些方法)
processRequest(request, response)
方法中最关键的一步为调用this.doService(request, response)
方法。但该方法在FrameworkServlet类中为抽象方法,需要子类实现,因此调用的是DispatcherServlet类里的 this.doService(request, response) 方法。
FrameworkServlet类重写的 doService() 方法:
该方法调用了FrameworkServlet中的 doDispatch(request, response) 方法:
因此分析清楚 doDispatch(request, response) 方法即可理解SpringMVC的大致原理。上述关系树见下图:
Spring MVC 详细执行流程
客户端发来请求后,经过上述分析的流程,会执行FrameworkServlet类里的 doDispatch(request, response) 方法。下面按顺序分析该方法完成的工作:
- this.checkMultipart() :判断当前请求是否为文件上传请求,若是,则将其进行包装
- this.getHandler():根据当前的url请求地址
("/hello")
判断哪个控制器(@Controller
)里的映射方法(@RequestMapping("/hello")
)与之对应(在HandlerMapping中找到这个请求的映射信息),返回一个mappedHandler。 - this.noHandlerFound():如果没找到对应的处理器(控制器)能处理当前请求,则404抛异常
- this.getHandlerAdapter():根据当前处理器mappedHandler获取相应的适配器(反射工具)HandlerAdapter。
- ha.handle():此处真正执行目标方法。使用适配器执行目标方法,并将目标方法执行完毕后的返回值
("sucess")
作为视图名保存到一个ModelAndView
对象中。(此时执行目标方法,但还未转发页面) - this.applyDefaultViewName():如果目标方法返回值类型为void,即没有指定返回的视图名,则执行默认的视图名(转发到请求地址本身)
- this.processDispatchResult():根据目标方法最终执行完成后封装的
ModelAndView
内的信息转发到相应的页面(页面信息保存在view里),并且可以从请求域中取出ModelAndView
中保存的数据。
下面详细分析上述步骤中的重要细节。
更多详细源码分析见文章【Spring Boot】Spring Boot2 源码分析