转载

“过时”的SpringMVC我们到底在用什么?深入分析DispatchServlet源码

之前已经分析过了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我们到底在用什么?深入分析DispatchServlet源码

问题来了,我们到底还在用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依赖

 <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.0.4.RELEASE</version>
</dependency>

看下这个starter包含什么内容

“过时”的SpringMVC我们到底在用什么?深入分析DispatchServlet源码

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

SpringBoot启动类

“过时”的SpringMVC我们到底在用什么?深入分析DispatchServlet源码

测试controller

“过时”的SpringMVC我们到底在用什么?深入分析DispatchServlet源码

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

“过时”的SpringMVC我们到底在用什么?深入分析DispatchServlet源码

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

DispatcherServlet源码分析

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

看一下 doDispatch() 的注释说明

“过时”的SpringMVC我们到底在用什么?深入分析DispatchServlet源码

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

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

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

官网上的说明

“过时”的SpringMVC我们到底在用什么?深入分析DispatchServlet源码

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

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

“过时”的SpringMVC我们到底在用什么?深入分析DispatchServlet源码

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

“过时”的SpringMVC我们到底在用什么?深入分析DispatchServlet源码

getHandler()

看下 getHandler() 源码

“过时”的SpringMVC我们到底在用什么?深入分析DispatchServlet源码

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

“过时”的SpringMVC我们到底在用什么?深入分析DispatchServlet源码

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

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

“过时”的SpringMVC我们到底在用什么?深入分析DispatchServlet源码

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

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

“过时”的SpringMVC我们到底在用什么?深入分析DispatchServlet源码

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

  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() 方法,看是否支持。

“过时”的SpringMVC我们到底在用什么?深入分析DispatchServlet源码

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

“过时”的SpringMVC我们到底在用什么?深入分析DispatchServlet源码

HandlerAdapter.handle()

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

“过时”的SpringMVC我们到底在用什么?深入分析DispatchServlet源码

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

“过时”的SpringMVC我们到底在用什么?深入分析DispatchServlet源码

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

Handler的前置后置处理

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

“过时”的SpringMVC我们到底在用什么?深入分析DispatchServlet源码

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

“过时”的SpringMVC我们到底在用什么?深入分析DispatchServlet源码

回过头来想下,这里的前后置处理会包括什么呢?在 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源码
原文  http://zackku.com/spring-mvc/
正文到此结束
Loading...