SpringMVC请求执行流程源码解析

文章目录

    • 0.SpringMVC九大内置组件
    • 1.processRequest方法
        • 1.请求先到service方法
        • 2.然后不管是get还是post都会跳转到processRequest方法统一处理
    • 2.doService方法
    • 3.doDispatch方法
        • 1.代码
        • 2.checkMultipart
    • 4.核心流程

0.SpringMVC九大内置组件

CleanShot 2025-02-10 at 13.35.28@2x

1.processRequest方法

1.请求先到service方法

org.springframework.web.servlet.FrameworkServlet#service

CleanShot 2025-02-10 at 13.28.08@2x

2.然后不管是get还是post都会跳转到processRequest方法统一处理

org.springframework.web.servlet.FrameworkServlet#processRequest

/**
 * 处理 HTTP 请求的核心方法,负责执行 Spring MVC 的核心请求处理流程。
 * 该方法由 FrameworkServlet 调用,用于请求的前后处理、异常捕获、异步支持等。
 *
 * @param request  HttpServletRequest 对象,表示客户端的 HTTP 请求
 * @param response HttpServletResponse 对象,表示服务器返回的 HTTP 响应
 * @throws ServletException  处理 Servlet 相关异常
 * @throws IOException       处理 I/O 相关异常
 */
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
       throws ServletException, IOException {

    // 记录请求处理的开始时间(用于统计请求耗时)
    long startTime = System.currentTimeMillis();
    Throwable failureCause = null; // 记录请求处理过程中发生的异常

    // 1. 获取当前线程的 LocaleContext(国际化上下文)
    LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
    LocaleContext localeContext = buildLocaleContext(request);

    // 2. 获取当前线程的 RequestAttributes(SpringMVC 请求属性)
    RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
    ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

    // 3. 获取 WebAsyncManager(用于处理异步请求)
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

    // 4. 初始化线程变量(将请求作用域信息存入 ThreadLocal)
    initContextHolders(request, localeContext, requestAttributes);

    try {
       // 5. 调用 doService 进行实际的请求处理(由子类实现,比如 DispatcherServlet)
       doService(request, response);
    }
    catch (ServletException | IOException ex) {
       failureCause = ex;  // 记录异常
       throw ex;  // 继续向上抛出异常
    }
    catch (Throwable ex) {
       failureCause = ex;  // 记录异常
       throw new NestedServletException("Request processing failed", ex);
    }

    finally {
       // 6. 还原之前的 LocaleContext 和 RequestAttributes,防止线程污染
       resetContextHolders(request, previousLocaleContext, previousAttributes);
       
       // 7. 标记请求处理完成,通知所有监听器
       if (requestAttributes != null) {
          requestAttributes.requestCompleted();
       }

       // 8. 记录请求处理结果(用于日志记录)
       logResult(request, response, failureCause, asyncManager);

       // 9. 发布请求处理完成事件(Spring 事件机制)
       publishRequestHandledEvent(request, response, startTime, failureCause);
    }
}

2.doService方法

org.springframework.web.servlet.DispatcherServlet#doService

