首页 技术 正文
技术 2022年11月20日
0 收藏 567 点赞 4,521 浏览 6683 个字

初探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。

相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:9,075
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,551
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,399
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,176
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:7,811
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:4,893