文章目录
前言
Spring MVC是当前web应用使用最为广泛的框架,本文将会从源码角度详细分析Spring MVC实现原理。
Spring MVC基本流程
在具体讲解之前,我们先从宏观上简单介绍Spring MVC的处理流程,了解用户从发起请求到获得响应的完整流程。
流程简介:
-
用户发起请求到DispatcherServlet(前端控制器)
-
DispatcherServlet通过HandlerMapping(处理映射器)寻找用户要请求的Handler
-
HandlerMapping返回执行链,包含两部分内容:
- 处理器对象:Handler
- HandlerInterceptor(拦截器)的集合
-
前端控制器通过HandlerAdapter(处理器适配器)对Handler进行适配包装
-
调用包装后的Handler中的方法,处理业务
-
处理业务完成,返回ModelAndView对象,包含两部分
- Model:模型数据
- View:视图名称,不是真正的视图
-
DispatcherServlet获取处理得到的ModelAndView对象
-
DispatcherServlet将视图名称交给ViewResolver(视图解析器),查找视图
-
ViewResolver返回真正的视图对象给DispatcherServlet
-
DispatcherServlet把Model(数据模型)交给视图对象进行渲染
-
返回渲染后的视图
-
将最终的视图返回用户,产生响应
Spring MVC九大组件
HandlerMapping
HandlerMapping
是用来查找Handler
的,也就是处理器,具体表现形式可以是类也 可以是方法。比如,标注了@RequestMapping
的每个method
都可以看成是一个Handler
,由Handler
来负责实际的请求处理。HandlerMapping
在请求到达之后, 它的作用便是找到请求相应的处理器Handler
和Interceptors
。
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
在上面我们有看到ViewResolver
的resolveViewName
()方法,需要两个参数。那么第二个参数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()实现,源代码如下:
从源码可以看出,请求处理的核心步骤如下:
- 确定当前请求的处理器
- 获取当前请求的处理适配器
- 拦截器前置处理
- 真正调用处理器
- 拦截器后置处理
下面我们一个一个步骤进行分析。
确定当前请求的处理器
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中。当然,在这个过程中,为了解决各种复杂性的问题,引入了非常的设计来应对。但我们只要理解其核心实现即可,过于细节的内容可以先不管。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/132178.html