一文彻底解密Spring 源码之Spring MVC

  • Post author:
  • Post category:其他




前言

对于

Web

应用程序而言,我们从浏览器发起一个请求,请求经过一系列的分发和处理,最终会进入到我们指定的方法之中,这一系列的的具体流程到底是怎么样的呢?



Spring MVC 请求流程

记得在初入职场的时候,面试前经常会背一背

Spring MVC

流程,印象最深的就是一个请求最先会经过

DispatcherServlet

进行分发处理,

DispatcherServlet

就是我们

Spring MVC

的入口类,下面就是一个请求的大致流转流程(图片参考自

Spring In Action

):

在这里插入图片描述

  1. 一个请求过来之后会到达

    DispatcherServlet

    ,但是

    DispatcherServlet

    也并不知道这个请求要去哪里。

  2. DispatcherServlet

    收到请求之后会去查询处理器映射(HandlerMapping),从而根据浏览器发送过来的

    URL

    解析出请求最终应该调用哪个控制器。
  3. 到达对应控制器(Controller)之后,会完成一些逻辑处理,而且在处理完成之后会生成一些返回信息,也就是

    Model

    ,然后还需要选择对应的视图名。
  4. 将模型(

    Model

    )和视图(

    View

    )传递给对应的视图解析器(View Resolver),视图解析器会将模型和视图进行结合。
  5. 模型和视图结合之后就会得到一个完整的视图,最终将视图返回前端。

上面就是一个传统的完整的

Spring MVC

流程,为什么要说这是传统的流程呢?因为这个流程是用于前后端没有分离的时候,后台直接返回页面给浏览器进行渲染,而现在大部分应用都是前后端分离,后台直接生成一个

Json

字符串就直接返回前端,不需要经过视图解析器进行处理,也就是说前后端分离之后,流程就简化成了

1-2-3-4-7

(其中第四步返回的一般是 Json 格式数据)。



Spring MVC 两大阶段

Spring MVC主要可以分为两大过程,一是初始化,二就是处理请求。初始化的过程主要就是将我们定义好的

RequestMapping

映射路径和

Controller

中的方法进行一一映射存储,这样当收到请求之后就可以处理请求调用对应的方法,从而响应请求。



初始化

初始化过程的入口方法是

DispatchServlet



init()

方法,而实际上

DispatchServlet

中并没有这个方法,所以我们就继续寻找父类,会发现

init

方法在其父类(FrameworkServlet)的父类

HttpServletBean

中。



HttpServletBean#init()

在这个方法中,首先会去家在一些 Servlet 相关配置(web.xml),然后会调用

initServletBean()

方法,这个方法是一个空的模板方法,业务逻辑由子类

FrameworkServlet

来实现。

在这里插入图片描述



FrameworkServlet#initServletBean

这个方法本身没有什么业务逻辑,主要是初始化

WebApplicationContext

对象,

WebApplicationContext

继承自

ApplicationContext

,主要是用来处理

web

应用的上下文。

在这里插入图片描述



FrameworkServlet#initWebApplicationContext


initWebApplicationContext()

方法主要就是为了找到一个上下文,找不到就会创建一个上下文,创建之后,最终会调用方法

configureAndRefreshWebApplicationContext(cwac)

方法,而这个方法最终在设置一些基本容器标识信息之后会去调用

refresh()

方法,也就是

初始化

ioc

容器



在这里插入图片描述

当调用

refresh()

方法初始化

ioc

容器之后,最终会调用方法

onRefresh()

,这个方法也是一个模板钩子方法,由子类实现,也就是回到了我们

Spring MVC

的入口类

DispatcherServlet



DispatchServlet#onRefresh


onRefresh()

方法就是

Spring MVC

初始化的最后一个步骤,在这个步骤当中会初始化

Spring MVC

流程中可能需要使用到的九大组件。

在这里插入图片描述



Spring MVC 九大组件



MultipartResolver

这个组件比较熟悉,主要就是用来处理文件上传请求,通过将普通的

Request

对象包装成

MultipartHttpServletRequest

对象来进行处理。



LocaleResolver


LocaleResolver

用于初始化本地语言环境,其从

Request

对象中解析出当前所处的语言环境,如中国大陆则会解析出

zh-CN

等等,模板解析以及国际化的时候都会用到本地语言环境。



