spring源码------一个请求在spring中的处理过程(从FrameworkServlet规范到DispatcherServlet)代码及流程图说明 (2)

本文主要是介绍spring源码------一个请求在spring中的处理过程(从FrameworkServlet规范到DispatcherServlet)代码及流程图说明 (2),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

      • 1.从`FrameworkServlet`到`DispatcherServlet`
        • 1.1 从`FrameworkServlet`进入到`DispatcherServlet`的`processRequest`
        • 1.2 进行真正请求处理前的准备`doService`
        • 1.3 进行请求分发的`doDispatch`
        • 后续的

 前面的文章已经讲了从 一个请求在spring中的处理过程(从Servlet规范到FrameworkServlet)代码及流程图说明接下来就是,从 FrameworkServletDispatcherServlet的部分进行分析了。

1.从FrameworkServletDispatcherServlet

 在FrameworkServletprocessRequest方法中,有调用doService方法的这个步骤。这个方法是一个抽象方法,必须由子类来实现。这个方法是处理GET, POST, PUT , DELETE请求具体逻辑的方法。

1.1 从FrameworkServlet进入到DispatcherServletprocessRequest
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {......//进行业务处理doService(request, response);......
}

 所以我们可以直接到DispatcherServlet中进一步的分析。直接看对应的doService方法