/**
 * 处理 HTTP 请求的核心方法,负责调度 SpringMVC 请求,并将请求转发给 `doDispatch()` 进行处理。
 * 该方法由 `FrameworkServlet` 调用,主要职责包括:
 * 1. 记录请求日志
 * 2. 处理包含(include)请求
 * 3. 将 SpringMVC 组件(如 `WebApplicationContext`、`LocaleResolver`)绑定到 `request`
 * 4. 处理 FlashMap(用于跨请求传递参数)
 * 5. 解析 `RequestPath`
 * 6. 调用 `doDispatch()` 进行请求分发
 *
 * @param request  HttpServletRequest 对象,表示客户端的 HTTP 请求
 * @param response HttpServletResponse 对象,表示服务器返回的 HTTP 响应
 * @throws Exception 可能抛出的异常
 */
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
	// 1. 记录请求日志(用于调试)
	logRequest(request);

	// 2. 处理包含(include)请求(一般不用)
	// 如果请求是 include(如 JSP 的 <jsp:include>),需要保存原始的请求属性
	Map<String, Object> attributesSnapshot = null;
	if (WebUtils.isIncludeRequest(request)) {
		attributesSnapshot = new HashMap<>();
		Enumeration<?> attrNames = request.getAttributeNames();
		while (attrNames.hasMoreElements()) {
			String attrName = (String) attrNames.nextElement();
			// 只有在 cleanupAfterInclude 为 true 或者 以 `org.springframework.` 开头的属性才会保存
			if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
				attributesSnapshot.put(attrName, request.getAttribute(attrName));
			}
		}
	}

	// 3. 将 SpringMVC 相关对象存入 `request`,供 Controller 和 View 使用
	request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext()); // 绑定 WebApplicationContext
	request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver); // 绑定 LocaleResolver
	request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver); // 绑定 ThemeResolver
	request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource()); // 绑定 ThemeSource(主题源)

	// 4. 处理 FlashMap(用于跨请求数据传输,方便重定向时传递参数)
	if (this.flashMapManager != null) {
		FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
		if (inputFlashMap != null) {
			// 绑定输入 FlashMap,防止多次修改
			request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
		}
		// 绑定输出 FlashMap(用于存储新传递的数据)
		request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
		request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
	}

	// 5. 解析 `RequestPath`
	RequestPath previousRequestPath = null;
	if (this.parseRequestPath) {
		// 先保存原始的 RequestPath(如果有的话)
		previousRequestPath = (RequestPath) request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE);
		// 解析并缓存 RequestPath
		ServletRequestPathUtils.parseAndCache(request);
	}

	try {
		// 6. 处理请求:调用 `doDispatch()` 进行请求分发
		doDispatch(request, response);
	}
	finally {
		// 7. 如果请求是 include,并且不是异步请求,则恢复原始请求属性
		if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
			if (attributesSnapshot != null) {
				restoreAttributesAfterInclude(request, attributesSnapshot);
			}
		}

		// 8. 如果解析了 `RequestPath`,则恢复之前的路径信息
		if (this.parseRequestPath) {
			ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);
		}
	}
}

3.doDispatch方法

1.代码
/**
 * 执行请求分发,调用对应的 `Handler` 处理 HTTP 请求。
 * <p>主要流程:
 * 1. 确定请求的处理器(Handler)和处理器适配器(HandlerAdapter)
 * 2. 处理 `Last-Modified` 头,提高 GET 请求的缓存命中率
 * 3. 执行拦截器 `preHandle()`
 * 4. 调用 `HandlerAdapter.handle()` 方法,执行控制器(Controller)
 * 5. 处理 `ModelAndView`,渲染视图
 * 6. 处理异常
 * 7. 执行拦截器 `postHandle()` 和 `afterCompletion()`
 * 8. 处理异步请求
 *
 * @param request  当前 HTTP 请求
 * @param response 当前 HTTP 响应
 * @throws Exception 处理过程中可能抛出的异常
 */
