Spring MVC 工作流程源码分析

news2024/11/17 22:23:37

前言:

我们知道 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 的原理。

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

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

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

相关文章

HTTPS单双向认证流程详解与联想

HTTPS单向认证 HTTPS在单向认证传输的过程中会涉及到三个密钥&#xff1a; 服务端的公钥和私钥&#xff0c;用来进行非对称加密交换密钥 客户端生成的随机密钥&#xff0c;用来进行对称加密传输数据 认证过程 1.客户端向服务器发起HTTPS请求&#xff0c;连接到服务器的443端…

JS-06 原型式继承借用构造函数实现继承

目录 1 原型式继承 场景 前置问题 实现方法 2 借用构造函数实现继承 前置问题 错误的实现方式 正确的实现方式 1 原型式继承 场景 a、创建一个纯洁的对象&#xff1a;对象在控制台打印什么属性都没有 b、创建一个继承自某个父对象的子对象 前置问题 一个对象里有很…

基于Vue uni-app的自定义列表表格信息展示组件

摘要&#xff1a;随着软件技术的不断发展&#xff0c;前端开发面临着越来越多的挑战。特别是在业务场景复杂多变的情况下&#xff0c;如何提高开发效率和降低维护成本成为了关键。本文旨在探讨组件化开发在前端应用中的重要性&#xff0c;并以Vue uni-app自定义列表表格为例&am…

韶音、南卡、Oladance开放式耳机哪个好?深度测评告诉你答案!

作为一名资深数码博主&#xff0c;五年来我有幸试用了众多蓝牙耳机&#xff0c;涵盖了市场上的大小品牌。品牌方常邀请我进行产品评测&#xff0c;而我的粉丝也常在私信中求教如何挑选开放式蓝牙耳机。近期&#xff0c;我细致比对了市面上备受关注的三款开放式耳机&#xff1a;…

Linux中常见的基本指令(上)

目录 一、ls指令 1. ls 2. ls -l 3. ls -a 4.ls -F 二、qwd指令 三、cd指令 1. cd .. 2. cd / / / 3. cd ../ / / 4. cd ~ 5. cd - 五、mkdir指令 六、rmdir指令和rm指令 一、ls指令 语法 &#xff1a; ls [ 选项 ][ 目录或文件 ] 。 功能 &#xff1a;对于目录…

Python词法和语法分析工具库之ply使用详解

概要 在编程语言的开发、编译器的实现和数据解析等领域,词法分析和语法分析是关键的技术。Python的ply库是一个功能强大的词法和语法分析工具,基于经典的Lex和Yacc工具实现。ply库为开发者提供了一种简单且高效的方法,用于定义词法规则和语法规则,从而实现对自定义语言和数…

HNU-计算机体系结构-实验2-Tomasulo算法

计算机体系结构 实验2 计科210X 甘晴void 202108010XXX 1 实验目的 熟悉Tomasulo模拟器同时加深对Tomasulo算法的理解&#xff0c;从而理解指令级并行的一种方式-动态指令调度。 掌握Tomasulo算法在指令流出、执行、写结果各阶段对浮点操作指令以及load和store指令进行什么…

自动控制: 最小二乘估计(LSE)、加权最小二乘估计(WLS)和线性最小方差估计

自动控制&#xff1a; 最小二乘估计&#xff08;LSE&#xff09;、加权最小二乘估计&#xff08;WLS&#xff09;和线性最小方差估计 在数据分析和机器学习中&#xff0c;参数估计是一个关键步骤。最小二乘估计&#xff08;LSE&#xff09;、加权最小二乘估计&#xff08;WLS&…

Linux eBPF:网络、系统监控和安全领域的创新

扩展 Berkeley Packet Filter&#xff08;eBPF&#xff09;是Linux内核中的一项强大技术&#xff0c;最初用于网络数据包过滤。随着时间的推移&#xff0c;eBPF的功能和应用场景不断扩展&#xff0c;如今已成为网络、系统监控和安全等领域的重要工具。eBPF可以在Linux内核中安全…

