详解SpringMVC执行原理

图为SpringMVC的一个比较完整的流程图,实线表示SpringMVC框架提供的技术,不需要开发者实现,虚线表示需要开发者实现。

简要分析执行流程

  1. DIspatcherServlet表示前置控制器,是整个SpringMVC的控制中心。用户发出请求,DIspatcherServlet接收请求并拦截请求。我们假设请求的url为:localhost:8080/SpringMVC/hello。如上url拆分成三部分:

    • http:localhost:8080服务器域名
    • SpringMVC部署在服务器上的web站点
    • hello表示控制器

    通过分析,如上url表示为:请求位于服务器localhost:8080上的SpringMVC站点的hello控制器。

  2. HandlerMapping为处理器映射。DIspatcherServlet调用HandleMapping,HandlerMapping根据请求url查找Handler。

  3. HandlerExecution表示具体的的Handler,其主要作用是根据url查找控制器,如上url被查找控制器为:hello。

  4. HandlerExecution将解析后的信息传递给DIspatcherServlet,如解析控制器映射等。

  5. HandlerAdapter表示处理器适配器,其按照特定的规则去执行Handler。

  6. Handler让具体的Controller执行

  7. Controller将具体的执行信息返回给HandlerAdapter,如ModelAndView。

  8. HandlerAdapter将视图逻辑名或模型传给DIspatcherServlet

  9. DIspatcherServlet调用视图解析器(ViewResolver)来解析HandlerAdapter传递的逻辑视图名。

  10. 视图解析器将解析的逻辑视图名传给DIspatcherServlet

  11. DIspatcherServlet根据视图解析器解析的视图结果,调用具体的视图。

  12. 最终视图呈现给用户

关于SpringMVC的配置(结合上面的流程来理解)

  1. 首先在web.xml中配置DIspatcherServlet

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
     <servlet>
    <servlet-name>springmvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    //绑定文件,方便以后直接使用
    <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
    //resource下的applicationContext.xml
    </init-param>

    <load-on-startup>1</load-on-startup>
    //用户发出的请求都会被它接收、拦截
    </servlet>
    <servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <url-pattern>/</url-pattern>
    </servlet-mapping>
  2. 对于Spring-mvc.xml的配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/mvc
    https://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <!--1.note driver-->
    <mvc:annotation-driven/>
    <!--2.static resource filter-->
    <mvc:default-servlet-handler/>
    <!--3.scan controller-->
    <context:component-scan base-package="com.hu.controller"/>
    <!--view-->
    <!--视图解析器
    1.获取了ModelAndView的数据
    2.解析ModelAndView的视图名字
    3.拼接视图名字、找到对应的视图
    4.将数据渲染到视图上-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/jsp/"/>
    <property name="suffix" value=".jsp"/>
    </bean>

    </beans>
    • 解释7、8、9、10、11步:
    1. 在Controller中return了一个视图(“login”),并且通过Model返回了数据(也可以说是ModelAndView,比如一个list集合)。HandlerAdapter把这些信息给了DIspatcherServlet(model:一个list集合,view:”login” – 这个login是jsp文件名,也就是自己写的小页面。而model带的list数据将会传递给login这个页面)。
    2. DIspatcherServlet调用视图解析器(ViewResolver)来解析HandlerAdapter传递的逻辑视图名。说人话就是,DIspatcherServlet调用了一个小弟,这个小弟是用来拼接url的。在上述的Spring-mvc.xml文件中,你可以在标签中找到这个小弟。它有两个属性,一个是prefix,另一个是suffix。字面意思理解一下,pre-前面,fix-修复。所以它就是”修复残缺的url”的工具。prefix修复前缀,suffix修复后缀。比如,DIspatcherServlet拿来刚才HandlerAdapter传递的逻辑视图名传递的”login”,把”login”送给小弟(ViewResolver)去修复一下url(拼接url)。拼接好了之后,按照如上的bean配置,得到的url即为:/WEB-INF/jsp/login.jsp。小弟搞好了之后再把它交给大哥DIspatcherServlet。
    3. DIspatcherServlet根据视图解析器解析的视图结果,调用具体的视图。收到了修补后的url,DIspatcherServlet根据这个url帮我们进行了网页的跳转。所谓的呈现具体视图,就是网页~
    • 解释1、2、3、4、5、6:
    1. 用户通过发起了请求:”喂!服务器!你TM的醒醒!我要去你那儿访问个东西!url是:”localhost:8080/SpringMVC/hello”,你最好赶快帮我找!”。
    2. 服务器一看:”8080,这不就是老子的地址嘛!SpringMVC,这不是我罩着的web文件夹吗!文件夹里面都是web的东西,什么jsp啦,css,什么的。我再仔细定睛一看,你TM的这个小用户,原来是想找hello这个Controller来帮你做事情啊!”
    3. 这个时候呢,DIspatcherServlet这个大哥就让HandlerMapping这个东西根据用户发来的url查找对应的handler。handler去找到底有没有用户想找的controller(hello)。找到了就返回给DIspatcherServlet,然后DIspatcherServlet大哥再通过HandlerAdapter来调用Controller。
    • 整合解释:
    1. 老王想去p站看小电影,他之前不满于平民的视频清晰度,为此老王充了会员。现在尊贵的会员要登录p站看小电影了。他在输入框内敲下了奢华的账号与密码,点击”登录”!
    2. 就在这个平平无奇的点击登录操作背后,后端上演的是一场狂风暴雨与惊涛骇浪。当然了,它也蕴藏着无数程序员的夜不能寐和他们所剩无几的几根倔强与坚强。
    3. 在前端input标签内,action属性剑指请求,method里面是卑微的请求方法(毕竟要跟服务器大哥说话,还是放尊重点用个post吧!)。当尊贵的小王点击登录按钮后,前端的submit脑海中一阵电流钻过,请求发过去了。
    4. 服务器大哥(DIspatcherServlet)看到了请求,”localhost:8080/SpringMVC/hello”。这个请求曾原模原样的写在action里面。服务器大哥(DIspatcherServlet)开始分析用户的请求了:”emm…8080是我,Tomcat本尊所在的地址(这地址其实是开发者分给我的,我多么想在8488端口啊,如果有的话…!就是没这王者的命啊!)。而SpringMVC是本服务器下面的一个文件夹,里面是开发者曾部署给我的web文件,等会我的操作还得靠里面的文件嘞!!至于hello嘛,是用户想找的Controller,用它来实现某种功能!”。服务器大哥分析完了之后,就把这种事交给手下的小弟去做了。大哥吆喝了几个小弟,他们是HandlerMapping、HandlerAdapter、ViewResolver。这些小弟开始干活了,小弟们实现了各种操作,他们要做的就是把做完的事报告给大哥,然后让大哥在顶层做决策。守得云开见月明,大哥看到所有操作都over了,就返给了用户一个结果。
    5. 老王一看,我擦,密码错误!?重新输入密码,还是不对!TMD,你知不知道反复做这种操作后端都经历了什么啊!老王瘫在椅子上,好家伙,自己花了999软妹币充值的十年vip账号让别人盗了!事后老王惆怅不已,在家书里写下了这样一段话:”吾子孙,万不可玩p站。”

    厘清一些误解:

    上述url中的地址,并不是web站点的目录,它实际上是在通过Controller来访问web站点的资源。这一点很牛逼哦!因为你无法直接通过目录形式访问得到web资源,你得经过Controller!否则,想登录p站,门都没有。