>>分享流行的Java框架以及开源软件,对孙卫琴的《精通Spring》、《Spring Cloud Alibaba微服务开发零基础入门到实操》提供技术支持 书籍支持  卫琴直播  品书摘要  在线测试  资源下载  联系我们
发表一个新主题 开启一个新投票 回复文章 您是本文章第 28402 个阅读者 刷新本主题
 * 贴子主题:  SpringMVC已经过时了?深入分析DispatchServlet源码 回复文章 点赞(0)  收藏  
作者:日月光华    发表时间:2019-12-28 04:31:30     消息  查看  搜索  好友  邮件  复制  引用

SpringMVC已经过时了?深入分析DispatchServlet源码

本文先简述下目前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依赖

<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 方法内。

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

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

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

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

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 的基本过程。下面来总结下以上内容:

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

感谢您的观看.专注Java、大数据知识干货及相关领域动态分享,请多多关注哦!


程序猿的技术大观园:www.javathinker.net
  Java面向对象编程-->面向对象开发方法概述之开发思想(上)
  JavaWeb开发-->Servlet技术详解(Ⅲ)
  JSP与Hibernate开发-->数据库事务的概念和声明
  Java网络编程-->基于UDP的数据报和套接字
  精通Spring-->通过Axios访问服务器
  Vue3开发-->Vue指令
  10分钟认识RocketMQ!想进阿里连这个都不会?
  卫琴姐姐制作的最新视频课程:基于Spring的JavaWeb开发技术详...
  孙卫琴系列Java书籍的QQ交流读者群
  @Service注解的使用
  【项目实践】使用Vue.js和ElementUI快速实现后台管理系统的界...
  Spring Boot JPA @OneToOne
  Spring Cloud构建微服务架构: 消息总线
  微服务拆分实践
  探讨通过Feign配合Hystrix进行调用时异常的处理
  RESTful API 设计最佳实践
  分布式消息队列RocketMQ部署与监控
  深入理解Mybatis一级缓存
  一份Spring Boot核心知识清单
  Spring MVC Controller单例陷阱
  什么是CXF
  更多...
 IPIP: 已设置保密
楼主      
该用户目前不在线 nihaota 
  
威望: 0
级别: 新手上路
魅力: 1315
经验: 1315
现金: 2944
发文章数: 243
注册时间: 0001-01-01
 消息  查看  搜索  好友  邮件  复制  引用


讨债公司
发文章时间 2022-10-28 20:44:30
 IPIP: 已设置保密 1 楼     
1页 1条记录 当前第1
发表一个新主题 开启一个新投票 回复文章


中文版权所有: JavaThinker技术网站 Copyright 2016-2026 沪ICP备16029593号-2
荟萃Java程序员智慧的结晶,分享交流Java前沿技术。  联系我们
如有技术文章涉及侵权,请与本站管理员联系。