【Spring MVC】Spring MVC实现原理(源码阅读)
迪丽瓦拉
2025-05-31 18:32:51
0

文章目录

  • 前言
  • Spring MVC基本流程
  • Spring MVC九大组件
    • HandlerMapping
    • HandlerAdapter
    • HandlerExceptionResolver
    • ViewResolver
    • RequestToViewNameTranslator
    • LocaleResolver
    • ThemeResolver
    • MultipartResolver
    • FlashMapManager
  • Spring MVC源码分析
    • 初始化阶段
    • 运行调用阶段
      • 确定当前请求的处理器
      • 获取当前请求的处理适配器
      • 拦截器前置处理
      • 真正调用处理器
      • 拦截器后置处理
  • 总结

前言

Spring MVC是当前web应用使用最为广泛的框架,本文将会从源码角度详细分析Spring MVC实现原理。

Spring MVC基本流程

在具体讲解之前,我们先从宏观上简单介绍Spring MVC的处理流程,了解用户从发起请求到获得响应的完整流程。
在这里插入图片描述

流程简介:

  1. 用户发起请求到DispatcherServlet(前端控制器)

  2. DispatcherServlet通过HandlerMapping(处理映射器)寻找用户要请求的Handler

  3. HandlerMapping返回执行链,包含两部分内容:

    • 处理器对象:Handler
    • HandlerInterceptor(拦截器)的集合
  4. 前端控制器通过HandlerAdapter(处理器适配器)对Handler进行适配包装

  5. 调用包装后的Handler中的方法,处理业务

  6. 处理业务完成,返回ModelAndView对象,包含两部分

    • Model:模型数据
    • View:视图名称,不是真正的视图
  7. DispatcherServlet获取处理得到的ModelAndView对象

  8. DispatcherServlet将视图名称交给ViewResolver(视图解析器),查找视图

  9. ViewResolver返回真正的视图对象给DispatcherServlet

  10. DispatcherServlet把Model(数据模型)交给视图对象进行渲染

  11. 返回渲染后的视图

  12. 将最终的视图返回用户,产生响应

Spring MVC九大组件

HandlerMapping

HandlerMapping是用来查找Handler的,也就是处理器,具体表现形式可以是类也 可以是方法。比如,标注了@RequestMapping的每个method都可以看成是一个Handler,由Handler来负责实际的请求处理。HandlerMapping在请求到达之后, 它的作用便是找到请求相应的处理器HandlerInterceptors

HandlerAdapter

从名字上看,这是一个适配器。因为Spring MVC中Handler可以是任意形式的,只要能够处理请求就行。但是把请求交给 Servlet的时候,由于Servlet的方法结构都是如doService(HttpServletRequest req, HttpServletResponse resp)这样的形式,让固定的Servlet处理方法调用Handler来进行处理,这一步工作便是HandlerAdapter要做的事。

HandlerExceptionResolver

从这个组件的名字上看,这个就是用来处理Handler过程中产生的异常情况的组件。 具体来说,此组件的作用是根据异常设置 ModelAndView, 之后再交给render()方法进行渲染,而render()便将ModelAndView渲染成页面。 不过有一点,HandlerExceptionResolver只是用于解析对请求做处理阶段产生的异常,而渲染阶段的异常则不归他管了,这也是Spring MVC组件设计的一大原则(分工明确互不干涉)。

ViewResolver

视图解析器,相信大家对这个应该都很熟悉了。因为通常在Spring MVC的配置文件中, 都会配上一个该接口的实现类来进行视图的解析。 这个组件的主要作用,便是将String类型的视图名和Locale解析为View类型的视图。这个接口只有一个 resolveViewName()方法。从方法的定义就可以看出,Controller层返回的String类型的视图名viewName, 最终会在这里被解析成为View。View是用来渲染页面的,也就是说,它会将程序返回的参数和数据填入模板中,最终生成html 文件。ViewResolver在这个过程中,主要做两件大事,即ViewResolver会找到渲染所用的模板(使用什么模板来渲染?)和所用 的技术(其实也就是视图的类型,如 JSP等)填入参数。默认情 况下,Spring MVC 会为我们自动配置一个 InternalResourceViewResolver,这个是针 对 JSP 类型视图的。

RequestToViewNameTranslator

这个组件的作用,在于从Request中获取viewName. 因为ViewResolver是根据ViewName查找View, 但有的 Handler处理完成之后,没有设置View也没有设置ViewName, 便要通过这个组件来从Request中查找viewName

LocaleResolver

在上面我们有看到ViewResolverresolveViewName()方法,需要两个参数。那么第二个参数Locale是从哪来的呢,这就是LocaleResolver要做的事了。 LocaleResolver用于从request中解析出Locale, 在中国大陆地区,Locale当然就会是zh-CN之类, 用来表示一个区域。这个类也是i18n的基础。

ThemeResolver

从名字便可看出,这个类是用来解析主题的。主题就是样式,图片以及它们所形成的显示效果的集合。

MultipartResolver