积鼎CFDPro水文水动力模型,专为中小流域洪水“四预”研发的流体仿真技术

水动力模型与水文模型是水利工程与水文学研究中不可或缺的两大工具。水动力模型着重于流体运动的动力学机制&#xff0c;通过一系列方程组捕捉水流的时空变化&#xff0c;而概念性水文模型则侧重于流域尺度的水文循环过程&#xff0c;利用物理概念与经验关系进行近似模拟。两者…

微信小程序毕业设计-农场驿站平台系统项目开发实战(附源码+论文)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;微信小程序毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计…

Ubuntu 离线下载安装 Tmux(亲测有效)

昨晚跑NER模型中断了&#xff0c;今天就考虑安装下Tmux&#xff0c;但是一直安装不上&#xff0c;在尝试了好几次之后&#xff0c;终于不报错了&#xff01;&#xff01;特记录一下下载安装过程。&#xff08;我这里是离线下载安装的&#xff09; 1. 下载安装包 tmux wget ht…

调试小技巧:除了可以在控制台编写js代码,还有一个地方也可以,来试试吧!

写在前面 作为一名程序员&#xff0c;平常调试代码是必不可少的&#xff0c;控制台应该说是经常使用&#xff0c;除了可以看到打印的信息之外&#xff0c;我们还可以直接在控制台编写代码&#xff0c;以调试我们的代码。如下所示&#xff1a; 通过编写一些 js 代码&#xff0c…

[C++]红黑树

一、概念 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是Red或 Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制&#xff0c;红黑树确保没有一条路 径会比其他路径长出俩倍&#xff0c;因而是…

配置阿里yum源

配置阿里yum源&#xff08;这个很重要&#xff09;&#xff1a;https://developer.aliyun.com/article/1480470 1.备份系统自带yum源配置文件 mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup2.下载ailiyun的yum源配置文件 2.1 CentOS7 wge…

【Vue】响应式特性

响应式&#xff1a;简单理解就是数据改变&#xff0c;视图会自动更新。 如何访问 和 修改 data中的数据&#xff08;响应式演示&#xff09; data中的数据, 最终会被添加到实例上 例如这里&#xff0c;app身上就会拥有msg属性&#xff0c;修改msg的值&#xff0c;界面的值也会…

Deepin Linux 深度 V23 beige 官方源及换镜像源方法。

Deepin Linux 深度 V23 英文版本号&#xff1a;beige 谁起的烂名字。。。。。。 1. 打开文件管理器&#xff0c;在apt文件夹点右键&#xff08;以管理员身份打开&#xff09;&#xff0c; 2. 输入你的登录密码&#xff0c;以便打开文件夹&#xff08;管理员权限&#xff09;。…

成都市酷客焕学新媒体科技有限公司:助力品牌打破困境!

在数字化浪潮的推动下&#xff0c;营销策略对品牌的发展愈发关键。成都市酷客焕学新媒体科技有限公司&#xff0c;作为短视频营销领域的佼佼者&#xff0c;凭借其卓越的策略和实力&#xff0c;助力众多品牌在信息海洋中脱颖而出&#xff0c;实现品牌的显著增长。 酷客焕学专注于…

【机器学习】基于tensorflow实现你的第一个DNN网络

博客导读&#xff1a; 《AI—工程篇》 AI智能体研发之路-工程篇&#xff08;一&#xff09;&#xff1a;Docker助力AI智能体开发提效 AI智能体研发之路-工程篇&#xff08;二&#xff09;&#xff1a;Dify智能体开发平台一键部署 AI智能体研发之路-工程篇&#xff08;三&am…

学习sam的过程

一、抓包 我平时都是用花瓶去抓包的&#xff0c;配置也很简单。就是下载软件&#xff0c;然后一步步安装。下载地址&#xff1a;Download a Free Trial of Charles • Charles Web Debugging Proxy 。然后配置手机代理 对于那些走http协议的app是可以的&#xff0c;https的还是…