之前已经分析过了Spring的IOC(《零基础带你看Spring源码——IOC控制反转》)与AOP(《从源码入手,一文带你读懂Spring AOP面向切面编程》)的源码,本次就来分析下SpringMVC。本文先简述下目前SpringMVC的使用情况,然后通过Demo的简单让大家有一个初步的使用印象,然后带着印象去看其中执行的分发源码。

到底什么是Spring MVC,我们还在用吗?

Spring MVC,官方名字其实是Spring Web MVC,Maven上的包名也是spring-webmvc。从Spring诞生以来,它就是一款基于Servlet Api的web架构。值得一提的是,在Spring5的时候,出了一款新的Web架构,Flux,是基于事件驱动模型(类似nodejs)做的。以后会写一篇来专门介绍一下Flux,敬请关注。

MVC,可以说是“上个世纪”最流行的前后端交互模型。它包含Model(业务模型)、View(用户视图)、Controller(控制器),把各部分分开组织,对代码抽象与隔离的处理可谓是代码设计的典范。

不过自从15年开始,随着各种前端框架的崛起,使得前端后端的关系发生进一步的演变,从MVC架构演变成前后端分离的REST架构了。以前MVC架构每次请求都需要经过控制器->模型->视图的流程,演变成前端请求后端接口,返回JSON的这样一种REST架构。

问题来了,我们到底还在用SpringMVC吗?答案是,不全用。前后端做了代码以及部署的分离,也就是说后端并不感知前端的存在,所以对于后端而言,View(用户视图)也就无从可谈了。Model(业务模型)发送性质上的改变,以前是一个前端所需要的Model,给页面读取,现在是一个JSON格式给到前端,由前端自由处理。

而作为Web框架的核心,Controller(控制器)则是依然留存的。所以现在大家用SpringMVC用的更多是Controller这一层。当然SpringMVC还有其他组件,包括filter、Http Caching、Web Security等等。本文只是着重MVC架构中的Controller的功能,而Controller的核心组件则是DispatcherServlet。所以后面我们将通过Demo,来逐步深入了解下,DispatcherSevlet如何做到对请求控制分发的。

传统SpringMVC启动简述

在传统的SpringMVC中,需要配置web.xml和applicationContext.xml。前者是负责配置项目初始化的配置,如servlet、welcome页面等,是JavaEE的规范。后者是初始化Spring Context的配置,主要是Bean的配置。

前文说到,SpringMVC是基于Servlet的架构,而DispatcherServlet则是SpringMVC拦截处理所有请求的Servlet,所以web.xml需要配置DispatcherServlet。其他的还有contextLoaderListener,负责加载除DispatcherServlet外的所有context内容,另外还需要通过contextConfigLoader指定Spring的配置文件(如applicationContext.xml)。

那么在项目启动的时候,加载web.xml首先会执行contextLoaderListener,让它初始化好Spring的Application context。后面有HTTP请求进来,则会落到DispatcherServlet上,让它去做处理分发。

SpringBoot Web Demo搭建

自从Spring配置注解和SpringBoot诞生以来,越来越少人去写web.xml和applicationContext.xml配置文件了。但为了方便直接了解Dispatcher的原理,Demo直接用SpringBoot的starter一键式搭建。

直接添加web的starter依赖

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-web</artifactId>
  4. <version>2.0.4.RELEASE</version>
  5. </dependency>

看下这个starter包含什么内容



绿框是springMVC的依赖,红框是Spring自动配置的依赖,蓝框则是内嵌tomcat的依赖。里面Spring的版本是5.0.8 RELEASE的。

SpringBoot启动类

测试controller

启动项目后,在浏览器里面输入http://localhost:8080/hello?name=Zack。结果返回Hello Zack

以上就是我们现在利用SpringMVC的基本内容,下面我们来看下SpringMVC如何利用DispatcherServlet做拦截分发的。

DispatcherServlet源码分析

当一个请求进来的时候,会先执行各种filter,过滤掉最终需要的请求,然后会落到DispatcherServlet中的doService()方法。该方法是预先设置一些特殊请求参数,然后再转发给doDispatch()做真正的处理转发。

看一下doDispatch()的注释说明



该方法的作用就是执行实际分发到的handler。

  • Handler通过HandlerMapping的优先级获取。HandlerAdapter通过查询DispatcherServlet已装载的HandlerAdapter,并且支持该Handler而获取的。
  • 所有的HTTP请求都是doDispatch()去处理的。具体是落到哪个方法去处理业务逻辑,取决于HandlerAdapters或者handlers。

从注释可知,整个的分发逻辑核心,就在于HandlerAdapter和Handler。那这两到底是什么东西?

官网上的说明



