前言Spring MVC 请求流程
Spring MVC 两大阶段
初始化
HttpServletBean#init()frameworkServlet#initServletBeanframeworkServlet#initWebApplicationContextDispatchServlet#onRefreshSpring MVC 九大组件
MultipartResolverLocaleResolverThemeResolverHandlerMappingHandlerAdapterHandlerExceptionResolverRequestToViewNameTranslatorViewResolverFlashMapManager 处理请求
DispatcherServlet#doDispatchDispatcherServlet#getHandlerAbstractHandlerMapping#getHandlerAbstractHandlerMethodMapping#getHandlerInternalAbstractHandlerMethodMapping#lookupHandlerMethodAbstractHandlerMethodMapping 的初始化AbstractHandlerMethodMapping#initHandlerMethodsAbstractHandlerMethodMapping#detectHandlerMethodsAbstractHandlerMethodMapping#register 总结
前言对于 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 应用的上下文。
initWebApplicationContext() 方法主要就是为了找到一个上下文,找不到就会创建一个上下文,创建之后,最终会调用方法 configureAndRefreshWebApplicationContext(cwac) 方法,而这个方法最终在设置一些基本容器标识信息之后会去调用 refresh() 方法,也就是初始化 ioc 容器。
当调用 refresh() 方法初始化 ioc 容器之后,最终会调用方法 onRefresh(),这个方法也是一个模板钩子方法,由子类实现,也就是回到了我们 Spring MVC 的入口类 DispatcherServlet。
DispatchServlet#onRefreshonRefresh() 方法就是 Spring MVC 初始化的最后一个步骤,在这个步骤当中会初始化 Spring MVC 流程中可能需要使用到的九大组件。
这个组件比较熟悉,主要就是用来处理文件上传请求,通过将普通的 Request 对象包装成 MultipartHttpServletRequest 对象来进行处理。
LocaleResolverLocaleResolver 用于初始化本地语言环境,其从 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 是一个抽象类,其 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 对象,才能进行方法的调用
这个方法主要会从 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#initHandlerMethodsinitHandlerMethods 方法中,首先还是会从 Spring 的上下文中获取所有的 Bean,然后会进一步从带有 RequestMapping 注解和 Controller 注解中的 Bean 去解析并获得 HandlerMethod。
这个方法中,其实就是通过反射获取到 Controller 中的所有方法,然后调用 registerHandlerMethod 方法将相关信息注册到 MappingRegistry 对象中的各种 Map 集合之内:
registerHandlerMethod 方法中会直接调用 AbstractHandlerMethodMapping 对象持有的 mappingRegistry 对象中的 regidter 方法,这里会对 Controller 中方法上的一些元信息进行各种解析,比如参数,路径,请求方式等等,然后会将各种信息注册到对应的 Map 集合中,最终完成了整个初始化。
总结本文重点以 RequestMappingHandlerMapping 为例子分析了在 Spring 当中如何初始化 HandlerMethod,并最终在调用的时候又是如何根据 url 获取到对应的方法并进行执行最终完成整个流程。



