Spring MVC 工作流程源码分析

前言:

我们知道 Spring MVC 的核心是前端控制器 DispatcherServlet,客户端所有的请求都会交给 DispatcherServlet 来处理,本篇我我们来分析 Spring MVC 处理客户端请求的流程,也就是工作流程。

Sping MVC 只是储备传送门:

Servlet 和 Spring MVC

是一种服务端程序,主要用于交互式的浏览和修改数据,生成动态 Web 内容,整个过程是客户端发送请求到服务器, 服务器将请求信息发送至 Servlet,Servlet 生成相应内容并将其传给服务器,服务器将响应返回给客户端,传统的 Servlet 技术中,一个接口对应一个 Servlet,每个请求都需要在 web.xml 中配置一个 Servlet 节点,会导致我们开发出许多 Servlet,使用 Spring MVC 可以有效的简化这一步骤,简单来说 Spring MVC 其实就是 Servlet(当前这个说法不够准确)。

Sping MVC 工作流程简图

我们知道 Servlet#service 方法的主要作用是接收客户端发送的 HTTP 请求,并根据请求的类型(GET、POST、PUT、DELETE等)将请求分发到相应的处理器(Controller)进行处理,处理器处理完请求后,将结果返回给 Servlet#service 方法,再由该方法返回给客户端,Servlet#service 方法是由 Spring DispatcherServlet 类实现的,它是整个Spring MVC 框架的核心组件之一。
在这里插入图片描述

HttpServlet#service 方法源码分析

Servlet#service、GenericsServlet#service 都是接口方法,我们就从 HttpServlet#service 方法开始分析,HttpServlet#service 方法逻辑十分简单,对 HttpServletRequest 和 ServletResponse 判断后,调用了 FrameworkServlet#service 方法。

//javax.servlet.http.HttpServlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
	//判断是否实现了 HttpServletRequest 和  HttpServletResponse 接口
	if (req instanceof HttpServletRequest && res instanceof HttpServletResponse) {
		//如果实现了 就强转
		HttpServletRequest request = (HttpServletRequest)req;
		HttpServletResponse response = (HttpServletResponse)res;
		//调用  FrameworkServlet#service 方法
		this.service(request, response);
	} else {
		//否则抛出异常
		throw new ServletException("non-HTTP request or response");
	}
}

** FrameworkServlet#service 方法源码分析**

FrameworkServlet#service 方法主要就是对 LocaleContext 和 RequestAttributes 的处理,调用 DispatcherServlet#doService 方法,然后不管是否成功都会发布第二件事就是发布 ServletRequestHandledEvent 事件。

//org.springframework.web.servlet.FrameworkServlet#service
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
	//获取请求方法
	HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
	//是否是 PATCH 类型
	if (httpMethod != HttpMethod.PATCH && httpMethod != null) {
		//调用父类方法处理
		super.service(request, response);
	} else {
		//处理请求
		this.processRequest(request, response);
	}

}


//org.springframework.web.servlet.FrameworkServlet#processRequest
//处理请求
protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
	long startTime = System.currentTimeMillis();
	Throwable failureCause = null;
	//获取语言环境
	LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
	//给当前请求设置语言环境
	LocaleContext localeContext = this.buildLocaleContext(request);
	//获取请求属性
	RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
	//绑定到当前请求上
	ServletRequestAttributes requestAttributes = this.buildRequestAttributes(request, response, previousAttributes);
	//获取异步处理器
	WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
	//注册回调拦截器
	asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new FrameworkServlet.RequestBindingInterceptor());
	//将request中最新的 国际化上下文 请求参数 设置到当前线程的上下文中 也是 ThreadLocal 
	this.initContextHolders(request, localeContext, requestAttributes);

	try {
		//处理实际请求 调用DispatcherServlet#doService
		this.doService(request, response);
	} catch (IOException | ServletException var16) {
		failureCause = var16;
		throw var16;
	} catch (Throwable var17) {
		failureCause = var17;
		throw new NestedServletException("Request processing failed", var17);
	} finally {
		//还原以前的国际化上下文和请求参数设置到当前线程的上下文中
		this.resetContextHolders(request, previousLocaleContext, previousAttributes);
		if (requestAttributes != null) {
			requestAttributes.requestCompleted();
		}

		this.logResult(request, response, (Throwable)failureCause, asyncManager);
		//发布 ServletRequestHandledEvent 事件
		this.publishRequestHandledEvent(request, response, startTime, (Throwable)failureCause);
	}

}