@SuppressWarnings("deprecation")
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request; // 处理后的请求对象
    HandlerExecutionChain mappedHandler = null; // 处理器链(包含处理器 + 拦截器)
    boolean multipartRequestParsed = false; // 是否解析了 multipart 请求(是否是上传请求)

    // 获取 WebAsyncManager(用于管理异步请求)
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

    try {
        ModelAndView mv = null; // 视图模型
        Exception dispatchException = null; // 处理过程中可能抛出的异常

        try {
            // 1. 检查请求是否为 Multipart 类型(如文件上传),并进行解析
            processedRequest = checkMultipart(request);
            multipartRequestParsed = (processedRequest != request);

            // 2. 通过 `HandlerMapping` 获取当前请求对应的 `HandlerExecutionChain`
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null) {
                noHandlerFound(processedRequest, response); // 404 处理
                return;
            }

            // 3. 通过 `HandlerAdapter` 获取支持该处理器的适配器(SpringMVC 允许不同的控制器风格)
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

            // 4. 处理 HTTP `Last-Modified` 头,提高 GET 请求的缓存命中率
            String method = request.getMethod();
            boolean isGet = HttpMethod.GET.matches(method);
            if (isGet || HttpMethod.HEAD.matches(method)) {
                long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                    return; // 直接返回 304 Not Modified
                }
            }

            // 5. 执行拦截器 `preHandle()`,如果返回 false,直接终止请求
            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }

            // 6. 调用 `HandlerAdapter.handle()`,执行 Controller 方法
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

            // 7. 检查是否为异步请求,如果是,则不继续执行后续流程
            if (asyncManager.isConcurrentHandlingStarted()) {
                return;
            }

            // 8. 处理默认视图名称
            applyDefaultViewName(processedRequest, mv);

            // 9. 执行拦截器 `postHandle()`,此时 `ModelAndView` 还未渲染
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        }
        catch (Exception ex) {
            dispatchException = ex;
        }
        catch (Throwable err) {
            // 处理 `Handler` 方法中抛出的 `Error`
            dispatchException = new NestedServletException("Handler dispatch failed", err);
        }

        // 10. 处理请求结果,包括渲染视图和异常处理
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
    catch (Exception ex) {
        // 11. 触发 `afterCompletion()` 方法,保证拦截器总能执行
        triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    }
    catch (Throwable err) {
        triggerAfterCompletion(processedRequest, response, mappedHandler,
                new NestedServletException("Handler processing failed", err));
    }
    finally {
        if (asyncManager.isConcurrentHandlingStarted()) {
            // 12. 如果请求是异步的,则调用拦截器的 `applyAfterConcurrentHandlingStarted()`
            if (mappedHandler != null) {
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        }
        else {
            // 13. 清理 multipart 请求的资源
            if (multipartRequestParsed) {
                cleanupMultipart(processedRequest);
            }
        }
    }
}

2.checkMultipart
/**
 * 检查当前请求是否是 multipart(多部分)请求,并进行解析。
 * <p>如果请求是 multipart 类型(如文件上传),则使用 `MultipartResolver` 进行解析,
 * 并将请求转换为 `MultipartHttpServletRequest`,否则返回原始请求。
 *
 * <p>主要处理逻辑:
 * 1. **检查是否配置了 `MultipartResolver`**
 * 2. **检查请求是否是 multipart 类型**
 * 3. **如果请求已经被解析过,则直接返回**
 * 4. **如果解析失败过,则跳过重新解析**
 * 5. **尝试解析 multipart 请求**
 * 6. **异常处理**
 *
 * @param request 当前 HTTP 请求
 * @return 处理后的请求(如果是 multipart,则返回 `MultipartHttpServletRequest`,否则返回原始请求)
 * @throws MultipartException 如果解析 multipart 失败,则抛出异常
 * @see MultipartResolver#resolveMultipart
 */
protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
    // 1. 判断是否配置了 `MultipartResolver`,并且请求是否为 multipart 类型
    if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
        
        // 2. 如果请求已经是 `MultipartHttpServletRequest`,说明已经解析过,直接返回
        if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
            if (DispatcherType.REQUEST.equals(request.getDispatcherType())) {
                logger.trace("Request already resolved to MultipartHttpServletRequest, e.g. by MultipartFilter");
            }
        }
        
        // 3. 如果当前请求之前解析 multipart 失败,则跳过重新解析
        else if (hasMultipartException(request)) {
            logger.debug("Multipart resolution previously failed for current request - " +
                    "skipping re-resolution for undisturbed error rendering");
        }
        
        // 4. 尝试解析 multipart 请求
        else {
            try {
                return this.multipartResolver.resolveMultipart(request);
            }
            catch (MultipartException ex) {
                // 5. 如果请求中已经存在 `ERROR_EXCEPTION_ATTRIBUTE`,说明是错误请求,记录日志后继续处理
                if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {
                    logger.debug("Multipart resolution failed for error dispatch", ex);
                    // 继续使用原始 request 进行处理
                }
                else {
                    // 6. 解析失败,抛出异常
                    throw ex;
                }
            }
        }
    }
    
    // 7. 如果请求不是 multipart,或解析失败,则返回原始请求
    return request;
}

4.核心流程

CleanShot 2025-02-10 at 15.01.11@2x

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/969215.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

