从一个请求入口来带你探究DispatcherServlet的奥秘——SpringMVC的核心组件——万字长文

文章目录1:十万个为什么?2:Debugger的使用3:Spring MVC的请求处理路口——processRequest4:DispatcherServlet类中的doDispatch方法探究——请求分发处理4.1 doDispatch方法—— 请求分发概述4.2 获取请求处理器4.3 查找处理适配器4.4 处理适配器执行4.5 处理返回值与响应4.5.1 render核心方法——对结果统一处理的渲染方法5:DispatcherServlet请求分发过程总结6:doDispatch方法的完整源码调试7:一
摘要由CSDN通过智能技术生成

1:十万个为什么?

这一周画了大部分的精力来探究SpringMVC源码,为什么忽然会有这想法呢?因为有一种感觉就是,当你项目上手多了以后,你就会发现一个问题:会非常好奇,为什么你前端发送一个请求(这个请求还可以携带上表单提交数据或者是你的url地址后面加上?的参数,比如http://localhost:8080/some.do?username=zlj)然后这个请求就可以根据路径到达你后端里面指定的Controller类(必须要标记@Controller注解等,或者实现Controller接口等)下的指定方法(这个方法上要有 @RequestMapping注解,比如 @RequestMapping(value = “/some.do”)
),然后这个方法执行完以后,就可以跳转到你的指定界面,这个过程就可以进行把Model填充到View中,进行渲染视图。如下面代码:

  @RequestMapping(value = "/some.do")
    public ModelAndView dosome(){
    //doGet
    //渲染视图
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("msg","欢迎使用springmvc做web开发");
        return modelAndView;
    }

上面步骤的顺利执行,一切的一切都要归功于SpringMVC的核心组件——DispatcherServlet。在讲该核心组件之前,我们就要先讲一下Debugger,
俗话说:工欲善其事,必先利其器。要想对源码进行详细的研究,离不开对源码调试,而要想对源码进行调试,一个好的IDE工具是非常必要的。以下演示,我们都使用IntelliJ IDEA为例。
在这里插入图片描述

学完下面的几个章节,我相信大家会对上面代码的执行流程,有一个深刻的了解

2:Debugger的使用

如果我们在启动入口设置断点,并结合单步调试,即可以完整地观察到SpringBoot应用的详细启动过程。通过在处理器方法中设置断点,则可以通过调用栈看到从Web容器的请求处理方法开始,到Spring MVC框架层的处理过程,最终到达处理器方法的执行的整个过程。

因为启动入口是整个应用的根,从根可以得到的调试信息是优先的。而在处理器方法中,因为是请求处理入口执行的最后一环, 通过处理器方法的调试信息,可以得到完整的请求处理的调用链。故在此以处理器方法为例,来演示整个调试过程,以及调试过程中可以得到的信息,从而达到通过调试来探索研究的目的。如果对上面这一段话不理解,我们马上看一张图,来解释这一段话的意思,如下是我已经在调试SpringMVC源码时候的一张图片。
在这里插入图片描述
从上面的栈帧窗口中,我们得到一个暴论如下四点:

1.栈顶是Java线程的run方法,这表示在Web容器接收到请求后,会通过一个独立的线构对该请求进行处理。不同的请求使用不同的线程处理。

2.再向上则是org apache tomcat相关的类,因为Spring Boot默认使用的Web容器是tomcat,所以这里的调用栈就是Web容器内部对请求的处理调用栈。

3.随后进入org. springframework.web. servlet相关的类。这一层 就是Spring MVC框架有Web容器整合的部分,该部分的整合依赖于名为FrameworkServlet这个Servlet组件(其实是其子类DispatcherServlet)。其后续执行都与org.springframework.web.servlet相关包有关。

4.Spring MVC框架层执行的终点也就是处理器方法,整个调用栈就是这样产生的

我们在后面的章节中主要是对DispatcherServlet这一个部分进行源码研究,1,2两点(SpringBoot下SpringMVC框架启动原理)和有关SpringBoot在启动时怎么把DispatcherServlet注册到基于Servlet标准的Web容器中(其实也可以放在SpringBoot下SpringMVC框架启动原理中)。在这里不进行源码研究。

一不小心,扯远了,马上回到正题,开始讲解Debugger的使用。
在这里插入图片描述

我们分两小部分,调试图解和调试的使用来讲解这一部分,这一部分单独写一篇文章,如下
idea中Debugger的使用——工欲善其事,必先利其器
唠嗑:在针对特定组件进行探索时,也需要依赖于IDE提供的强大功能,包括特定类与接口的查找、方法引用的查找、实现类的查找等查找操作。这里所讲到的操作,会在下面章节,用到的时候,来进行讲述。
在这里插入图片描述