** DispatcherServlet#doService 方法源码分析**

DispatcherServlet#doService 方法主要就是设置了 request 的一些属性,并对重定向做了一些处理,然后就调用了 DispatcherServlet#doDispatch 方法。

//org.springframework.web.servlet.DispatcherServlet#doService
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
	//日志记录
	this.logRequest(request);
	//请求属性备份
	Map<String, Object> attributesSnapshot = null;
	//是否是 include 请求
	if (WebUtils.isIncludeRequest(request)) {
		//创建属性快照
		attributesSnapshot = new HashMap();
		//获取所有属性名称
		Enumeration attrNames = request.getAttributeNames();
		//开始遍历
		label95:
		while(true) {
			String attrName;
			do {
				if (!attrNames.hasMoreElements()) {
					break label95;
				}

				attrName = (String)attrNames.nextElement();
			} while(!this.cleanupAfterInclude && !attrName.startsWith("org.springframework.web.servlet"));
			//加入属性快照
			attributesSnapshot.put(attrName, request.getAttribute(attrName));
		}
	}
	//设置 WebApplicationContext
	request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.getWebApplicationContext());
	//设置 国际化属性
	request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
	//设置 主题属性
	request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
	//设置 主题源
	request.setAttribute(THEME_SOURCE_ATTRIBUTE, this.getThemeSource());
	//重定向判断
	if (this.flashMapManager != null) {
		//重定向管理器不为空 获取重定向参数 同时更新
		FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
		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 {
		//核心方法 处理请求的方法
		this.doDispatch(request, response);
	} finally {
		//doDispatch 方法执行完后 如果不是异步调用且未完成 对已备份好的快照进行还原 在做完快照后又对 request 设置了一些属性
		if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted() && attributesSnapshot != null) {
			this.restoreAttributesAfterInclude(request, attributesSnapshot);
		}

	}

}

** DispatcherServlet#doDispatch 方法源码分析**

DispatcherServlet#doDispatch 方法是 Spring MVC 的核心方法,其内部流程就是 Spring MVC 处理请求的流程,例如先判断是否是文件上传请求、获取映射器处理器、获取处理器适配器、调用拦截器前处理方法、调用 Handler 处理请求、调用拦截器后处理方法、视图渲染、异步请求的处理等,后面篇章会逐个环节分析。

//org.springframework.web.servlet.DispatcherServlet#doDispatch
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
	//request
	HttpServletRequest processedRequest = request;
	//映射器处理器
	HandlerExecutionChain mappedHandler = null;
	//是否是文件上传 默认false
	boolean multipartRequestParsed = false;
	//异步管理器
	WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

	try {
		try {
			//模型和视图
			ModelAndView mv = null;
			//异常
			Object dispatchException = null;

			try {
				//文件上传特殊处理
				processedRequest = this.checkMultipart(request);
				//如果 request 变了 表示有经过特殊处理 也就是说是文件上传请求
				multipartRequestParsed = processedRequest != request;
				//遍历 HandlerMappings 集合 根据 HandlerMapping 获取 HandlerExecutionChain 即获取映射器处理器
				mappedHandler = this.getHandler(processedRequest);
				//映射器处理器是否为空
				if (mappedHandler == null) {
					//为空 没有找到映射器处理器 抛出异常或者返回404
					this.noHandlerFound(processedRequest, response);
					return;
				}
				//根据 mappedHandler 获取处理器适配器
				HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
				//获取请求方式
				String method = request.getMethod();
				//是否是 get 请求
				boolean isGet = "GET".equals(method);
				if (isGet || "HEAD".equals(method)) {
					//是get 请求或者 head 获取最后修改时间
					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
					if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
						//未修改 且是get 请求 直接返回
						return;
					}
				}
				//调用拦截器的preHandle方法 若返回false 处理结束
				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}
				//调用handler实际处理请求 获取ModelAndView对象 这里会调用 HandlerAdapter#handle方法处理请求 其内部会调用handler来处理具体的请求
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
				//判断异步请求是不是开始了 如果开始就直接返回
				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}
				//如果 mv 对象中没有视图 则配置默认视图
				this.applyDefaultViewName(processedRequest, mv);
				//调用拦截器的 postHandle 方法 
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			} catch (Exception var20) {
				dispatchException = var20;
			} catch (Throwable var21) {
				dispatchException = new NestedServletException("Handler dispatch failed", var21);
			}
			//处理结果 渲染视图  正常异常都会渲染
			this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
		} catch (Exception var22) {
			//调用拦截器的afterCompletion方法
			this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
		} catch (Throwable var23) {
			调用拦截器的afterCompletion方法
			this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
		}

	} finally {
		//判断异步请求是不是开始了
		if (asyncManager.isConcurrentHandlingStarted()) {
			//映射器处理器不为空
			if (mappedHandler != null) {
				//开始处理请求
				mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
			}
		} else if (multipartRequestParsed) {
			//对于文件上传的请求 清理资源 在上传的过程中文件会被保存到临时文件中 这里就会对这些文件继续清理
			this.cleanupMultipart(processedRequest);
		}

	}
}