在vivado中对数据进行延时,时序对齐问题上的理清

在verilog的ISP处理流程中&#xff0c;在完成第一个模块的过程中&#xff0c;我经常感到困惑&#xff0c;到底是延时了多少个时钟&#xff1f;今日对这几个进行分类理解。 目录 1.输入信号激励源描述 1.1将数据延时[9]个clk 1.2将vtdc与hzdc延时[9]个clk(等价于单bit的数据…

Spring 项目接入 DeepSeek,分享两种超简单的方式!

⭐自荐一个非常不错的开源 Java 面试指南&#xff1a;JavaGuide &#xff08;Github 收获148k Star&#xff09;。这是我在大三开始准备秋招面试的时候创建的&#xff0c;目前已经持续维护 6 年多了&#xff0c;累计提交了 5600 commit &#xff0c;共有 550 多位贡献者共同参与…

蓝桥杯-洛谷刷题-day5(C++)(为未完成)

1.P1328 [NOIP2014 提高组] 生活大爆炸版石头剪刀布 i.题目 ii.代码 #include <iostream> #include <string> using namespace std;int N, Na, Nb; //0-"剪刀", 1-"石头", 2-"布", 3-"蜥", 4-"斯"&#xff1…

MySQL - 索引 - 介绍

索引(Index)是帮助数据库高效获取数据的数据结构. 结构 语法 创建索引 creat [unique] index 索引名 on 表名 (字段名, ...); //创建唯一索引时加上unique, 多个字段用逗号隔开 查看索引 show index from 表名; 删除索引 drop index 索引名 on 表名;

2021年全国研究生数学建模竞赛华为杯E题信号干扰下的超宽带(UWB)精确定位问题求解全过程文档及程序

2021年全国研究生数学建模竞赛华为杯 E题 信号干扰下的超宽带(UWB)精确定位问题 原题再现&#xff1a; 一、背景   UWB&#xff08;Ultra-Wideband&#xff09;技术也被称之为“超宽带”&#xff0c;又称之为脉冲无线电技术。这是一种无需任何载波&#xff0c;通过发送纳秒…

安装WPS后,导致python调用Excel.Application异常,解决办法

在使用xlwings编辑excel文件时&#xff0c;默认调用的是“Excel.Application”&#xff0c;如果安装过wps&#xff0c;会导致该注册表为WPS&#xff0c;会导致xlwings执行异常 因为安装过WPS&#xff0c;导致与Excel不兼容的问题&#xff0c;想必大家都听说过。有些问题及时删…

STM32智能小车(循迹、跟随、避障、测速、蓝牙、wifi、4g、语音识别)总结

前言 有需要帮忙代做51和32小车或者其他单片机项目&#xff0c;课程设计&#xff0c;报告&#xff0c;PCB原理图的小伙伴&#xff0c;可以在文章最下方加我V交流咨询&#xff0c;本篇文章的小车所有功能实现的代码还有硬件清单放在资源包里&#xff0c;有需要的自行下载即可&a…

机器学习所需要的数学知识【01】

总览 导数 行列式 偏导数 概理论 凸优化-梯度下降 kkt条件

singleTaskAndroid的Activity启动模式知识点总结

一. 前提知识 1.1. 任务栈知识 二. Activity启动模式的学习 2.1 standard 2.2 singleTop 2.3.singleTask 2.4.singleInstance 引言&#xff1a; Activity作为四大组件之一&#xff0c;也可以说Activity是其中最重要的一个组件&#xff0c;其负责调节APP的视图&#xff…

Java中使用EasyExcel

Java中使用EasyExcel 文章目录 Java中使用EasyExcel一&#xff1a;EasyExcel介绍1.1、核心函数导入数据导出数据 1.2、项目实际应用导入数据导出数据 1.3、相关注解ExcelProperty作用示例 二&#xff1a;EasyExcel使用2.1、导入功能2.2、导出功能 三&#xff1a;EasyExcel完整代…

WinForm 防破解、反编译设计文档

