zl程序教程

您现在的位置是:首页 >  后端

当前栏目

【Spring MVC】Spring MVC实现原理(源码阅读)

Spring源码MVC原理 实现 阅读
2023-09-14 09:14:20 时间

前言

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中。当然,在这个过程中,为了解决各种复杂性的问题,引入了非常的设计来应对。但我们只要理解其核心实现即可,过于细节的内容可以先不管。
在这里插入图片描述