本篇简单分析了 Spring MVC 的工作流程,从源码角度分析了一个 Spring MVC 执行一个客户端请求的过程,希望可以帮助大家更好的理解 Spring MVC 的原理。

欢迎提出建议及对错误的地方指出纠正。

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

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

相关文章

Talken - 语音命令系统

Talken - 语音命令系统 通过集成最先进的语音命令系统 Talken,释放游戏的全部潜力。 借助 Talken,您可以让玩家通过语音命令控制动作,从而重新定义游戏体验。 观看角色移动并对语音指令做出实时反应,模糊游戏与现实之间的界限。 主要特征: 🗣️ 语音驱动的游戏玩法:…

浙江大学数据结构MOOC-课后习题-第九讲-排序2 Insert or Merge

题目汇总 浙江大学数据结构MOOC-课后习题-拼题A-代码分享-2024 题目描述 测试点 思路分析 刚开始我打算想推出一个规律&#xff0c;来判断是否是归并排序&#xff0c;但实在太过于复杂&#xff0c;我很难去想出这样的规律…因此&#xff0c;参考了其他博主的思路——每做一次排…

7 步解决Android Studio模拟器切换中文输入

详细步骤传送地址&#xff1a;Android Studio 模拟器切换中文输入 目录 01 问题概述 02 模拟器的调试 01 问题概述 大家在使用Android Studio 软件进行项目演示时总会遇到一些输入框需要输入中文汉字的情况&#xff0c;由于AS自带的模拟器基本都是英文&#xff0c;这时就有同…

服务器主机托管一站式托管服务有哪些?

服务器主机托管一站式托管服务&#xff0c;作为现代企业信息化建设的重要一环&#xff0c;为企业提供了一种高效、安全、可靠的服务器运行环境。下面&#xff0c;我们将从多个方面详细介绍这一服务的内容。 一、硬件与基础设施 服务器主机托管服务首先涵盖了服务器硬件和网络基…

Vulhub——CAS 4.1、AppWeb、apisix

文章目录 一、Apereo CAS 4.1&#xff08;反序列化命令执行漏洞&#xff09;二、CVE-2018-8715&#xff08;AppWeb认证绕过漏洞&#xff09;三、apisix3.1 CVE-2020-13945(默认密钥漏洞&#xff09;3.2 CVE-2021-45232&#xff08;Dashboard API权限绕过导致RCE&#xff09; 一…

vue3 手动简单 24h 甘特图封装

甘特图 手动封装简版甘特图&#xff0c;纯展示功能&#xff0c;无其他操作 文章目录 甘特图前言效果图组件使用总结 前言 开始的思路是使用echarts 瀑布图来体现&#xff0c;但是试验后发现&#xff0c;头部时间功能不满足&#xff0c;然未找到其他组件&#xff0c;于是手动封…

厨师服穿戴智能监测摄像机

随着科技的发展&#xff0c;智能监测摄像技术已经在各个领域得到了广泛应用。近年来&#xff0c;厨师服穿戴智能监测摄像机逐渐成为了厨房管理和食品安全监控的重要工具。这种设备能够为厨师提供实时监测和反馈&#xff0c;提高工作效率和食品安全&#xff0c;进一步提高整个餐…

网上书城|基于SprinBoot+vue的网上书城管理系统(源码+数据库+文档)

网上书城管理系统 目录 基于SprinBootvue的网上书城管理系统 一、前言 二、系统设计 三、系统功能设计 1系统功能模块 2管理员功能模块 3用户后台功能模块 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介…

贵州大学24计算机考研数据速览,国家重点实验室22408复试线285分!贵州大学计算机考研考情分析!

