为了账号安全,请及时绑定邮箱和手机立即绑定

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

标签:
Java


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

Spring MVC官方名字其实是Spring Web MVCMaven上的包名也是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依赖

 <dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-web</artifactId>

    <version>2.0.4.RELEASE</version></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方法内。

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

从Spring的ApplicationContext中取出HandlerMapping的Bean

然后对上面取出来的Bean做优先级排序主要对是@Order注解的排序

如果上面取不出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()

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

我们按Demo例子看下HttpRequestHandlerAdapter的handle()方法实现。

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

Handler的前置后置处理

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

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

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

总结

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

前后端的架构演变导致SpringMVC的使用发生改变更多着重在“C”上了。

“C”的核心在DispatcherServlet的doDispatcher()方法中。

利用request的路由对比从已初始化的handlerMappings和handlerAdapters中获取handler和handlerAdapter。

handler是封装在HandlerExecutionChain中其中还包括handler的前后置拦截器。

最后利用适配器模式调用HandlerAdapter.handle()方法去执行handler具体处理的业务逻辑。

在执行具体业务逻辑前后会执行封装在HandlerExecutionChain里面的拦截器。

©著作权归作者所有来自51CTO博客作者架构之路的原创作品如需转载请注明出处否则将追究法律责任


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消