初探DispatcherServlet#doDispatch

写在前面

SpringBoot其实就是SpringMVC的简化版本,对于request的处理流程大致是一样的, 都要经过DispatcherServlet拦截之后通过相应的Handler去寻找对应的Controller处理业务最后返回ModelAndView做视图解析之后渲染到前端页面。

0x01 doDispatch

首先所有请求都会经过org/springframework/web/servlet/DispatcherServlet.java,这一点也可以根据该方法注释了解到。

我们的请求会先进入到DispatcherServlet#doService方法中,在doService中调用了doDispatch,而doDispatch是实现大部分处理request逻辑的地方,大致可分为请求处理(如寻找相应controller,获取ModelAndView,resolveView视图解析等)和页面渲染,下面是该方法代码。

/**
* Process the actual dispatching to the handler.
* <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
* The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
* to find the first that supports the handler class.
* <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
* themselves to decide which methods are acceptable.
* @param request current HTTP request
* @param response current HTTP response
* @throws Exception in case of any kind of processing failure
*/
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
//将request对象重新存储到processedRequest
HttpServletRequest processedRequest = request;
//处理器链
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
//获取异步请求管理器
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try {
//最终返回的ModelAndView对象
ModelAndView mv = null;
Exception dispatchException = null; try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request); // Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
} // Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
} if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
} // Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) {
return;
} applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}

0x02 前期处理

前面部分代码是对请求的一些前期处理,从processedRequest = checkMultipart(request);开始进入对request的处理逻辑部分。

首先check该request是否为文件上传请求,如果是则重新封装request,不是则把传入的request原封不动return回来

之后判断我们的requst是否在checkMultipart方法中封装过(即request是文件上传请求),判断的布尔值结果赋值给multipartRequestParsed,此值类似于flag用作后面判断,当是文件上传请求时在最后会清除文件上传过程中的临时文件。

0x02 getHandler

之后进入Handler部分,调用org/springframework/web/servlet/handler/AbstractHandlerMapping#getHandler并返回executionChain赋值给mappedHandler,如果没找到对应的handler和拦截器就会进入if中调用noHandlerFound抛出异常。

org/springframework/web/servlet/handler/AbstractHandlerMapping#getHandlerExecutionChain实现:

简而概之,这里返回值executionChain中封装了2个重要的东西,之后会在doDispatch中被用到:

  1. 处理当前请求的Controller及其方法的信息
  2. 当前请求所需要的拦截器信息

0x03 getHandlerAdapter

下面调用getHandlerAdapter根据之前返回的executionChain拿到handler,再根据handler获取适配的handlerAdapter处理器适配器

这里缺省为RequestMappingHandlerAdapter优先级最高,最终返回的也是它。

0x04 Last_Modified处理

之后处理GET和HEAD请求头的 Last_Modified 字段。

当浏览器第一次发起 GET 或者 HEAD 请求时,请求的响应头中包含一个 Last-Modified 字段,这个字段表示该资源最后一次修改时间,以后浏览器再次发送 GET、HEAD 请求时,都会携带上该字段,服务端收到该字段之后,和资源的最后一次修改时间进行对比,如果资源还没有过期,则直接返回 304 告诉浏览器之前的资源还是可以继续用的,如果资源已经过期,则服务端会返回新的资源以及新的 Last-Modified

0x04 applyPreHandler

接下来做了一个判断,调用applyPreHandler()方法对所有的拦截器进行遍历,如果发现拦截器的preHandle()方法返回false的话,则直接执行triggerAfterCompletion()方法,并返回false,运行停止,如果获取的布尔类型为true,则将对interceptorIndex进行赋值为1

0x05 handle

之后是handlerAdaptor调handle,去进行对handler的一个处理

这里的chain比较复杂

org/springframework/web/servlet/mvc/method/AbstractHandlerMethodAdapter#handle
org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter#handleInternal
org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter#invokeHandlerMethod
org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod#invokeAndHandle
org/springframework/web/method/support/InvocableHandlerMethod#invokeForRequest