贵州大学计算机科学与技术学院坐落在贵州大学北校区&#xff08;贵阳花溪&#xff09;。 学院现有教职工139人&#xff0c;其中专职教师126人&#xff0c;教授17人&#xff0c;副教授37人&#xff0c;讲师46人&#xff0c;高级实验师4人&#xff0c;实验师17人。具有博士学位的…

部署LAMP环境

红帽9搭建LAMP 安装Apache 2.安装数据库服务 3.安装php (1)使用IP访问/phpinfo.php 4.安装phpMyAdmin &#xff08;1&#xff09;数据库端口改为学号后五位 &#xff08;2&#xff09;登录phpmyadmin 5.SSH增加一个端口10022&#xff0c;fttp增加两个端口10080和8080 &#xf…

如何利用云平台上更好地规划安全生产教育与培训

在平台上进行安全教育和培训&#xff0c;可以采取以下步骤和策略&#xff0c;以确保教育的有效性和参与度&#xff1a; 一、明确教育目标和培训内容 确定教育目标&#xff1a;明确希望员工通过培训达到的安全意识和技能水平。 制定培训内容&#xff1a;根据行业特点、岗位需求…

科学技术创新杂志科学技术创新杂志社科学技术创新编辑部2024年第10期目录

科技创新 单桩穿越岩溶发育地层力学特征与溶洞处置措施研究 刘飞; 1-7《科学技术创新》投稿&#xff1a;cnqikantg126.com 基于多目标优化的中低压配电网电力规划研究 向星山;杨承俊;张寒月; 8-11 激光雷达测绘技术在工程测绘中的应用研究 张军伟;闫宏昌; 12-15 …

大语言模型的创意“魔法“:召唤隐藏的联想思维

随着人工智能的迅猛发展&#xff0c;大语言模型正在掀起一场"创意风暴"。这些强大的AI模型不仅能够生成栩栩如生的文本&#xff0c;还展现出惊人的创造力。但你是否好奇&#xff0c;它们的创意究竟来自何处? 最新研究表明&#xff0c;大语言模型的创意之源在于激活…

高熔体强度聚丙烯(HMSPP)属于高端聚丙烯 我国市场国产化进程有所加快

高熔体强度聚丙烯&#xff08;HMSPP&#xff09;属于高端聚丙烯 我国市场国产化进程有所加快 高熔体强度聚丙烯&#xff08;HMSPP&#xff09;又称高熔体强度PP&#xff0c;是一种含有微交联结构或长支链结构的改性聚丙烯。高熔体强度聚丙烯具有绿色环保、轻量化、结晶性好、熔…

c 系统宏有多少

在C语言中&#xff0c;系统宏&#xff08;也称为预定义宏或内置宏&#xff09;的数量并不是固定的&#xff0c;因为它们取决于C标准、编译器以及可能的其他因素。然而&#xff0c;有一些常见的预定义宏是几乎所有C编译器都支持的。 以下是一些常见的C预定义宏&#xff1a; __…

Creo模型按一定的比例放大或缩小(实际尺寸)

原来&#xff0c;距离是100mm 缩放操作 放大3倍&#xff0c;距离变为300mm

最强端侧多模态模型MiniCPM-V 2.5,8B 参数,性能超越 GPT-4V 和 Gemini Pro

前言 近年来&#xff0c;人工智能领域掀起了一股大模型热潮&#xff0c;然而大模型的巨大参数量级和高昂的算力需求&#xff0c;限制了其在端侧设备上的应用。为了打破这一局限&#xff0c;面壁智能推出了 MiniCPM 模型家族&#xff0c;致力于打造高性能、低参数量的端侧模型。…

Figma 文件批量导出到本地的方法

作为新一代UX设计师&#xff0c;我们应该熟练地使用市场上的许多设计软件&#xff0c;并更熟悉它们的软件功能。现在市场上的即时设计&#xff0c;作为一种在线合作设计工具&#xff0c;值得成为许多设计师的常用工具。最近&#xff0c;我了解到即时设计进行了新的功能更新&…

激光雷达测试板智能系统应用

在自动驾驶技术和机器人感知系统的迅猛发展中&#xff0c;激光雷达&#xff08;Lidar&#xff09;作为一种先进的测距技术&#xff0c;正逐渐成为这些系统不可或缺的组成部分。而在这一技术的实际应用前&#xff0c;对激光雷达进行精确的测试和校准是至关重要的一步。激光雷达测…

C#根据数据量自动排版标签的样例

这是一个C#根据数据量自动排版标签的样例 using System; using System.Collections.Generic; using System.Data.SqlClient; using System.Drawing; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using HslCommuni…