1.2 进行真正请求处理前的准备doService
	protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {logRequest(request);// Keep a snapshot of the request attributes in case of an include,  to be able to restore the original attributes after the include.//如果是一个include请求,<jsp:incluede page="xxx.jsp"/> 这种中,可能在一个请求(A)中嵌套了另外的一个请求(B),因此需要备份当前请求(A)Map<String, Object> attributesSnapshot = null;//是否是一个include请求,通过request中的javax.servlet.include.request_uri属性判断,JSP在运行期间是会被编译成相应的Servlet类来运行的,// 所以在Servlet中也会有类似的功能和调用语法,这就是RequestDispatch.include()方法,在一个被别的servlet使用RequestDispatcher的include方法调用过的servlet中,// 如果它想知道那个调用它的servlet的上下文信息该怎么办呢,那就可以通过request中的attribute中的如下属性获取:javax.servlet.include.request_uriif (WebUtils.isIncludeRequest(request)) {attributesSnapshot = new HashMap<>();Enumeration<?> attrNames = request.getAttributeNames();while (attrNames.hasMoreElements()) {String attrName = (String) attrNames.nextElement();//获取包含的请求中的获取A请求的内部B请求设定的spring的策略if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {attributesSnapshot.put(attrName, request.getAttribute(attrName));}}}// Make framework objects available to handlers and view objects.//设置web应用上下文到请求中,request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());//设置本地解析器request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);//设置主题解析器request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);//设置主题,如果没有设置则为null,默认的为WebApplicationContextrequest.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());//将request跟response保存到一个FlashMap中,FlashMap用来将一个请求跟另外一个请求关联起来,通常在redirect的时候有用if (this.flashMapManager != null) {//如果当前请求的FlashMap在之前的请求中保存过了,则取出来,并去除对应的缓存FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);//保存FlashMap到属性中if (inputFlashMap != null) {request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));}request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);}try {//进行请求分发处理doDispatch(request, response);}finally {//在FrameworkServlet中会生成WebAsyncManagerif (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {// Restore the original attribute snapshot, in case of an include.if (attributesSnapshot != null) {restoreAttributesAfterInclude(request, attributesSnapshot);}}}}

 这里对上面的流程总结一下:

  1. 检查当前的请求中是否包含另外的一个请求(也就是请求包含),如果是这种情况需要把内部请求的属性作为快照保存起来。(个人对于这一块还是有点没有理解清楚的,欢迎指点)
  2. 设置对应的web请求上下,本地解析器,主题解析器,主题到request中
  3. 检查flashMapManager是不是null(这个不会为空,在initFlashMapManager方法初始化的时候回创建),如果不是空则吧对应的request, respons设置进一个FlashMap对象中,同时设置对应的输入,输出跟flashMap管理对象到request
  4. 进行请求分发处理doDispatch方法
  5. 最后处理异步请求处理相关的逻辑,主要检查这个请求的异步处理逻辑是不是正在处理,是的就将上面保存的内部请求设置到request中。

 这里涉及到请求包含(include)跟请求转发(forward)。关于这两个东西可以百度一下或者参考下面这个文章请求包含(Include)和请求转发(Forward)这个位置理解有点费力。个人理解可能也有点缺陷,欢迎指正。

 在准备好了处理请求需要的相关环境跟参数的之后,就是进行请求的分发处理了,之所以叫分发是因为每个请求都有对应的处理类,现在进入到分发的逻辑方法doDispatch

1.3 进行请求分发的doDispatch

 因为doDispatch这个方法的内部逻辑比较复杂(方法内部调用了其他的复杂的方法逻辑),一篇博客肯定写不完,所以这里主要写整个方法内的逻辑。

	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {HttpServletRequest processedRequest = request;HandlerExecutionChain mappedHandler = null;boolean multipartRequestParsed = false;//从request中获取WebAsyncManagerWebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);try {ModelAndView mv = null;Exception dispatchException = null;try {//检查是不是multipart请求并将请求转化为MultipartHttpServletRequest类型的processedRequest = checkMultipart(request);//如果请求不是原来的request请求,则表示是multipart请求并且解析过的multipartRequestParsed = (processedRequest != request);//获取封装了HandlerInterceptor的HandlerExecutionChainmappedHandler = getHandler(processedRequest);//如果不存在对应的处理链则返回if (mappedHandler == null) {noHandlerFound(processedRequest, response);return;}//从HandlerExecutionChain中获取Handler然后寻找合适的HandlerAdapterHandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());//获取请求方式String method = request.getMethod();boolean isGet = "GET".equals(method);//检查是不是GET请求if (isGet || "HEAD".equals(method)) {//检查当前的get类型的请求的最后修改时间是不是存在的,这个参数用来减少数据传输用long lastModified = ha.getLastModified(request, mappedHandler.getHandler());//如果浏览器请求中的最后请求时间跟服务器的最后修改时间一致,并且是get类型请求则直接返回。关于lastModified这个会说明if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {return;}}//调用mappedHandler中拦截器的applyPreHandle方法,如果对应的请求不符合规则则直接返回if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}//执行对应的HandlerAdapter的Handler方法,拿到对应的视图mv = ha.handle(processedRequest, response, mappedHandler.getHandler());//检查当前的请求是否正在异步处理,如果是的则直接放弃并返回if (asyncManager.isConcurrentHandlingStarted()) {return;}//如果处理的结果返回的视图是空的则使用默认的视图,不为空则用处理的结果applyDefaultViewName(processedRequest, mv);//调用applyPostHandle方法对视图进行返回后的处理mappedHandler.applyPostHandle(processedRequest, response, mv);}catch (Exception ex) {dispatchException = ex;}catch (Throwable err) {//进行错误视图的处理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//如果mappedHandler不是null,则调用对应的mappedHandler中的AsyncHandlerInterceptorif (mappedHandler != null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}}else {// Clean up any resources used by a multipart request.//对流类型的请求,做后置的处理if (multipartRequestParsed) {cleanupMultipart(processedRequest);}}}}

 对于doDispatch方法的处理流程就跟网上的mvc处理流程图一样的,把整个流程包含在了一个方法里面。这里就用网上的一张图具体说明
流程图

  1. 首先会检查当前的请求是不是multipart/form-data类型的请求,是的则将请求进行转换为MultipartHttpServletRequest类型的request。(MultipartHttpServletRequestHttpServletRequest的子类)
  2. 获取合适的获取封装了HandlerInterceptor集合的HandlerExecutionChain,如果没有找到合适的,就直接返回404的错误页面
  3. HandlerExecutionChain中获取Handler然后寻找合适的HandlerAdapter
  4. 检查是不是get请求,是的然后在检查lastModified这个属性选择是直接返回还是进行后续的请求处理。(lastModified这个属性后面说一下,其实前面文章已经说过了)
  5. 调用前面选择的HandlerAdapterapplyPreHandle方法检查请求是否符合定义的要求,不符合就返回。
  6. 调用前面选择的HandlerAdapterhandle方法,进行逻辑的处理,然后返回ModelAndView对象
  7. 检查当前的请求是否正在异步处理,如果是的则直接放弃并返回
  8. 检查返回的ModelAndView的视图是不是null,是的则返回默认的,不是的则不处理
  9. 调用前面选择的HandlerAdapterapplyPostHandle方法对视图进行最后的处理
  10. 对正常返回的或者发生异常时生成的视图进行处理,
  11. 对异步请求或multipart/form-data类型请求进行后续的处理

 对于lastModified这个属性可以参考这篇文章HttpServlet—getLastModified与缓存

后续的

 虽然整个的request请求的逻辑只有这么多,但是里面的每个步骤都是比较复杂的。这一篇讲不完,因此挑选里面比较重要的几个步骤进行分篇讲解。主要以下几个步骤

  1. HandlerExecutionChain的选择
  2. HandlerAdapter的获取跟handle方法逻辑

这篇关于spring源码------一个请求在spring中的处理过程(从FrameworkServlet规范到DispatcherServlet)代码及流程图说明 (2)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/783320

相关文章

SpringBoot整合Flowable实现工作流的详细流程

《SpringBoot整合Flowable实现工作流的详细流程》Flowable是一个使用Java编写的轻量级业务流程引擎,Flowable流程引擎可用于部署BPMN2.0流程定义,创建这些流程定义的... 目录1、流程引擎介绍2、创建项目3、画流程图4、开发接口4.1 Java 类梳理4.2 查看流程图4

一文详解如何在idea中快速搭建一个Spring Boot项目

《一文详解如何在idea中快速搭建一个SpringBoot项目》IntelliJIDEA作为Java开发者的‌首选IDE‌,深度集成SpringBoot支持,可一键生成项目骨架、智能配置依赖,这篇文... 目录前言1、创建项目名称2、勾选需要的依赖3、在setting中检查maven4、编写数据源5、开启热

Java对异常的认识与异常的处理小结

《Java对异常的认识与异常的处理小结》Java程序在运行时可能出现的错误或非正常情况称为异常,下面给大家介绍Java对异常的认识与异常的处理,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参... 目录一、认识异常与异常类型。二、异常的处理三、总结 一、认识异常与异常类型。(1)简单定义-什么是

Before和BeforeClass的区别及说明

《Before和BeforeClass的区别及说明》:本文主要介绍Before和BeforeClass的区别及说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Before和BeforeClass的区别一个简单的例子当运行这个测试类时总结Before和Befor

Python实例题之pygame开发打飞机游戏实例代码

《Python实例题之pygame开发打飞机游戏实例代码》对于python的学习者,能够写出一个飞机大战的程序代码,是不是感觉到非常的开心,:本文主要介绍Python实例题之pygame开发打飞机... 目录题目pygame-aircraft-game使用 Pygame 开发的打飞机游戏脚本代码解释初始化部

Python pip下载包及所有依赖到指定文件夹的步骤说明

《Pythonpip下载包及所有依赖到指定文件夹的步骤说明》为了方便开发和部署,我们常常需要将Python项目所依赖的第三方包导出到本地文件夹中,:本文主要介绍Pythonpip下载包及所有依... 目录步骤说明命令格式示例参数说明离线安装方法注意事项总结要使用pip下载包及其所有依赖到指定文件夹,请按照以

SpringBoot项目配置logback-spring.xml屏蔽特定路径的日志

《SpringBoot项目配置logback-spring.xml屏蔽特定路径的日志》在SpringBoot项目中,使用logback-spring.xml配置屏蔽特定路径的日志有两种常用方式,文中的... 目录方案一:基础配置(直接关闭目标路径日志)方案二:结合 Spring Profile 按环境屏蔽关

Java使用HttpClient实现图片下载与本地保存功能

《Java使用HttpClient实现图片下载与本地保存功能》在当今数字化时代,网络资源的获取与处理已成为软件开发中的常见需求,其中,图片作为网络上最常见的资源之一,其下载与保存功能在许多应用场景中都... 目录引言一、Apache HttpClient简介二、技术栈与环境准备三、实现图片下载与保存功能1.

SpringBoot排查和解决JSON解析错误(400 Bad Request)的方法

《SpringBoot排查和解决JSON解析错误(400BadRequest)的方法》在开发SpringBootRESTfulAPI时,客户端与服务端的数据交互通常使用JSON格式,然而,JSON... 目录问题背景1. 问题描述2. 错误分析解决方案1. 手动重新输入jsON2. 使用工具清理JSON3.

java中long的一些常见用法

《java中long的一些常见用法》在Java中,long是一种基本数据类型,用于表示长整型数值,接下来通过本文给大家介绍java中long的一些常见用法,感兴趣的朋友一起看看吧... 在Java中,long是一种基本数据类型,用于表示长整型数值。它的取值范围比int更大,从-922337203685477