HandlerAdapter协助DispatcherServlet去调用对应的handler,忽略具体handler是怎么调用的。例如调用注解形式的controller需要处理注解,xml配置形式的要解析配置文件。这个适配器就是为了帮助DispatcherServlet屏蔽掉处理具体的细节。

至于Handler没有清晰解释,但我们debug源码可以发现,Handler其实就是实际分配到具体需要去处理的方法(对比下图红框和上面Demo的controller)。

回到doDispatch()这个方法的源码上,看到getHandler()getHandlerAdapter()就是获取Handler和HandlerAdapter所在。

getHandler()

看下getHandler()源码



整个方法就那么几行,不过需要注意有两个点。一个是该方法是返回HandlerExecutionChain类型,而不是一个Handler。



HandlerExecutionChain其实就是Handler的一层封装,还包含Handler对应的interceptor拦截器,用于执行Handler的一些前置和后置的操作。

另外一个点,HandlerExecutionChain是按顺序遍历handlerMappings拿出来的。那HandlerMapping又是什么呢?



从官网说明可知,它是一个请求和handler(实际是HandlerExecutionChain)的关联Map,通俗的说就是路由与处理逻辑的关联。它主要有两个实现,一个是RequestMappingHandlerMapping(支持注解形式方法),另一个是SimpleUrlHandlerMapping(维护显示注册的URI资源)。

由此可推测,在Spring启动的时候,就会去扫描注解、注册的静态资源,从而初始化这个handlerMappings。具体逻辑就在DispatcherServlet中的initHandlerMappings方法内。



初始化的方法内,主要有三步:

  1. 从Spring的ApplicationContext中取出HandlerMapping的Bean
  2. 然后对上面取出来的Bean做优先级排序,主要对是@Order注解的排序
  3. 如果上面取不出Bean,则用默认策略。

对于第三点的默认策略,可以找到DispatcherServlet.properties这个文件,里面配置了一些默认HandlerMapping、HandlerAdapter等相关类。

在初始化handlerMappings后,如果有请求进来,后面的request就用请求的路由与HandlerMapping对比,最后找出HandlerHandlerExecutionChain)。

getHandlerAdapter()

在取出实际处理的Handler后,就需要用它找出support它的适配器(HandlerAdapter)。按照前面对HandlerAdapter的描述,对于Demo而言,support这个Handler必定是RequestMappingHandlerAdapter

这个逻辑也非常简单,同样是遍历已初始化的handlerAdapters(初始化的过程类似handlerMappings),然后对于具体每个handlerAdapter,调用其support()方法,看是否支持。

supports()方法也很简单,就用instanceof判断handler是否Adapter自己支持的类。

HandlerAdapter.handle()

在获取完HandlerHandlerAdapter后,就可以执行HandlerAdapter中的handle方法,其实际只是调用Handler的方法。

我们按Demo例子,看下HttpRequestHandlerAdapterhandle()方法实现。



这个方法里面就是用HttpServlet的Request和Reponse去调用我们自己写的controller里面的方法。需要注意的是,这个方法返回的是ModelAndView,但我们目前基于Rest架构是已经不用的了,所以方法返回null回去了。

Handler的前置后置处理

前面提到Handler是被封装在HandlerExecutionChain里面的,其中还包含一些前置后置的拦截器。所以在执行HandlerAdapter.handle()前后会有对HandlerExecutionChain的调用,执行interceptor对前后置处理的方法

具体里面的实现就是执行interceptorpreHandle()postHandle()方法。

回过头来想下,这里的前后置处理会包括什么呢?在HandlerInterceptor注解上有说明三个实现类,分别是UserRoleAuthorizationInterceptor(检查用户权限)、LocaleChangeInterceptor(修改本地时间)、ThemeChangeInterceptor(修改当前主题)。可以看出HandlerInterceptor基本都是对请求的一些预处理和结果封装。

总结

以上就是SpringMVC中DispatcherServlet的基本过程。下面来总结下以上内容:

  1. 前后端的架构演变导致SpringMVC的使用发生改变,更多着重在“C”上了。
  2. “C”的核心在DispatcherServletdoDispatcher()方法中。
  3. 利用request的路由,对比从已初始化的handlerMappingshandlerAdapters中获取handlerhandlerAdapter
  4. handler是封装在HandlerExecutionChain中,其中还包括handler的前后置拦截器。
  5. 最后利用适配器模式,调用HandlerAdapter.handle()方法去执行handler具体处理的业务逻辑。
  6. 在执行具体业务逻辑前后会执行封装在HandlerExecutionChain里面的拦截器。

更多技术文章、精彩干货,请关注

博客:zackku.com

微信公众号:Zack说码

