前言
关于 Web
应用程序而言,咱们从浏览器建议一个恳求,恳求经过一系列的分发和处理,终究会进入到咱们指定的办法之中,这一系列的的详细流程到底是怎么样的呢?
Spring MVC 恳求流程
记住在初入职场的时分,面试前经常会背一背 Spring MVC
流程,印象最深的便是一个恳求最早会经过 DispatcherServlet
进行分发处理,DispatcherServlet
便是咱们 Spring MVC
的进口类,下面便是一个恳求的大致流通流程(图片参阅自 Spring In Action
):
- 一个恳求过来之后会到达
DispatcherServlet
,但是DispatcherServlet
也并不知道这个恳求要去哪里。 -
DispatcherServlet
收到恳求之后会去查询处理器映射(HandlerMapping),从而依据浏览器发送过来的URL
解分出恳求终究应该调用哪个控制器。 - 到达对应控制器(Controller)之后,会完结一些逻辑处理,并且在处理完结之后会生成一些回来信息,也便是
Model
,然后还需要选择对应的视图名。 - 将模型(
Model
)和视图(View
)传递给对应的视图解析器(View Resolver),视图解析器会将模型和视图进行结合。 - 模型和视图结合之后就会得到一个完整的视图,终究将视图回来前端。
上面便是一个传统的完整的 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
获取到对应的办法并进行履行终究完结整个流程。