跟进到org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java#invokeAndHandle方法,这里调用invokeFoRequest会返回returnValue,该方法会根据输入的uri,调用相关的controller的方法获取返回值,并将其返回给returnValue,作为待查找的模板文件名,再去传给视图解析器处理。(这里因为我用的Controller方法中没有返回值,所以returnValue为null)

最终层层返回值赋值给mv

0x06 异步请求处理

下一步判断是否需要进行异步处理请求,需要的话return掉

0x07 applyDefaultViewName

接下来applyDefaultViewName方法判断当前视图是否为空,如果为空,调用getDefaultViewName方法获取ModelAndView

但是因为这里mav值为空,所以viewTemplateName会从uri中获取,我们看下是如何处理defaultViewName的,调试之后发现最终在getViewName方法中调用transformPath对URL中的path进行了处理

重点在于第3个if中stripFilenameExtension方法

/org/springframework/util/StringUtils#stripFilenameExtension该方法会对后缀做一个清除(去掉.及其之后的内容)并将该uri返回

最终通过mv.setViewName(defaultViewName);将该uri赋值给mv

0x08 applyPostHandle

接下来调用 applyPostHandle 方法执行拦截器里边的 postHandle 方法。

0x09 processDispatchResult

之后会进入到processDispatchResult方法,包括异常处理、渲染页面以及执行拦截器的 afterCompletion 方法都在这里完成。该方法中第1个if会被跳过,跟进第2个if中的render方法

render方法中,首先会获取mv对象的viewName,然后调用resolveViewName方法,resolveViewName方法最终会获取最匹配的视图解析器。

跟一下resolveViewName方法,这里涉及到两个方法:1、首先通过getCandidateViews筛选出resolveViewName方法返回值不为null的视图解析器添加到candidateViews中; 2、之后通过getBestView拿到最适配的解析器,getBestView中的逻辑是优先返回在candidateViews存在重定向动作的view,如果都不存在则根据请求头中的Accept字段的值与candidateViews的相关顺序,并判断是否兼容来返回最适配的View

getCandidateViews:

getBestView:

这里最终返回的是ThymeleafView(不同情况会返回不同的视图解析器,添加了Thymeleaf依赖会有ThymeleafView,也可能会有自定义的视图解析器,返回值不唯一)之后ThymeleafView调用了render方法,继续跟进

调用renderFragment

该方法在后面首先判断viewTemplateName是否包含::,若包含则获取解析器,调用parseExpression方法将viewTemplateName(也就是Controller中最后return的值)构造成片段表达式(~{})并解析执行。后面就不跟了,如果是Thymeleaf还会对表达式进行预处理操作,不同的视图解析器执行流程应该也是不一样的。

0x10 cleanupMultipart

最后在 finally 代码块中判断是否开启了异步处理,如果开启了,则调用相应的拦截器;如果请求是文件上传请求,则再调用 cleanupMultipart 方法清除文件上传过程产生的一些临时文件。

结语

简单调试跟了下DispatcherServlet。