一、引言 1.1 文档目的 本设计文档旨在阐述 WinForm 应用程序防破解、反编译的设计方案&#xff0c;为开发团队提供详细的技术指导&#xff0c;确保软件的知识产权和商业利益得到有效保护。 1.2 背景 随着软件行业的发展&#xff0c;软件破解和反编译现象日益严重。WinForm…

基于SpringBoot和PostGIS的省域“地理难抵点(最纵深处)”检索及可视化实践

目录 前言 1、研究背景 2、研究意义 一、研究目标 1、“地理难抵点”的概念 二、“难抵点”空间检索实现 1、数据获取与处理 2、计算流程 3、难抵点计算 4、WebGIS可视化 三、成果展示 1、华东地区 2、华南地区 3、华中地区 4、华北地区 5、西北地区 6、西南地…

Jenkins 部署 之 Mac 一

Jenkins 部署 之 Mac 一 一.Jenkins 部署依赖 JDK 环境 查看 Mac JDK 环境&#xff0c;如果没有安装&#xff0c;先安装 打开终端输入命令:java -version Mac安装配置 JDK 二. 检查 HomeBrew 安装 检查 HomeBrew 是否安装&#xff0c;终端输入命令:brew -v Mac安装HomeB…

AN 433:源同步接口的约束与分析

文章目录 简介时钟和数据的关系SDR&#xff08;单数据速率&#xff09;和 DDR&#xff08;双数据速率&#xff09;接口约束默认时序分析行为 源同步输出输出时钟输出时钟约束时钟电路和约束示例 以系统为中心的输出延迟约束输出最大延时输出最小延时 以系统为中心的输出时序例外…

webshell通信流量分析

环境安装 Apatche2 php sudo apt install apache2 -y sudo apt install php libapache2-mod-php php-mysql -y echo "<?php phpinfo(); ?>" | sudo tee /var/www/html/info.php sudo ufw allow Apache Full 如果成功访问info.php&#xff0c;则环境安…

docker学习---第3步:docker实操大模型

文章目录 1.Images2.Container3.DockerfileENTRYPOINT和CMDCOPY和ADDLABLE、EXPOSE和VOLUME卷中的数据是如何做数据备份的&#xff1f; ARG和ENVHEALTHCHECK 4. Network&#xff08;本节讲容器与容器之间的通信方案&#xff09; 跟着b站 胖虎遛二狗学习 Docker动手入门 &…

DeepSeek系统崩溃 | 极验服务如何为爆火应用筑起安全防线?

引言 极验服务让您的产品站在风口之时&#xff0c;不必担心爆红是灾难的开始&#xff0c;而是期待其成为驱动持续创新的全新起点。 01现象级狂欢背后&#xff0c;你的业务安全防线抗得住吗&#xff1f; “近期DeepSeek线上服务受到大规模恶意攻击&#xff0c;注册可能繁忙&am…

中国计算机学会(CCF)新规解读:CSP-J/S年龄限制政策

中国计算机学会&#xff08;CCF&#xff09;新规解读&#xff1a;CSP-J/S年龄限制政策 一、政策背景与动机 问题根源 低龄化竞赛趋势&#xff1a;近年来&#xff0c;CSP-J/S&#xff08;非专业级软件能力认证&#xff09;参赛者中小学生比例显著增加&#xff0c;部分学生甚至在…

K8s之存储卷

一、容忍、crodon和drain 1.容忍 即使节点上有污点&#xff0c;依然可以部署pod。 字段&#xff1a;tolerations 实例 当node01上有标签test11&#xff0c;污点类型为NoSchedule&#xff0c;而node02没有标签和污点&#xff0c;此时pod可以在node01 node02上都部署&#xff0c…

用大模型学大模型03-数学基础 概率论 条件概率 全概率公式 贝叶斯定理

要深入浅出地理解条件概率与贝叶斯定理&#xff0c;可以从以下几个方面入手&#xff0c;结合理论知识和实例进行学习&#xff1a; 贝叶斯定理与智能世界的暗语 条件概率&#xff0c;全概率公式与贝叶斯公式的推导&#xff0c;理解和应用 拉普拉斯平滑 贝叶斯解决垃圾邮件分类 …