先上图:
SpringMVC 架构(详细图解)以及执行流程
1、SpringMVC 执行流程
SpringMVC 大体的执行流程图:
这张图只是展示出了 SpringMVC 的 MVC 三部分的处理情况,殊不知 SpringMVC 还有另外的三个组件,就是我们常说的 SpringMVC 三大组件:HandlerMapping 处理器映射器、HandlerAdapter 处理器适配器以及 ViewReslover 视图解析器,下面给出详细流程图:
这张图中我用了两种颜色的块状区域来表示,绿色表示 MVC 部分,橘色表示 SpringMVC 三大组件,这里共划分出了 11 个步骤,这里对这 11 个步骤进行一下说明:
1)用户发送请求至前端控制器 DispatcherServlet。
2)DispatcherServlet 收到请求调用 HandlerMapping 处理器映射器。
3) 处理器映射器根据请求 url 找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给 DispatcherServlet。
4) DispatcherServlet 通过 HandlerAdapter 处理器适配器调用处理器。
5) HandlerAdapter 执行处理器(handler,也叫后端控制器)。
6) Controller 执行完成返回 ModelAndView。
7) HandlerAdapter 将 handler 执行结果 ModelAndView 返回给 DispatcherServlet。
8) DispatcherServlet 将 ModelAndView 传给 ViewReslover 视图解析器。
9) ViewReslover 解析后返回具体 View 对象。
10) DispatcherServlet 对 View 进行渲染视图(即将模型数据填充至视图中)。
11) DispatcherServlet 响应用户。
2、思考几个问题
既然了解了 SpringMVC 的执行流程,那么我们提出几个问题一起来思考一下,这里我会提供源码阅读的入口,帮助读者更加深入的了解 SpringMVC 的执行流程,具体如何实现的,感兴趣的读者可以深入源码研究:
前端控制器是如何找到对应的处理器的?
在 web.xml 中配置的前端控制器,这里我们就从它入手进行探索,这里我们打开前段控制器 DispatcherServlet 类,找到一个叫做 doDispatch 的方法:
看到这个英文注释了吗,“为当前请求确定处理器”,这就是前端控制器查找对应处理器的入口,我们点进去看一下,会发现你看到了 HandlerMapping 组件,并调用了 getHandler 方法,处理完成后返回了一个 HandlerExecutionChain 对象,是不是和我们流程图中的2、3步对应了呢。具体是如何查找的,可以继续跟踪下去,这里就不再深入了。
前端控制器是如何执行对应的处理器的?
这个问题我们还是看 doDispatch 方法,如下图所示,第一处标记代码根据注释可以知道是“为当前请求确定处理器适配器”,就是对 Handler 做适配,真正执行处理器的代码在第二处标记处。
我们先来看一下是如何查找对应的适配器的,点进 getHandlerAdapter 方法看一下,就看到了有一个组件 HandlerAdapter,它就是根据 HandlerAdapter 的 supports 方法进行适配的,这个 supports 方法实现例子会在下面体现。
再来看一下如何执行处理器的,ha.handle 方法有很多实现类,这里我们以 SimpleControllerHandlerAdapter 为例,看一下它的源码,就十分清晰了。
前端控制器是如何根据视图名称解析视图对象的?
有很多开发人用在使用 SpringMVC 的时候会有这样一个疑惑,为什么我在 Controller 中配置上视图名称,SpringMVC 就能找到对应的页面呢?感到很神奇,这里我们就要用到视图解析器 ViewReslover 了。依旧是 doDispatch 方法,我们找到下图标记的代码(处理前端请求):我们点进去,就能看到一个 render 方法,render 就是“渲染” 的意思,继续深入进去
到这里是不是就用到了 viewName(视图名称)了,这个方法叫做 resolveViewName 解析视图名称,是不是看着很亲切,我们继续深入进去:
到这里就看到了 ViewResolver 了,然后循环遍历所有的 ViewResolver,如果能根据当前视图名称解析出视图对象,就直接将视图对象返回。具体是如何解析的,感兴趣的可以跟进到 viewResolver.resolveViewName 中进行查看(有多个实现类),这里就不再深入了。
默认配置
有很多读者看完上面三个问题后,就有疑问了,入门程序中并没有配置 SpringMVC 的三大组件,那么 SpringMVC 是如何完成工作的呢?其实 SpringMVC 是有默认配置的,这里我们一起看一下。
我们在 DispatherServlet 中找到 initStrategies 方法,如下图所示,可以看出这里初始化了很多配置,我们的三大组件都是在这里进行初始化的。
这里我们以 initHandlerMappings 方法进行讲解,找到 SpringMVC 的默认配置文件。进入 initHandlerMappings 方法,因为我们并没有进行配置(注解或者 Bean 标签),所以该方法中的前两种情况都会跳过,会来到最下面的默认情况处,调用了 getDefaultStrategies 方法,读取默认的配置文件。
在 getDefaultStrategies 方法中,有一个 defaultStrategies,我们当该类上面看一下:这里就是进行加载默认配置文件的地方,点击 DEFAULT_STRATEGIES_PATH 常量,找到了默认的配置配置文件。我们打开这个配置文件,就找到了我们想要的东西了。
该配置文件所处的位置如下所示:
常用组件说明
流程搞明白了,这里对用到的组件在进行一个汇总:
1、DispatcherServlet:前端控制器
用户请求到达前端控制器,它就相当于 MVC 模式中的 C,DispatcherServlet 是整个流程控制的中心,由它调用其它组件处理用户的请求,dispatcherServlet 的存在降低了组件之间的耦合性。
2、HandlerMapping:处理器映射器
HandlerMapping 负责根据用户请求找到 Handler 即处理器,SpringMVC 提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。
3、Handler:处理器
Handler 是继 DispatcherServlet 前端控制器的后端控制器,在 DispatcherServlet 的控制下Handler 对具体的用户请求进行处理。
由于 Handler 涉及到具体的用户业务请求,所以一般情况需要程序员根据业务需求开发Handler。
4、HandlAdapter:处理器适配器
通过 HandlerAdapter 对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。
5、View Resolver:视图解析器
View Resolve r负责将处理结果生成 View 视图,View Resolver 首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成 View 视图对象,最后对 View 进行渲染将处理结果通过页面展示给用户。
6、View:视图
SpringMVC 框架提供了很多的 View 视图类型的支持,包括:jstlView、freemarkerView、pdfView 等。我们最常用的视图就是jsp。
SpringMVC 三大组件的配置
1、使用 bean 标签的方式配置映射器和适配器
1)RequestMappingHandlerMapping:注解式处理器映射器,对类中标记@ResquestMapping 的方法进行映射,根据 ResquestMapping 定义的 url 匹配ResquestMapping 标记的方法,匹配成功返回 HandlerMethod 对象给前端控制器,HandlerMethod 对象中封装url对应的方法 Method。
2)RequestMappingHandlerAdapter:注解式处理器适配器,对标记 @ResquestMapping的方法进行适配。
3)配置如下:
<!--注解映射器 -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>
<!--注解适配器 -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>
2、使用 mvc 标签的方式配置映射器和适配器
springmvc.xml 配置文件中使用 <mvc:annotation-driven/> 标签替代 bean 标签配置的注解处理器和适配器。
<mvc:annotation-driven />
3、配置视图解析器
<!-- 配置视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
InternalResourceViewResolver:默认支持JSP视图解析
viewClass:JstlView 表示 JSP 模板页面需要使用 JSTL 标签库,所以 classpath 中必须包含jstl 的相关 jar 包。此属性可以不设置,默认为JstlView。
prefix 和 suffix:查找视图页面的前缀和后缀,最终视图的址为:前缀+逻辑视图名+后缀,逻辑视图名需要在 controller 中返回的 ModelAndView 指定,比如逻辑视图名为 hello,则最终返回的jsp视图地址 “WEB-INF/jsp/hello.jsp”
在入门程序中,我们返回视图是这样写的:
modelAndView.setViewName("/WEB-INF/jsp/goodsList.jsp");
添加了视图解析器配置后,就可以写成下面的样子:
modelAndView.setViewName("goodsList");