其实这是一个大家很熟悉的组件,MultipartResolver用于处理上传请求,通过将普通的Request包装成 MultipartHttpServletRequest来实现。MultipartHttpServletRequest可以通过getFile()直接获得文件,如果是多个文件上传,还可以通过调用getFileMap得到Map这样的结构。MultipartResolver的作用就是用来封装普通 的request,使其拥有处理文件上传的功能。

FlashMapManager

FlashMap用于重定向Redirect时的参数数据传递, 只需要在redirect之前,将要传递的数据写入request(可以通过 ServletRequestAttributes.getRequest() 获得) 的属性OUTPUT_FLASH_MAP_ATTRIBUTE中,这样在 redirect之后的handler中,Spring就会自动将其设置到Model中,后续就可以直接从Model中取得数据了。而 FlashMapManager就是用来管理FlashMap的。

Spring MVC源码分析

初始化阶段

对于Spring-MVC来说,最核心的类是DispatcherServlet,它负责将请求转发给各个Controller进行处理。类图如下:
在这里插入图片描述

通过类图可以发现,DispatcherServlet实际上最终继承了HttpServlet,而HttpServlet是web容器启动的核心类,它有一个init()方法来做一些初始化操作。在HttpServletBean重写了init(),具体代码如下:
在这里插入图片描述

可以看到,在init()方法中,真正完成初始化容器动作的逻辑其实在initServletBean()方法中,而该方法的具体实现在其子类FrameworkServlet中。
在这里插入图片描述

从上面的代码可以明显看出,真正初始化web IoC容器是在initWebApplicationContext()方法中实现的。具体代码如下:

在这里插入图片描述
在这里插入图片描述

在这个方法中,建立了父子容器的关联关系,并最终调用了configureAndRefreshWebApplicationContext(cwac)方法来初始化IoC容器。方法最终调用了AbstractApplicationContext的refresh()方法。

父容器(Root
WebApplicationContext):对三层架构中的service层、dao层进行配置,如业务bean,数据源(DataSource)等。通常情况下,配置文件的名称为applicationContext.xml。在web应用中,其一般通过ContextLoaderListener来加载。

子容器(Servlet WebApplicationContext):对三层架构中的web层进行配置,如控制器(controller)、视图解析器(view resolvers)等相关的bean。通过spring mvc中提供的DispatchServlet来加载配置,通常情况下,配置文件的名称为spring-servlet.xml。

在这里插入图片描述
在这里插入图片描述

IoC容器初始化以后,最后调用了 DispatcherServlet的onRefresh()方法,在onRefresh()方法中又是直接调用 initStrategies()方法初始化 SpringMVC 的九大组件。
在这里插入图片描述

运行调用阶段

当前置的初始化完成之后,Spring MVC就可以接受并处理请求了。下面,我们从发起一个请求为例,详细介绍其执行过程。对于Spring MVC而言,所有请求都会经过DispatcherServlet的doService()方法。而doService()中的核心逻辑由 doDispatch()实现,源代码如下:
在这里插入图片描述

从源码可以看出,请求处理的核心步骤如下:

  1. 确定当前请求的处理器
  2. 获取当前请求的处理适配器
  3. 拦截器前置处理
  4. 真正调用处理器
  5. 拦截器后置处理

下面我们一个一个步骤进行分析。

确定当前请求的处理器

getHandler(HttpServletRequest request)源码如下:
在这里插入图片描述

从源码可以看出,直接遍历handlerMappings,第一个匹配中的HandlerMapping对象,再从HandlerMapping对象获取处理器。因此问题的关键就是handlerMappings有哪些?它们在实例化的时候又做了哪些准备。
我们知道,在初始化阶段会初始化九大组件,其中就有HandlerMapping。也就是在这个时刻,handlerMappings准备好了。HandlerMapping是一个接口,它有很多个实现,但在实际应用中,我们更常见的是RequestMappingHandlerMapping。下面我们就以RequestMappingHandlerMapping为例进行分析,其类图如下:
在这里插入图片描述

RequestMappingHandlerMapping本身也是Spring容器的一个Bean实例。从类图中可以看到,其抽象父类实现了AbstractHandlerMethodMapping实现了InitializingBean接口,因此可以肯定在它的afterPropertiesSet()实现中做了一些初始化处理。
在这里插入图片描述

在detectHandlerMethods(beanName)方法中实现了检测并注册处理器方法的方逻辑。在这里,建立了url和handler人以及method的映射关系。
继续回到HandlerMapping,在匹配到HandlerMapping,调用了它的getHandler()方法,具体实现在它的抽象父类AbstractHandlerMapping中,源码如下:
在这里插入图片描述

可以看到,最关键的步骤有2个:

第一步是根据request查找出具体的Handler(这里实际上是方法法处理器),这个很好理解。简单来说就是根据url查找前面注册好的Method和Handler(这里是指Controller实例),并包装成HandlerMethod返回。
第二步就是根据request查找对应的拦截器实例,并将处理器、拦截器包装成处理器执行链HandlerExecutionChain返回。
在这里插入图片描述

至此,已经确定了当前请求的处理器,它包含了方法处理器以及拦截器链。

