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()); // 绑定 WebApplicationContextrequest.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver); // 绑定 LocaleResolverrequest.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver); // 绑定 ThemeResolverrequest.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);// 解析并缓存 RequestPathServletRequestPathUtils.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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深度学习项目--基于RNN的阿尔茨海默病诊断研究(pytorch实现)

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 前言 其实这个项目比较适合机器学习做&#xff0c;用XGBoost会更好&#xff0c;这个项目更适合RNN学习案例&#xff0c;测试集准确率达到百分之84.2&#xf…

华宇TAS应用中间件与因朵科技多款产品完成兼容互认证

在数字化浪潮澎湃向前的当下&#xff0c;信息技术的深度融合与协同发展成为推动各行业创新变革的关键力量。近日&#xff0c;华宇TAS应用中间件携手河北因朵科技有限公司&#xff0c;完成了多项核心产品的兼容互认证。 此次兼容性测试的良好表现&#xff0c;为双方的进一步深入…

麒麟操作系统-MySQL5.7.36二进制安装

1、创建MySQL虚拟用户 groupadd mysql useradd -g mysql -s /sbin/nologin -M mysql 2、创建目录 mkdir -p /data/file #创建文件目录 mkdir -p /opt/mysql #创建MySQL安装目录 mkdir -p /data/mysql/mysql3306/{data,logs} #创建MySQL数据及日志目录 3、安装MySQL5.7.36 …

算法学习笔记之贪心算法

导引&#xff08;硕鼠的交易&#xff09; 硕鼠准备了M磅猫粮与看守仓库的猫交易奶酪。 仓库有N个房间&#xff0c;第i个房间有 J[i] 磅奶酪并需要 F[i] 磅猫粮交换&#xff0c;硕鼠可以按比例来交换&#xff0c;不必交换所有的奶酪 计算硕鼠最多能得到多少磅奶酪。 输入M和…

Xcode证书密钥导入

证书干嘛用 渠道定期会给xcode证书&#xff0c;用来给ios打包用&#xff0c;证书里面有记录哪些设备可以打包进去。 怎么换证书 先更新密钥 在钥匙串访问中&#xff0c;选择系统。(选登录也行&#xff0c;反正两个都要导入就是了)。 mac中双击所有 .p12 后缀的密钥&#xff…

使用 Elastic APM 监控你的 C++ 应用程序

作者&#xff1a;来自 Elastic Haidar Braimaanie 在本文中&#xff0c;我们将使用 Opentelemetry CPP 客户端来监控 Elastic APM 中的 C 应用程序。 介绍 开发人员、SRE 和 DevOps 专业人员面临的主要挑战之一是缺乏能够为他们提供应用程序堆栈可见性的综合工具。市场上的许多…

前端骨架怎样实现

前端骨架屏&#xff08;Skeleton Screen&#xff09;是一种优化页面加载体验的技术&#xff0c;通常在内容加载时展示一个简易的占位符&#xff0c;避免用户看到空白页面。骨架屏通过展示页面结构的骨架样式&#xff0c;让用户有页面正在加载的感觉&#xff0c;而不是等待内容加…

团结引擎 Shader Graph:解锁图形创作新高度

Shader Graph 始终致力于为开发者提供直观且高效的着色器构建工具&#xff0c;持续推动图形渲染创作的创新与便捷。在团结引擎1.4.0中&#xff0c;Shader Graph 迎来了重大更新&#xff0c;新增多项强大功能并优化操作体验&#xff0c;助力开发者更轻松地实现高质量的渲染效果与…

微信小程序地图标记点,安卓手机一次性渲染不出来的问题

问题描述&#xff1a; 如果微信小程序端&#xff0c;渲染的标记物太多&#xff0c;安卓手机存在标记物不显示的问题&#xff0c;原因初步判断是地图还没有渲染完&#xff0c;标记物数据已经加载完了&#xff0c;导致没有在地图上显示。 解决办法&#xff1a; 使用map组件的b…

AI前端开发的崛起与ScriptEcho的助力