“过时”的SpringMVC我们到底在用什么?深入分析DispatchServlet源码的更多相关文章

  1. 跟我学SpringMVC目录汇总贴、PDF下载、源码下载

    国内私募机构九鼎控股打造APP,来就送 20元现金领取地址:http://jdb.jiudingcapital.com/phone.html内部邀请码:C8E245J (不写邀请码,没有现金送)国内私 ...

  2. TextView 的新特性,Autosizing 到底是如何实现的? | 源码分析

    一.前言 Hi,大家好,我是承香墨影! 前两天聊了一下 Autosizing 的使用,反映还不错.毕竟是这种能解决实际问题的新 Api,确实在需要的时候,用起来会很顺手. 简单回顾一下,Autosiz ...

  3. Spring+SpringMvc+Mybatis框架集成搭建教程五(项目源码发布到GitHub)

    一.背景 我们做完了上面的四步操作以后,来把我们写好的项目提交到自己的GitHub仓库进行版本管理,具体步骤如下. 二.提交步骤 1.首先你要保证你已经有GitHub的账号和密码(没有可以去githu ...

  4. 工作流引擎 springmvc SSM 流程审批 Java Activiti 后台框架源码

    工作流模块  1.模型管理    :web在线流程设计器.预览流程xml.导出xml.部署流程 2.流程管理    :导入导出流程资源文件.查看流程图.根据流程实例反射出流程模型.激活挂起 3.运行中 ...

  5. 详解SpringMVC请求的时候是如何找到正确的Controller[附带源码分析]

    目录 前言 源码分析 重要接口介绍 SpringMVC初始化的时候做了什么 HandlerExecutionChain的获取 实例 资源文件映射 总结 参考资料 前言 SpringMVC是目前主流的W ...

  6. SpringMVC源码阅读:定位Controller

    1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码分析,弄清楚Spr ...

  7. SpringMVC源码情操陶冶-ViewResolver视图解析

    简单分析springmvc是如何解析view视图,并返回页面给前端 SpringMVC配置视图解析器 <bean id="viewResolver" class=" ...

  8. SpringMVC源码阅读:Json,Xml自动转换

    1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码(基于Spring ...

  9. SpringMVC源码分析系列

    说到java的mvc框架,struts2和springmvc想必大家都知道,struts2的设计基本上完全脱离了Servlet容器,而springmvc是依托着Servlet容器元素来设计的,同时sp ...

随机推荐

  1. bing查询旁站脚本

    #!/usr/bin/env python # -*- coding: UTF-8 -*- #by i3ekr import re,optparse,sys,requests,time,os pars ...

  2. Linux 入门记录:十、Linux 下获取帮助

    一.获取帮助 Linux 提供了极为详细的帮助工具和文档,通过查阅相关文档,可以大大减少需要记忆的东西并提高效率. 二.--help参数 几乎所有命令都可以使用 -h 或 --help 参数获取命令的 ...

  3. 110.Balanced Binary Tree---《剑指offer》面试39

    题目链接 题目大意:判断一个二叉树是否是平衡二叉树. 法一:dfs.利用求解二叉树的高度延伸,先计算左子树的高度,再计算右子树的高度,然后两者进行比较.o(nlgn).代码如下(耗时4ms): pub ...

  4. 2015多校第7场 HDU 5379 Mahjong tree 构造,DFS

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5379 题意:一颗n个节点n-1条边的树,现在要给每个节点标号(1~n),要求:(1)每一层的兄弟节点的 ...

  5. Smarty模板快速入门

    文件下载 1.下载地址:http://www.smarty.net/ 2.我下载的版本是3.1.27 ,将下载的文件smarty-3.1.27.zip解压出来,然后将libs文件夹的所有文件复制到你的 ...

  6. 【python】时间戳、字典列表排序

    记录一下昨天学到的知识: 一.文件相关 文件追加:f = open("fname","a")    文件不存在时创建 二.时间戳相关 http://www.jb ...

  7. python_day7学习笔记

    类 1)创建一个类 #coding=utf-8 __author__ = 'Administrator' class Employee: '所有员工的基类' empCount = 0 def __in ...

  8. git clone命令

    从远程clone一个仓库 ...知识浅薄 git clone都发现貌似用的不顺 因为我有几个git账号 但是我也不知道就是git账号是怎么保存在终端上的 所以当我需要用一个新的github账号来clo ...

  9. Restful Framework (三)

    目录 一.版本 二.解析器 三.序列化 四.请求数据验证 一.版本 回到顶部 程序也来越大时,可能通过版本不同做不同的处理 没用rest_framework之前,我们可以通过以下这样的方式去获取. c ...

  10. Nginx配置问题总结

    1.Nginx直接下载解压,有个nginx.exe文件,双击即开启Nginx服务(windows系统下).默认是80端口. 若服务无法启动,考虑以下三方面问题: (1)端口号80是否被占用 (2)防火 ...