讲了这么多,怎么还没进入今天的正题啊,DispatcherServlet快出来,别急,别急(我自己都急了)在讲DispatcherServlet之前,我们有必要讲一下Spring MVC的请求处理路口,为什么还要讲它啊,因为饭要一口口吃才不会噎死,如果我直接上DispatcherServlet类中的doDispatch方法,是不是会一脸懵逼,它从哪里来都不知道啊!!!!
在这里插入图片描述

3:Spring MVC的请求处理路口——processRequest

在这里,我先下一个暴论:
Spring MVC与Web容器之间的整合是通过DispatcherServlet组件实现的。Spring Boot在启动时把DispatcherServlet注册到基于Servlet 标准的Web 容器中,Web容器在接收到HTTP请求时,经过预处理后把该请求交给DispatcherServlet处理,同时DispatcherServlet还负责处理把需要返回的内容写入HTTP响应。这就意味着Spring MVC请求处理的核心就是DispatcherServlet

这一段话的前面几句可能不好理解,我们必须得自己调试一遍SpringBoot下SpringMVC框架的启动原理,才可以很好的理解这段话(比如Spring Boot在启动时是怎么把DispatcherServlet注册到基于Servlet 标准的Web 容器中的?Web容器在接收到HTTP请求时,怎么经过预处理把该请求交给DispatcherServlet处理?)
但是我们一定记住这句话:Web容器在接收到HTTP请求时,经过预处理后把该请求交给DispatcherServlet处理。它对我们后面,怎么找到MVC的请求处理路口有很大的帮助!!!!(自认为)
在这里插入图片描述

在前面提到研究请求处理的步骤可以通过在处理器方法上设置一个断点, 再访问此请求进入断点中断,此时查看该线程的调用栈即可找到相关的处理逻辑。那么这里继续以此思路来查看Web容器中对于请求的处理步骤。

1.在处理器方法的调用栈中,从最顶层开始调用栈的上层查看,排除掉JDK自身包java.*下的方法及tomcat服务器包org apache.*下的方法,最先出现的Spring包org. springframework下相关的方法是Filter组件。

2.在Filter 组件执行完成后,则进入org.springframework 中的Servlet组件执行请求与响应的处理。这个Servlet组件即是Spring MVC与Web容器整合的核心。在Servlet组件中,依次调用org. springframework包中的所有MVC组件,最终执行到开发者定义的处理器方法。这也就是断点所在处,之后即层层向上返回,同时把返回内容写入HTTP响应中。

上面的两断话放在栈帧窗口中,一切就变得好理解起来,下图是按照在处理器方法上设置一个断点,然后浏览器执行请求,就可以进入断点中断中查看。栈帧窗口如下
在这里插入图片描述
在上面的处理步骤中,我们提到了Filter,那我们在简单讲一下Filter到Servlet的过渡
唠嗑:在Servlet标准中,请求的处理过程是先通过由所有的Fiter组件构成Fiter链对请求进行过滤与预处理,如果在Filter 链中没有对请求提前结束处理,则最终会进入Servlet组件中对请求进行处理。

对于Spring MVC来说,最终的Servlet组件就是DispatcherServlet,而其中调用链中出现的Spring MVC提供的一些Filter则各有其功能,在这不在概述

在过滤器执行完成后,将会进入Servlet组件的执行中。通过栈帧窗口可知,在栈帧窗口中最先出现的Servlet组件相关类为javax.servlet.http.HttpServlet,该类为抽象类,执行时肯定是一个具体的类,为该类的子类(由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用)。栈帧窗口如下图
在这里插入图片描述
看了怎么久,怎么还没出现,MVC的请求处理路口啊啊啊,更别说DispatcherServlet的奥秘了,啥都没懂啊!!!!或许这就是源码吧!!!我都快急死了,好,接下来,我就在下一个暴论,让大家看看DispatcherServlet到底在哪。
在这里插入图片描述