ThemeResolver

这个主要是用户主题解析,在

Spring MVC

中,一套主题对应一个

.properties

文件,可以存放和当前主题相关的所有资源,如图片,css样式等。



HandlerMapping

用于查找处理器(

Handler

),比如我们

Controller

中的方法,这个其实最主要就是用来存储

url

和 调用方法的映射关系,存储好映射关系之后,后续有请求进来,就可以知道调用哪个

Controller

中的哪个方法,以及方法的参数是哪些。



HandlerAdapter

这是一个适配器,因为

Spring MVC

中支持很多种

Handler

,但是最终将请求交给

Servlet

时,只能是

doService(req,resp)

形式,所以

HandlerAdapter

就是用来适配转换格式的。



HandlerExceptionResolver

这个组件主要是用来处理异常,不过看名字也很明显,这个只会对处理

Handler

时产生的异常进行处理,然后会根据异常设置对应的

ModelAndView

,然后交给

Render

渲染成页面。



RequestToViewNameTranslator

这个主键主要是从

Request

中获取到视图名称。



ViewResolver

这个组件会依赖于

RequestToViewNameTranslator

组件获取到的视图名称,因为视图名称是字符串格式,所以这里会将字符串格式的视图名称转换成为

View

类型视图,最终经过一系列解析和变量替换等操作返回一个页面到前端。



FlashMapManager

这个主键主要是用来管理

FlashMap

,那么

FlashMap

又有什么用呢?要明白这个那就不得不提到重定向了,有时候我们提交一个请求的时候会需要重定向,那么假如参数过多或者说我们不想把参数拼接到

url

上(比如敏感数据之类的),这时候怎么办呢?因为参数不拼接在

url

上重定向是无法携带参数的。


FlashMap

就是为了解决这个问题,我们可以在请求发生重定向之前,将参数写入

request

的属性

OUTPUT_FLASH_MAP_ATTRIBUTE

中,这样在重定向之后的

handler

中,

Spring

会自动将其设置到

Model

中,这样就可以从

Model

中取到我们传递的参数了。



处理请求

在九大组件初始化完成之后,

Spring MVC

的初始化就完成了,接下来就是接收并处理请求了,那么处理请求的入口在哪里呢?处理请求的入口方法就是

DispatcherServlet

中的

doService

方法,而

doService

方法又会调用

doDispatch

方法。



DispatcherServlet#doDispatch

在这里插入图片描述

这个方法最关键的就是调用了

getHandler

方法,这个方法就是会获取到前面九大组件中的

HandlerMapping

,然后进行反射调用对应的方法完成请求,完成请求之后后续还会经过视图转换之类的一些操作,最终返回

ModelAndView

,不过现在都是前后端分离,基本也不需要用到视图模型,在这里我们就不分析后续过程,主要就是分析

HandlerMapping

的初始化和查询过程。



DispatcherServlet#getHandler

这个方法里面会遍历

handllerMappings

,这个

handllerMappings

是一个

List

集合,因为

HandlerMapping

有多重实现,也就是

HandlerMapping

不止一个实现,其最常用的两个实现为

RequestMappingHandlerMapping



BeanNameUrlHandlerMapping



在这里插入图片描述



AbstractHandlerMapping#getHandler


AbstractHandlerMapping

是一个抽象类,其

getHandlerInternal

这个方法也是一个模板方法:

在这里插入图片描述


getHandlerInternal

方法最终其会调用子类实现,而这里的子类实现会有多个,其中最主要的就是

AbstractHandlerMethodMapping



AbstractUrlHandlerMapping

两个抽象类,那么最终到底会调用哪个实现类呢?

这时候如果拿捏不准我们就可以看一下类图,上面我们提到,

HandlerMapper

有两个非常主要的实现类:

RequestMappingHandlerMapping



BeanNameUrlHandlerMapping

。那么我们就分别来看一下这两个类的类图关系:

在这里插入图片描述

在这里插入图片描述

可以看到,这两个实现类的抽象父类正好对应了

AbstractHandlerMapping

的两个子类,所以这时候具体看哪个方法,那就看我们想看哪种类型了。

  • RequestMappingHandlerMapping:主要用来存储

    RequestMapping

    注解相关的控制器和

    url

    的映射关系。

  • BeanNameUrlHandlerMapping:主要用来处理

    Bean name

    直接以

    /

    开头的控制器和

    url

    的映射关系。