获取当前请求的处理适配器

获取处理request的处理器适配器handler adapter。对于本例来说获取的是RequestMappingHandlerAdapter,不做过多赘述。

拦截器前置处理

执行拦截器HandlerInterceptor的前置处理方法preHandle()。

真正调用处理器

执行调用适配器AbstractHandlerMethodAdapter的handle()方法,进行真正的调用逻辑处理,该方法直接调用了子类的handleInternal()方法。
在这里插入图片描述

可以看到,最关键的是调用了invokeHandlerMethod()方法。
在这里插入图片描述

在invocableMethod.invokeAndHandle()中完成了Request中的参数和方法参数上数据的绑定。Spring MVC中提供两种Request参数到方法中参数的绑定方式:通过注解@RequestParam进行绑定、通过参数名称进行绑定。
使用注解进行绑定,我们只要在方法参数前面声明@RequestParam(“name”),就可以 将request中参数name的值绑定到方法的该参数上。使用参数名称进行绑定的前提是必须要获取方法中参数的名称。Spring基于ASM技术实现了运行时获取参数的实现。
在这里插入图片描述

到这里,方法的参数值列表也获取到了,方法反射调用也结束了,并且方法返回值也拿到了。后面就需要对返回结果进行处理了,继续跟进handleReturnValue()方法,对于常见的json响应体返回,实际调用了RequestResponseBodyMethodProcessor的实现方法:
在这里插入图片描述

这里根据实际的消息转换器,将方法返回值写入到response中,在执行完后续操作之后,直接将结果输出。
在这里插入图片描述

在执行真正的消息转换器写入之前,会先调用ResponseBodyAdvice的beforeBodyWrite()方法一些前置处理,在这里我们可以改写返回的body数据。

拦截器后置处理

执行拦截器HandlerInterceptor的后置处理方法postHandle()。

总结

总的来说,Spring MVC做的事情还是比较清晰的,在初始化阶段,主要会建立请求url与执行实例和执行方法的映射关系。在调用阶段,根据前面的映射关系,找到对应的方法执行,并将返回结果写入response中。当然,在这个过程中,为了解决各种复杂性的问题,引入了非常的设计来应对。但我们只要理解其核心实现即可,过于细节的内容可以先不管。
在这里插入图片描述

相关内容

热门资讯

linux入门---制作进度条 了解缓冲区 我们首先来看看下面的操作: 我们首先创建了一个文件并在这个文件里面添加了...
C++ 机房预约系统(六):学... 8、 学生模块 8.1 学生子菜单、登录和注销 实现步骤: 在Student.cpp的...
A.机器学习入门算法(三):基... 机器学习算法(三):K近邻(k-nearest neigh...
数字温湿度传感器DHT11模块... 模块实例https://blog.csdn.net/qq_38393591/article/deta...
有限元三角形单元的等效节点力 文章目录前言一、重新复习一下有限元三角形单元的理论1、三角形单元的形函数(Nÿ...
Redis 所有支持的数据结构... Redis 是一种开源的基于键值对存储的 NoSQL 数据库,支持多种数据结构。以下是...
win下pytorch安装—c... 安装目录一、cuda安装1.1、cuda版本选择1.2、下载安装二、cudnn安装三、pytorch...
MySQL基础-多表查询 文章目录MySQL基础-多表查询一、案例及引入1、基础概念2、笛卡尔积的理解二、多表查询的分类1、等...
keil调试专题篇 调试的前提是需要连接调试器比如STLINK。 然后点击菜单或者快捷图标均可进入调试模式。 如果前面...
MATLAB | 全网最详细网... 一篇超超超长,超超超全面网络图绘制教程,本篇基本能讲清楚所有绘制要点&#...
IHome主页 - 让你的浏览... 随着互联网的发展,人们越来越离不开浏览器了。每天上班、学习、娱乐,浏览器...
TCP 协议 一、TCP 协议概念 TCP即传输控制协议(Transmission Control ...
营业执照的经营范围有哪些 营业执照的经营范围有哪些 经营范围是指企业可以从事的生产经营与服务项目,是进行公司注册...
C++ 可变体(variant... 一、可变体(variant) 基础用法 Union的问题: 无法知道当前使用的类型是什...
血压计语音芯片,电子医疗设备声... 语音电子血压计是带有语音提示功能的电子血压计,测量前至测量结果全程语音播报࿰...
MySQL OCP888题解0... 文章目录1、原题1.1、英文原题1.2、答案2、题目解析2.1、题干解析2.2、选项解析3、知识点3...
【2023-Pytorch-检... (肆十二想说的一些话)Yolo这个系列我们已经更新了大概一年的时间,现在基本的流程也走走通了,包含数...
实战项目:保险行业用户分类 这里写目录标题1、项目介绍1.1 行业背景1.2 数据介绍2、代码实现导入数据探索数据处理列标签名异...
记录--我在前端干工地(thr... 这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 前段时间接触了Th...
43 openEuler搭建A... 文章目录43 openEuler搭建Apache服务器-配置文件说明和管理模块43.1 配置文件说明...