前言

关于 Web 应用程序而言,咱们从浏览器建议一个恳求,恳求经过一系列的分发和处理,终究会进入到咱们指定的办法之中,这一系列的的详细流程到底是怎么样的呢?

Spring MVC 恳求流程

记住在初入职场的时分,面试前经常会背一背 Spring MVC 流程,印象最深的便是一个恳求最早会经过 DispatcherServlet 进行分发处理,DispatcherServlet 便是咱们 Spring MVC 的进口类,下面便是一个恳求的大致流通流程(图片参阅自 Spring In Action):

小白都能看懂的 Spring 源码揭秘之Spring MVC

  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 中的办法进行一一映射存储,这样当收到恳求之后就能够处理恳求调用对应的办法,从而呼应恳求。

初始化

初始化进程的进口办法是 DispatchServletinit() 办法,而实际上 DispatchServlet 中并没有这个办法,所以咱们就持续寻找父类,会发现 init 办法在其父类(FrameworkServlet)的父类 HttpServletBean 中。

HttpServletBean#init()

在这个办法中,首先会去家在一些 Servlet 相关配置(web.xml),然后会调用 initServletBean() 办法,这个办法是一个空的模板办法,事务逻辑由子类 FrameworkServlet 来完结。

小白都能看懂的 Spring 源码揭秘之Spring MVC

FrameworkServlet#initServletBean

这个办法自身没有什么事务逻辑,首要是初始化 WebApplicationContext 目标,WebApplicationContext 继承自 ApplicationContext,首要是用来处理 web 应用的上下文。

小白都能看懂的 Spring 源码揭秘之Spring MVC

FrameworkServlet#initWebApplicationContext

initWebApplicationContext() 办法首要便是为了找到一个上下文,找不到就会创建一个上下文,创建之后,终究会调用办法 configureAndRefreshWebApplicationContext(cwac) 办法,而这个办法终究在设置一些根本容器标识信息之后会去调用 refresh() 办法,也便是初始化 ioc 容器。

小白都能看懂的 Spring 源码揭秘之Spring MVC

当调用 refresh() 办法初始化 ioc 容器之后,终究会调用办法 onRefresh(),这个办法也是一个模板钩子办法,由子类完结,也便是回到了咱们 Spring MVC 的进口类 DispatcherServlet

DispatchServlet#onRefresh

onRefresh() 办法便是 Spring MVC 初始化的最后一个进程,在这个进程傍边会初始化 Spring MVC 流程中或许需要使用到的九大组件。

小白都能看懂的 Spring 源码揭秘之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

小白都能看懂的 Spring 源码揭秘之Spring MVC

这个办法最关键的便是调用了 getHandler 办法,这个办法便是会获取到前面九大组件中的 HandlerMapping,然后进行反射调用对应的办法完结恳求,完结恳求之后后续还会经过视图转化之类的一些操作,终究回来 ModelAndView,不过现在都是前后端别离,根本也不需要用到视图模型,在这儿咱们就不剖析后续进程,首要便是剖析 HandlerMapping 的初始化和查询进程。

DispatcherServlet#getHandler

这个办法里面会遍历 handllerMappings,这个 handllerMappings 是一个 List 调集,由于 HandlerMapping 有多重完结,也便是 HandlerMapping 不止一个完结,其最常用的两个完结为 RequestMappingHandlerMappingBeanNameUrlHandlerMapping

小白都能看懂的 Spring 源码揭秘之Spring MVC

AbstractHandlerMapping#getHandler

AbstractHandlerMapping 是一个笼统类,其 getHandlerInternal 这个办法也是一个模板办法:

小白都能看懂的 Spring 源码揭秘之Spring MVC

getHandlerInternal 办法终究其会调用子类完结,而这儿的子类完结会有多个,其中最首要的便是 AbstractHandlerMethodMappingAbstractUrlHandlerMapping 两个笼统类,那么终究到底会调用哪个完结类呢?

这时分假如拿捏不准咱们就能够看一下类图,上面咱们说到,HandlerMapper 有两个十分首要的完结类:RequestMappingHandlerMappingBeanNameUrlHandlerMapping。那么咱们就分别来看一下这两个类的类图联系:

小白都能看懂的 Spring 源码揭秘之Spring MVC

小白都能看懂的 Spring 源码揭秘之Spring MVC

能够看到,这两个完结类的笼统父类正好对应了 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 目标,才能进行办法的调用

小白都能看懂的 Spring 源码揭秘之Spring MVC

AbstractHandlerMethodMapping#lookupHandlerMethod

这个办法首要会从 mappingRegistry 中获取射中的办法,获取之后还会经过一系列的判别比较判别比较,由于有些 url 会对应多个办法,而办法的恳求类型不同,比方一个 GET 办法,一个 POST 办法,或许其他一些属性不相同等等,都会导致终究射中到不同的办法,这些逻辑首要都是在 addMatchingMappings 办法去进一步完结,并终究将射中的结果加入到 matches 调集内。

小白都能看懂的 Spring 源码揭秘之Spring MVC

在这个办法中,有一个目标十分关键,那便是 mappingRegistry,由于终究咱们依据 url 到这儿获取到对应的 HandlerMtthod,所以这个目标很关键:

小白都能看懂的 Spring 源码揭秘之Spring MVC

看这个目标其实很明显能够看出来,这个目标其实仅仅保护了一些 Map 目标,所以咱们能够很简单猜测到,一定在某一个地方,将 urlHandlerMapping 或许 HandlerMethod 的映射联系存进来了,这时分其实咱们能够依据 getMappingsByUrl 办法来进行反推,看看 urlLookup 这个 Map 是什么时分被存入的,结合上面的类图联系,一路反推,很简单就能够找到这个 Map 中的映射联系是 AbstractHandlerMethodMapping 目标的 afterPropertiesSet 办法完结的(AbstractHandlerMethodMapping 完结了 InitializingBean 接口),也便是当这个目标初始化完结之后,咱们的 urlHandler 映射联系现已存入了 MappingRegistry 目标中的调集 Map 中。

AbstractHandlerMethodMapping 的初始化

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

AbstractHandlerMethodMapping#initHandlerMethods

initHandlerMethods 办法中,首先仍是会从 Spring 的上下文中获取一切的 Bean,然后会进一步从带有 RequestMapping 注解和 Controller 注解中的 Bean 去解析并取得 HandlerMethod

小白都能看懂的 Spring 源码揭秘之Spring MVC

AbstractHandlerMethodMapping#detectHandlerMethods

这个办法中,其实便是经过反射获取到 Controller 中的一切办法,然后调用 registerHandlerMethod 办法将相关信息注册到 MappingRegistry 目标中的各种 Map 调集之内:

小白都能看懂的 Spring 源码揭秘之Spring MVC

AbstractHandlerMethodMapping#register

registerHandlerMethod 办法中会直接调用 AbstractHandlerMethodMapping 目标持有的 mappingRegistry 目标中的 regidter 办法,这儿会对 Controller 中办法上的一些元信息进行各种解析,比方参数,途径,恳求方式等等,然后会将各种信息注册到对应的 Map 调集中,终究完结了整个初始化。

小白都能看懂的 Spring 源码揭秘之Spring MVC

总结

本文要点以 RequestMappingHandlerMapping 为比如剖析了在 Spring 傍边如何初始化 HandlerMethod,并终究在调用的时分又是如何依据 url 获取到对应的办法并进行履行终究完结整个流程。