WebMvc的架构设计与组件功能解析
最后更新于
最后更新于
首先,我们先回忆一下,我们在 WebMvc 基础阶段,我们做的主要工作是什么。
是不是编写 Controller ,编写标注有 @RequestMapping
注解的方法,返回视图或者响应 json 呀。那它的背后一定是有一些组件支撑的,我们展开来看。
这是我们最最熟悉的 WebMvc 的组件了,它统一接收客户端(浏览器)的所有请求,并根据请求的 uri ,转发给我们编写好的 Controller 中的方法。不过这个匹配寻找,和请求转发的活,还有返回视图、响应 json 数据的活,都不是 DispatcherServlet
干的,而是委托给另外的组件来干,而这些组件的工作,与 DispatcherServlet
共同协作,完成整个 mvc 的工作。
这里需要小伙伴们先有一个意识:SpringWebMvc 中对于 WebMvc 的整个工作处理,它的组件划分是很清晰的,完全是各司其职,下面我们介绍核心组件的时候,各位可以试着体会一下。
在说 SpringWebMvc 内部的组件之前,咱先说说 Handler 这个概念。其实我们写的那些 Controller 中的方法,一个标注了 @RequestMapping
注解的方法,就是一个 Handler 。所以之前在 WebMvc 基础中,小册一直都是刻意避免直接说 Handler 这个词,而是用 “Controller 中的方法” 的概念代替,不过到了这里小册也就没必要再拘束了,各位应该知道,我们写的那些方法,实际上都是在定义一个一个的 Handler 。
很明显,Handler 中要做的事情,就是处理客户端发来的请求,并声明响应视图 / 响应 json 数据,这都是我们之前已经写过了的。DispatcherServlet
在接收到请求后,只要能匹配到我们声明的那些 @RequestMapping
,最终都会将这些请求转给我们写的 Handler 。
用图来表示的话,大概可以简单理解为这样子:
上面说了,WebMvc 的组件都是各司其职,根据 @RequestMapping
去找 Handler 这个活,DispatcherServlet
不会自己去干,而是委托给一个叫 **HandlerMapping**
的东西来负责。HandlerMapping
意为处理器映射器,它的作用就是根据 uri ,去匹配查找能处理的 Handler ,具体可看下图:
注意,这里 HandlerMapping
查找到可以处理请求的 Handler 之后,并不是立即将其返回,而是组合了一个 HandlerExecutionChain
,它的作用是什么呢?它为什么要额外封装一层呢?
来,思考,前面我们学过拦截器了,如果一个请求要被拦截器处理的话,那是不是在执行 Handler 之前,需要先执行这些拦截器呢?哎对了,HandlerMapping
在封装的时候就考虑到这一点了,于是它把当前这次请求,会涉及到的拦截器,和 Handler 一起封装起来,组成一个 HandlerExecutionChain
的对象,交给 DispatcherServlet
。
DispatcherServlet
拿到 HandlerExecutionChain
后,接下来要执行这些拦截器和 Handler 了吧,它也不自己执行,而是又交给一个 **HandlerAdapter**
去执行。这个 HandlerAdapter
意为处理器适配器,它的作用就是执行上面封装好的 HandlerExecutionChain
。加入 HandlerAdapter
后的流程图如下:
注意看 HandlerAdapter
与 Handler 的交互:执行 Handler 之后,虽然我们写的返回值基本都是返回视图名称,或者借助 @ResponseBody
响应 json 数据,但在 WebMvc 的框架内部,最终都是封装了一个 ModelAndView
对象,返回给 HandlerAdapter
。HandlerAdapter
再把这个 ModelAndView
对象交给 DispatcherServlet
,这部分的活也就干完了。
DispatcherServlet
拿到 HandlerAdapter
返回的 ModelAndView
之后,接下来要响应视图了,这个活它还是不干,而是交给了 ViewResolver
来做。ViewResolver
会根据 ModelAndView
中存放的视图名称,去预先配置好的位置去找对应的视图文件( .jsp 、.html 等),并进行实际的视图渲染。渲染完成后,将视图响应给 DispatcherServlet
。具体的流程可继续绘制如下:
画到这里,其实 DispatcherServlet
的核心工作流程机制也就出来了,这样看来,DispatcherServlet
自己啥具体活都没干,全部都是委托给别的组件干活,由此各位是不是能比较容易得感受到 WebMvc 中组件的职责分明了呢?
下面,我们分述这里面的核心组件,它们具体的功能。
上面咱刚说了,HandlerMapping
是处理器映射器,使用它,可以根据请求的 uri ,找到能处理该请求的一个 Handler ,并组合可能匹配的拦截器,包装为一个 HandlerExecutionChain
对象返回出去。它的接口定义刚刚好就是符合这个描述:
它有几个主要的落地实现,咱可以简单了解一下:
**RequestMappingHandlerMapping**
:支持 @RequestMapping
注解的处理器映射器【最最常用】 ,这种 HandlerMapping 就是我们前面用的,不需要显式注册也可以。
BeanNameUrlHandlerMapping
:使用 bean 的名称作为 Handler 接收请求路径的处理器映射器 ,使用该方式,需要我们编写的 Controller 类实现 Controller
接口(对,WebMvc 中有一个名为 Controller
的接口)。下面是一个简单的示例:
因为使用该方式编写的 Controller ,一个类只能接收一个请求路径,已被淘汰开发中不使用。
SimpleUrlHandlerMapping
:集中配置请求路径与 Controller 的对应关系的处理器映射器,这种 HandlerMapping
可以配置请求路径,与 Controller 的映射关系,例如下面是一个简单的 SimpleUrlHandlerMapping
配置:
使用该方式,依然需要编写的 Controller 类实现接口,并且还需要额外配置 uri 与 Controller 的映射关系,因此也被淘汰了。
后两种的编写都是很早期的了,自打 SpringFramework 升级到 3.0 ,全面支持注解驱动开发之后,这种传统的方式用的就越来越少了,我们也没必要去深入了解它们了,只需要记住 RequestMappingHandlerMapping
就好。
HandlerAdapter
,处理器适配器,从接口名上就能得知,它蕴含着一个适配器模式。
所谓适配器模式,我们可以简单的理解为,将一堆不兼容的东西,通过一个中间的 “桥梁” 将这些东西整合好。比方说现实中的 U 盘与 TF 卡,U 盘和 TF 卡都是外置闪存设备,但是 U 盘可以直接插入 USB 口,TF 卡不行,这个时候就需要一个读卡器做中间的 “桥梁” 了:TF 卡插到读卡器里,读卡器就可以插入 USB 口了。
那话说回来,HandlerAdapter
中是如何体现适配器模式呢?
刚才咱说了,Handler 的编写不止 @Controller
+ @RequestMapping
的方式,还有实现 Controller
接口的方式(当然可能还有别的方式)。咱就说这两种吧,如何用一个接口同时兼顾这两种方式呢?很明显就要用适配器了吧。先以 Object 接收进来,再针对不同的 Handler 编写方式,背后找对应的 HandlerAdapter
实现即可。
SpringWebMvc 中,对于 HandlerAdapter
的核心实现主要有以下几种:
**RequestMappingHandlerAdapter**
:基于 @RequestMapping
的处理器适配器,底层使用反射调用 Handler 的方法【最最常见】
SimpleControllerHandlerAdapter
:基于 Controller
接口的处理器适配器,底层会将 Handler 强转为 Controller
,调用其 handleRequest
方法
SimpleServletHandlerAdapter
:基于 Servlet
的处理器适配器,底层会将 Handler 强转为 Servlet
,调用其 service
方法
可以发现,WebMvc 中还是兼顾到了多种编写 Handler 的方式,不过我们只需要记住 RequestMappingHandlerAdapter
就好。
注意 Servlet
对于 SpringWebMvc 来讲,也可以是一个 Handler ,并且还为之提供了适配器!这就意味着,在 SpringWebMvc 整合的工程中,我们可以把 Servlet
直接注入到 SpringWebMvc 的 IOC 容器中,而不需要通过 web.xml
或者 @WebServlet
的方式注册到 ServletContext
中!
当然,如果使用 Servlet 的话,@Controller
和 @RequestMapping
就没法用了,那就只能像上面那样,利用 BeanNameUrlHandlerMapping
或者 SimpleUrlHandlerMapping
声明请求映射路径,这样看来编码复杂度反而更高了,所以这种写法在实际开发中就不要去用了,更多的可能是整合其他框架用(让其他框架中的 Servlet 加入到 SpringWebMvc 的 IOC 容器使统一管理)。
ViewResolver
,视图解析器,顾名思义它是解析和生成视图用的。
默认情况下,WebMvc 只会初始化一个 ViewResolver
,那就是我们之前在刚开始学习 WebMvc 基础的时候用的 InternalResourceViewResolver
。这个类继承自 UrlBasedViewResolver
,它可以方便的声明页面路径的前后缀,以方便我们在返回视图( jsp 页面)的时候编写视图名称。
除了 InternalResourceViewResolver
之外,SpringWebMvc 还为一些模板引擎提供了支持类,比方说 FreeMarker → FreeMarkerViewResolver
,Groovy XML → GroovyMarkupViewResolver
等,如果小伙伴有学过相关。
可能有的小伙伴会产生疑惑,Controller 不止可以跳转视图,还可以响应 json 数据呀,那响应 json 数据肯定不是 View 来处理,那会是谁呢?
哎,产生这个疑惑不怪你,因为这涉及到 DispatcherServlet
工作的内部原理了,咱这里可以先简单说一下:当 Handler 执行完毕后,SpringWebMvc 会根据当前 Handler 以及返回值,决定使用何种方式响应。如果我们在编写的 Controller 方法中没有标注 @ResponseBody
注解,而且返回 String
,则 SpringWebMvc 会认为要跳转页面,于是就去让 ViewResolver
处理去了;而如果方法上,或者整个 Controller 类上标注了 @RestController
或者 @ResponseBody
,则 SpringWebMvc 就不会再去找 ViewResolver
,而是转头去找 jackson 了,这个时候 ViewResolver
直接是不工作的。
听上去有点懵懵的是吧,没关系,到后面我们看 DispatcherServlet
的工作流程原理时,还会详细展开的。