初探DispatcherServlet#doDispatch的更多相关文章

  1. 6、SpringMVC源码分析(1):分析DispatcherServlet.doDispatch方法,了解总体流程

    所有的http请求都会交给DispatcherServlet类的doDispatch方法进行处理,将DispatcherServlet.doDispatch函数的javadoc复制到下面: /* * ...

  2. tomcat线程初探

    博主:handsomecui,希望路过的各位大佬留下你们宝贵的意见,在这里祝大家冬至快乐. 缘由: 初探缘由,在业务层想要通过(当前线程的栈)来获取到控制层的类名,然后打日志,可是发现并不能通过当前线 ...

  3. SpringMVC源码情操陶冶-DispatcherServlet简析(二)

    承接前文SpringMVC源码情操陶冶-DispatcherServlet类简析(一),主要讲述初始化的操作,本文将简单介绍springmvc如何处理请求 DispatcherServlet#doDi ...

  4. 2.SpringMVC源码分析:DispatcherServlet的初始化与请求转发

    一.DispatcherServlet的初始化 在我们第一次学Servlet编程,学java web的时候,还没有那么多框架.我们开发一个简单的功能要做的事情很简单,就是继承HttpServlet,根 ...

  5. javax.servlet.ServletException: Could not resolve view with name 'order/list' in servlet with name 'dispatcherServlet'

    javax.servlet.ServletException: Could not resolve view with name 'order/list' in servlet with name ' ...

  6. DispatcherServlet 分发流程

    0 太长不看版 HTTPServlet 的 Service 方法将请求按类进行分解 主要是根据HTTP方法的类型调用 doXXX 方法 GET 和 HEAD 方法需要对 if-modified-sin ...

  7. Spring MVC:DispatchServlet类

    Spring MVC架构 Spring Web MVC是基于Servlet API构建的原始Web框架,从一开始就已包含在Spring框架中.传统的模型层被拆分为了业务层(Service)和数据访问层 ...

  8. 学习SpringMVC——你们要的REST风格的CRUD来了

    来来来,让一下,客官,您要的REST清蒸CRUD来了,火候刚刚好,不油不腻,请慢用~~~ 如果说前面是准备调料,洗菜,切菜,摆盘,那么今天就来完整的上道菜,主要说的是基于REST风格实现数据的增删改查 ...

  9. Junit mockito 测试Controller层方法有Pageable异常

    1.问题 在使用MockMVC+Mockito模拟Service层返回的时候,当我们在Controller层中参数方法调用有Pageable对象的时候,我们会发现,我们没办法生成一个Pageable的 ...

随机推荐

  1. Ubuntu 查询用户账号

    查看当前登录 who users 查看系统中所有用户: grep bash /etc/passwd XXXX-VirtualBox:~/桌面$ w 13:23:26 up 15 min, 1 user ...

  2. Windows安装Svn客户端

    一.下载程序 官网地址,选择最新64位下载. 下载完成 二.安装过程 点击下一步 点击下一步 选择安装目录 点击安装 安装完成 三.修改中文 下载中文包 下载完成 点击下一步 安装完成 点击设置 选择 ...

  3. 如果被问到 HTTP 协议,你真的能讲清楚吗?

    前段时间,在和许久未见的老同学聊天时,突然被问到 http 协议到底是什么?脑海里面第一时间想起来的就是 request 请求.response 响应之类的词汇,但是这样讲他真的能知道是什么吗?我反问 ...

  4. spring框架学习日志一

    一.简介 1.对spring框架的简单理解 可以理解为它是一个管理对象的创建.依赖.销毁的容器 Spring 是一个开源框架. Spring 为简化企业级应用开发而生. 使用 Spring 可以使简单 ...

  5. flink双流join

    package com.streamingjoin import org.apache.flink.api.common.state.{ValueState, ValueStateDescriptor ...

  6. OVN架构

    原文地址 OVN架构 1.简介 OVN,即Open Virtual Network,是一个支持虚拟网络抽象的系统. OVN补充了OVS的现有功能,增加了对虚拟网络抽象的原生(native)支持,比如虚 ...

  7. linux centos7 移动文件到指定目录

    2021-08-26 在 centos7 环境下怎么移动一个文件到其他的目录下呢? 使用命令  mv 文件名 指定目录  即可完成该操作. 那么怎么将一个文件夹下的内容移动到另一个文件夹下呢?比如有时 ...

  8. Eclipse中安装配置Gradle

    Gradle是以Groovy语言为基础,面向Java应用为主.基于DSL(领域特定语言)语法的自动化构建工具. gradle对多工程的构建支持很出色,工程依赖是gradle的第一功能. gradle支 ...

  9. Mybatis(二)——全局配置文件

    一.在正文上方直接添加目录. 1.二级标题***申请开通js权限 2.添加js脚本到页脚Html代码 数组:采用一段连续的存储单元来"存储"数据.对于"指定下标" ...

  10. MyBatis学习总结(三)——MyBatis配置文件配置的优化

    一.连接数据库的配置单独放在一个properties文件中 上文 连接数据库的配置写在 mybatisConf.xml中,本文直接放在 db.properties 中, 在mybatisConf.xm ...