在调用栈中,定位到开始调用Servlet 相关方法处,此处为:internalDoFilter:231,ApplicationFilterChain。在该处执行的调用为servlet.service(request, response);, 在此处查看servlet的值,可以看到其具体类型为DispatcherServlet。直接上图,解释这段话的意思
在这里插入图片描述
来了,他来了,我们终于看见DispatcherServlet了!!!!感觉要胜利了!!!接下来我们要讲一下其中的调用过程,为什么忽然在ApplicationFilterChain类中会出现DispatcherServlet呢?我们在这里画一张DispatcherServlet类结构图图片,帮助大家理解。
在这里插入图片描述
对上面的五点做如下解释
第一点和第二点:进入父类的service执行逻辑,如果使用debug下的get请求进行测试,所以执行else分支。在父类的sevice执行逻辑中,对请求方法进行判断,以执行不同的处理。部分HttpServlet类下的service方法代码如下

 protected void service(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException{
   
        String method = req.getMethod();
//对请求方法进行判断,支持GET,HEAD,POST,PUT,DELETE,OPTIONS,TRACE。7种请求判断
//对每种请求方法分别执行对应的do方法
        if (method.equals(METHOD_GET)) {
   
        //...
        }else if (method.equals(METHOD_HEAD)) {
   
//...
            doHead(req, resp);
        } else if (method.equals(METHOD_POST)) {
   
            doPost(req, resp);           
        } else if (method.equals(METHOD_PUT)) {
   
            doPut(req, resp);            
        } else if (method.equals(METHOD_DELETE)) {
   
            doDelete(req, resp);            
        } else if (method.equals(METHOD_OPTIONS)) {
   
            doOptions(req,resp);            
        } else if (method.equals(METHOD_TRACE)) {
   
            doTrace(req,resp);        
        } else {
   
        //进入此逻辑表示请求方法不被支持,会直接返回错误响应
        //..

第三点:HttpServlet的do系列方法中,均返回固定的错误请求,错误状态码为405,请求方法不被支持。所以若要对对应请求方法提供支持,子类必须重写父类对应请求方法的do系列方法。这个时候FrameworkServlet类就出现了,它重写了HttpServlet中所有的do*系列方法。

唠嗑:为什么HttpServlet的do系列方法中,均返回固定的错误请求?
我们在HttpServlet类下的service方法中随便找一个do
方法,比如doPost方法,按住ctrl,点击方法,进入doPost方法源码,如下看到if和else分支里面都是sendError方法,就明白啦

    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException
    {
   
        String protocol = req.getProtocol();
        String msg = lStrings.getString("http.method_post_not_supported");
        if (protocol.endsWith("1.1")) {
   
            resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
        } else {
   
            resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
        }
    }

第四点:因为在Spring MVC 的设计中,可以根据@ RequestMapping注解中标记的方法条件,把请求根据请关方法分发到不同的处理器方法上,故最终所有的方法需要调用同一个分发逻辑,所以在FrameworkServlet的do*方法中,可以看到都调用了同一个方法: processRequest。下面是FrameworkServlet类中doGet方法的内容示例。

	@Override
	protected final void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
   
		processRequest(request, response);
	}

在上述过程执行完成后,Spring MVC提供的Servlet组件与Web容器整合,在后续的过程中,将调用与原始Servlet无关的processRequest 方法,以此为入口,进入到Spring MVC框架对请求的处理中。上述过程是从原始Web容器调用到MVC框架组件的过程(是不是跟这句话很像啊:Web容器在接收到HTTP请求时,经过预处理后把该请求交给DispatcherServlet处理)。

第五点:在请求处理方法中,所有处理的调用最终会委派给doService方法。在FrameworkServlet类中该方法(doService)是抽象方法,运行期真实调用的是DispatcherServlet重写的doService 方法。

唠嗑:在这里我们讲一下FrameworkServlet类中processRequest 方法的作用:该方法内先进行上下文的初始化操作,最后会进行上下文请求重置的操作,真实的请求与响应操作则是通过doService方法完成的。在构造本地化上下文时,调用方法buildLocaleContext。在这个方法逻辑中,会使用到本地化解析器解析请求中的地区信息,关于本地化解析以后会单独讲

讲了这么多,通过查看栈帧窗口,我们终于分析出来MVC的请求入口是FrameworkServlet类中processRequest 方法,在下面一章,我们主要就是讲DispatcherServlet 类doService方法中的核心方法doDispatch方法,对doService方法我们只做一个简单概括
在这里插入图片描述

4:DispatcherServlet类中的doDispatch方法探究——请求分发处理

对DispatcherServlet 类中的doServie方法简单解读,我们查看它的源码,主要就是三个作用:1.保存当前请求属性快照目的 2.把组件放入请求属性中的 3.执行核心doDispatch分发请求方法,如果大家对1,2感兴趣,可以自己探究,这里不在进行分析
接下来,我们直接进入正题,来对doDispatch方法一探究竟

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值