其实除了这两种

HandlerMapping

之外,

Spring

中还有其他一些

HandllerMapping

,如

SimpleUrlHandlerMapping

等。

提到的这几种

HandlerMapping

,对我们来说最常用,最熟悉的那肯定就是

RequestMappingHandlerMapping

,在这里我们就以这个为例来进行分析,所以我们应该



AbstractHandlerMethodMapping#getHandlerInternal

这个方法本身也没有什么逻辑,其主要的核心查找

Handler

逻辑在

lookupHandlerMethod

方法中,这个方法主要是为了获取一个

HandlerMethod

对象,前面的方法都是

Object

,而到这里变成了

HandlerMethod

类型,这是因为

Handler

有各种类型,目前我们已经基本跟到了具体类型之下,所以类型就变成了具体类型,而如果我们看的的另一条分支线,那么返回的就会是其他对象,正是因为支持多种不同类型的

HandlerMapping

对象,所以最终为了统一执行,才会需要在获得

Hanlder

之后,

DispatcherServlet

中会再次通过调用

getHandlerAdapter

方法来进一步封装成

HandlerAdapter

对象,才能进行方法的调用

在这里插入图片描述



AbstractHandlerMethodMapping#lookupHandlerMethod

这个方法主要会从

mappingRegistry

中获取命中的方法,获取之后还会经过一系列的判断比较判断比较,因为有些

url

会对应多个方法,而方法的请求类型不同,比如一个

GET

方法,一个

POST

方法,或者其他一些属性不相同等等,都会导致最终命中到不同的方法,这些逻辑主要都是在

addMatchingMappings

方法去进一步实现,并最终将命中的结果加入到

matches

集合内。

在这里插入图片描述

在这个方法中,有一个对象非常关键,那就是

mappingRegistry

,因为最终我们根据

url

到这里获取到对应的

HandlerMtthod

,所以这个对象很关键:

在这里插入图片描述

看这个对象其实很明显可以看出来,这个对象其实只是维护了一些

Map

对象,所以我们可以很容易猜测到,一定在某一个地方,将

url



HandlerMapping

或者

HandlerMethod

的映射关系存进来了,这时候其实我们可以根据

getMappingsByUrl

方法来进行反推,看看

urlLookup

这个

Map

是什么时候被存入的,结合上面的类图关系,一路反推,很容易就可以找到这个

Map

中的映射关系是

AbstractHandlerMethodMapping

对象的

afterPropertiesSet

方法实现的(

AbstractHandlerMethodMapping

实现了

InitializingBean

接口),也就是当这个对象初始化完成之后,我们的

url



Handler

映射关系已经存入了

MappingRegistry

对象中的集合

Map

中。



AbstractHandlerMethodMapping 的初始化


afterPropertiesSet

方法中并没有任何逻辑,而是直接调用了

initHandlerMethods



AbstractHandlerMethodMapping#initHandlerMethods


initHandlerMethods

方法中,首先还是会从

Spring

的上下文中获取所有的

Bean

,然后会进一步从带有

RequestMapping

注解和

Controller

注解中的

Bean

去解析并获得

HandlerMethod



在这里插入图片描述



AbstractHandlerMethodMapping#detectHandlerMethods

这个方法中,其实就是通过反射获取到

Controller

中的所有方法,然后调用

registerHandlerMethod

方法将相关信息注册到

MappingRegistry

对象中的各种

Map

集合之内:

在这里插入图片描述



AbstractHandlerMethodMapping#register


registerHandlerMethod

方法中会直接调用

AbstractHandlerMethodMapping

对象持有的

mappingRegistry

对象中的

regidter

方法,这里会对

Controller

中方法上的一些元信息进行各种解析,比如参数,路径,请求方式等等,然后会将各种信息注册到对应的

Map

集合中,最终完成了整个初始化。

在这里插入图片描述



总结

本文重点以

RequestMappingHandlerMapping

为例子分析了在

Spring

当中如何初始化

HandlerMethod

,并最终在调用的时候又是如何根据

url

获取到对应的方法并进行执行最终完成整个流程。



版权声明:本文为zwx900102原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。