近年来&#xff0c;人工智能&#xff08;AI&#xff09;技术飞速发展&#xff0c;深刻地改变着软件开发的格局。尤其是在前端开发领域&#xff0c;AI的应用越来越广泛&#xff0c;催生了对AI写代码工具的需求激增&#xff0c;也显著提升了相关人才的市场价值。然而&#xff0c;…

安装并配置 MySQL

MySQL 是世界上最流行的开源关系型数据库管理系统之一&#xff0c;因其高性能、可靠性和易用性而被广泛应用于各种规模的企业级应用中。本文将详细介绍如何在不同的操作系统上安装和配置 MySQL&#xff0c;帮助你快速搭建起一个功能完善的数据库环境。 选择适合你的安装方式 …

《探秘Windows 10驱动开发:从入门到实战》

《探秘Windows 10驱动开发:从入门到实战》 为什么要在 Windows 10 编写驱动程序 在当今数字化时代,计算机已成为人们生活和工作中不可或缺的工具 ,而 Windows 10 作为一款广泛使用的操作系统,其生态系统的丰富性和复杂性不言而喻。在这个庞大的体系中,驱动程序扮演着举足…

【prompt示例】智能客服+智能质检业务模版

本文原创作者&#xff1a;姚瑞南 AI-agent 大模型运营专家&#xff0c;先后任职于美团、猎聘等中大厂AI训练专家和智能运营专家岗&#xff1b;多年人工智能行业智能产品运营及大模型落地经验&#xff0c;拥有AI外呼方向国家专利与PMP项目管理证书。&#xff08;转载需经授权&am…

算法17(力扣217)存在重复元素

1、问题 给你一个整数数组 nums 。如果任一值在数组中出现 至少两次 &#xff0c;返回 true &#xff1b;如果数组中每个元素互不相同&#xff0c;返回 false 。 2、示例 &#xff08;1&#xff09; 示例 1&#xff1a; 输入&#xff1a;nums [1,2,3,1] 输出&#xff1a;…

使用 ffmpeg 给视频批量加图片水印

背景 事情是这样的……前两天突然接到 leader 给的一个任务&#xff1a;给视频加上图片 logo 水印。我这种剪映老司机当然迷之一笑了哈哈哈哈哈&#xff0c;沉浸在简单的任务中还没反应过来巴掌就如洪水般涌来&#xff0c;因为 leader 给了几十个视频……作为一个计算机人&…

CSS 属性选择器详解与实战示例

CSS 属性选择器是 CSS 中非常强大且灵活的一类选择器&#xff0c;它能够根据 HTML 元素的属性和值来进行精准选中。在实际开发过程中&#xff0c;属性选择器不仅可以提高代码的可维护性&#xff0c;而且能够大大优化页面的样式控制。本文将结合菜鸟教程的示例&#xff0c;从基础…

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

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

计算机毕业设计——Springboot的校园新闻网站

&#x1f4d8; 博主小档案&#xff1a; 花花&#xff0c;一名来自世界500强的资深程序猿&#xff0c;毕业于国内知名985高校。 &#x1f527; 技术专长&#xff1a; 花花在深度学习任务中展现出卓越的能力&#xff0c;包括但不限于java、python等技术。近年来&#xff0c;花花更…

PyCharm 批量替换

选择替换的内容 1. 打开全局替换窗口 有两种方式可以打开全局替换窗口&#xff1a; 快捷键方式&#xff1a; 在 Windows 或 Linux 系统下&#xff0c;按下 Ctrl Shift R。在 Mac 系统下&#xff0c;按下 Command Shift R。菜单操作方式&#xff1a;点击菜单栏中的 Edit&…

深度剖析责任链模式

一、责任链模式的本质&#xff1a;灵活可扩展的流水线处理 责任链模式&#xff08;Chain of Responsibility Pattern&#xff09;是行为型设计模式的代表&#xff0c;其核心思想是将请求的发送者与接收者解耦&#xff0c;允许多个对象都有机会处理请求。这种模式完美解决了以下…