初探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对象重新存储到processedRequestHttpServletRequest 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 afterCompletionif (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中被用到:
- 处理当前请求的Controller及其方法的信息
- 当前请求所需要的拦截器信息
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相关推荐
- DispatcherServlet详细分析
目录 1. DispatcherServlet的作用分析 1.1 uml类图 2. DispatcherServlet的初始化分析 2.1 初始化流程 2.2 详细分析流程 3. Dispatcher ...
- 2019-04-28 21:43:41.098 ERROR 2920 --- [nio-8888-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] :
这个错误看着让人很害怕,其实解决方案很简单 我虽然解决了但是我并不明白原理如果有大佬了解帮忙解释一下谢谢 2019-04-28 21:43:41.098 ERROR 2920 --- [nio-888 ...
- Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request p
今天在写springboot项目的时候遇到了这样一个问题,是有关注入的,报错信息如下 2020-04-07 16:24:42.675 ERROR 10384 --- [nio-8181-exec-1] ...
- Spring Boot问题之JSP无法显示Could not resolve view with name ‘xxxx‘ in servlet with name ‘dispatcherServlet
问题: 在Spring Boot应用中JSP页面无法显示, 页面提示: 控制台的错误信息类似: javax.servlet.ServletException: Could not resolve vi ...
- SpringBoot一条龙
1.SpringBoot简介 1.1.回顾什么是Spring Spring是一个开源框架,2003 年兴起的一个轻量级的Java 开发框架,作者:Rod Johnson . Spring是为了解决企业 ...
- 【SpringMVC】概述
概述: SpringMVC:是基于spring的一个框架, 实际上就是spring的一个模块, 专门是做web开发的. 理解是servlet的一个升级 Sp ...
- Spring源码分析【5】-Spring MVC处理流程
org.apache.catalina.core.ApplicationFilterChain.doFilter 获取Filter org.apache.catalina.core.Applicati ...
- HikariPool 连接池问题
前言:今天在一个项目运行的时候发现一个很奇怪的问题,当我有一段时间无操作之后再进行插入操作的话,就会出现HikariPool相关的报错,在此记录一下 问题 2022-02-20 13:14:04.17 ...
- idea下,Jetty采用main方法启动web项目
为什么80%的码农都做不了架构师?>>> 对于maven多模块的spring web项目,本地开发时,启动的方式一般有如下几种: 使用容器(tomcat/jetty/resin ...
最新文章
- 技术项目 - Linux Swap
- 动态规划下的巴什博弈
- android 读取xlsx文件,android怎么解析表格.xlsx文件
- Java 网络编程(超级详细)
- 后端http缓存策略
- 蓝桥杯2019真题-完全二叉树的权值
- javafx在一个窗口点击打开另一个窗口
- springboot下,JedisPool getResource导致大量线程WAITING,服务假死
- offset 和 零点的一点解释
- 小米电视刷鸿蒙系统,小米电视怎么刷鸿蒙OS系统?一招立省上千块
- 不要虚掷你的黄金时代,不要去倾听枯燥乏味的东西,不要设法挽留无望的失败,不要把你的生命献给无知、平庸和低俗。
- Emotion Expression With Fact Transfer for Video Description基于事实传递的视频描述情感表达
- Python之文件 打开与关闭
- 服务器好玩的项目_【警示分析】秘乐、赞丽、AOT、车秘等等,你正在玩和最关心的项目,降魔一一给你分析现状!必看!!!...
- 智能家居齐发力:鸿蒙专攻系统、海尔智家专注入户、小米侧重连接
- 论坛推广需要注意哪些
- 强力卸载office2003
- AnyMP4 DVD Copy for Mac(DVD刻录工具)
- 2011-2021年北京大学数字普惠金融指数(全国省、地级市、县域均有)
- 戴尔740服务器raid型号,DELL R740服务器创建RAID1和RIAD5
热门文章
- MFC动态创建控件并响应事件代码实现过程
- 含根式的定积分计算_不定积分计算法则总结
- 设置gbk_我的gVim设置
- access开发精要(11)-对象命名规定
- codeforces educational round110 e
- 【数据竞赛】Kaggle知识点:树模型特征Embedding
- 【Python】简约而不简单|值得收藏的Numpy小抄表(含主要语法、代码)
- 【机器学习基础】(二):理解线性回归与梯度下降并做简单预测
- 心路历程:「双非」研究生数据分析春招
- 目标检测一卷到底之后,终于又有人给它挖了个新坑|CVPR2021 Oral