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#doServicethis.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));}}//设置 WebApplicationContextrequest.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 {//requestHttpServletRequest processedRequest = request;//映射器处理器HandlerExecutionChain mappedHandler = null;//是否是文件上传 默认falseboolean 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) {//为空 没有找到映射器处理器 抛出异常或者返回404this.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.mzph.cn/bicheng/18686.shtml

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

相关文章

Java整合EasyExcel实战——3(上下列相同合并单元格策略)

参考&#xff1a;https://juejin.cn/post/7322156759443095561?searchId202405262043517631094B7CCB463FDA06https://juejin.cn/post/7322156759443095561?searchId202405262043517631094B7CCB463FDA06 准备条件 依赖 <dependency><groupId>com.alibaba</gr…

邻接矩阵广度优先遍历

关于图的遍历实际上就两种 广度优先和深度优先&#xff0c;一般关于图的遍历都是基于邻接矩阵的&#xff0c;考试这些&#xff0c;用的也是邻接矩阵。 本篇文章先介绍广度优先遍历的原理&#xff0c;和代码实现 什么是图的广度优先遍历&#xff1f; 这其实和二叉树的层序遍…

数据结构——二叉树的基本应用

在此之前我们已经初步了解了二叉树&#xff0c;在介绍堆的基本应用时&#xff0c;我们已经具体介绍了完全二叉树的基本应用&#xff0c;本章我们介绍二叉树的基本应用&#xff0c;这个不止指的是完全二叉树&#xff0c;而是指泛型的二叉树。 二叉树的基本应用&#xff0c;由于…

刷代码随想录有感(81):贪心算法——分发饼干

题干&#xff1a; class Solution { public:int findContentChildren(vector<int>& g, vector<int>& s) {sort(g.begin(), g.end());sort(s.begin(), s.end());int index s.size() - 1;int res 0;for(int i g.size() - 1; i > 0; i--){if(index >…

GitLab项目中添加用户,并设置其角色权限等

注意&#xff1a;创建用户(new user)&#xff0c;创建完用户然后再项目邀请用户&#xff0c;选择创建过的用户 一、以管理员身份登录GitLab的WebUI并创建用户 1>.使用管理员登录GitLab 使用管理员(root)用户登录成功后&#xff0c;点击如下图所示的小扳手&#xff0c;点击…

C++数据结构之:链List

摘要&#xff1a; it人员无论是使用哪种高级语言开发东东&#xff0c;想要更高效有层次的开发程序的话都躲不开三件套&#xff1a;数据结构&#xff0c;算法和设计模式。数据结构是相互之间存在一种或多种特定关系的数据元素的集合&#xff0c;即带“结构”的数据元素的集合&am…

在HTML和CSS当中运用显示隐藏

1.显示与隐藏 盒子显示:display:block;盒子隐藏: display:none:隐藏该元素并且该元素所占的空间也不存在了。 visibility:hidden:隐藏该元素但是该元素所占的内存空间还存在&#xff0c;即“隐身效果”。 2.圆角边框 在CSS2中添加圆角&#xff0c;我们不得不使用背景图像&am…

学习笔记——数据通信基础——数据通信网络(网络工程师)

网络工程师 网络工程&#xff0c;就是围绕着网络进行的一系列的活动&#xff0c;包括∶网络规划、设计、实施、调试、排错等。网络工程设计的知识领域很宽广&#xff0c;其中路由和交换是计算机网络的基本。 网络工程师∶是在网络工程领域&#xff0c;掌握专业的网络技术&…

散户如何参与期权交易?

期权就是股票&#xff0c;唯一区别标的物上证指数&#xff0c;会看大盘吧&#xff0c;期权交易两个方向认购做多&#xff0c;认沽做空&#xff0c;双向t0交易没了&#xff0c;期权交易跟期货一样&#xff0c;对的&#xff0c;玩的也是合约&#xff0c;唯一区别没有保证金不会爆…

127.0.0.1 和 localhost 以及 0.0.0.0 区别

之前用 nginx 的时候&#xff0c;发现用这几个 IP&#xff0c;都能正常访问到 nginx 的欢迎网页。一度认为这几个 IP 都是一样的。 但本质上还是有些区别的。 首先 localhost 就不叫 IP&#xff0c;它是一个域名&#xff0c;就跟 "baidu.com",是一个形式的东西&…

什么是Redis脑裂,如何解决呢

Redis 脑裂问题是指&#xff0c;在 Redis 哨兵模式或集群模式中&#xff0c;由于网络原因&#xff0c;导致主节点&#xff08;Master&#xff09;与哨兵&#xff08;Sentinel&#xff09;和从节点&#xff08;Slave&#xff09;的通讯中断&#xff0c;此时哨兵就会误以为主节点…

IdentiFace——多模态人脸识别系统,可捕捉从情绪到性别的所有信息及其潜力

1. 概述 面部识别系统的开发极大地推动了计算机视觉领域的发展。如今&#xff0c;人们正在积极开发多模态系统&#xff0c;将多种生物识别特征高效、有效地结合起来。 本文介绍了一种名为 IdentiFace 的多模态人脸识别系统。该系统利用基于 VGG-16 架构的模型&#xff0c;将人…

【NumPy】NumPy线性代数模块详解:掌握numpy.linalg的核心功能

&#x1f9d1; 博主简介&#xff1a;阿里巴巴嵌入式技术专家&#xff0c;深耕嵌入式人工智能领域&#xff0c;具备多年的嵌入式硬件产品研发管理经验。 &#x1f4d2; 博客介绍&#xff1a;分享嵌入式开发领域的相关知识、经验、思考和感悟&#xff0c;欢迎关注。提供嵌入式方向…

Hexo最新实战:(一)Hexo7.0+GitHub Pages博客搭建

前言 很多平台都能写博客还有创作激励&#xff0c;为什么我又要搭一个&#xff1f;为什么这次要选择用Hexo框架&#xff1f; 对应的原因是流量自由和省钱&#xff0c;第一个&#xff0c;很多平台能写但不是都有收益&#xff0c;而且平台有自身的规则&#xff0c;比如会屏蔽一…

【区块链】外部应用程序与区块链进行交互

一&#xff0c;外部应用程序与区块链进行交互案例目标与流程 1.1案例目标 掌握FISCO BCOS应用环境的搭建 与使用&#xff08;FISCO BCOSWeBASE&#xff09;掌握基于Java SpringBoot的应 用程序后端项目搭建与开发。掌握应用程序后端与FISCO BCOS 链的交互。掌握应用程序前端…

WPF hc:PropertyGrid 嵌套显示

重点&#xff1a; 编写Edit特性即可&#xff1a; public class ParameterEditor : PropertyEditorBase{public override FrameworkElement CreateElement(PropertyItem propertyItem){var pg new PropertyGrid();return pg;}public override DependencyProperty GetDependen…

基于Vue+SpirngBoot的博客管理平台的设计与实现(论文+源码)_kaic

摘 要 随着当下社会的发展&#xff0c;互联网已经成为时代的主流&#xff0c;从此进入了互联网时代&#xff0c;对大部分人来说&#xff0c;互联网在日常生活中的应用是越来越频繁&#xff0c;大家都在互联网当中互相交流、学习、娱乐。博客正是扮演这样一个角色。博客已成为当…

实验八 单区域OSPF路由协议配置

一、实验目的 掌握 OSPF 动态路由协议的配置、诊断方法。 二、实验步骤 1、 运行Cisco Packet Tracer软件&#xff0c;在逻辑工作区放入三台路由器、两台工作站PC及一台笔记本&#xff0c;分别点击各路由器&#xff0c;打开其配置窗口&#xff0c;关闭电源&#xff0c;分别加…

Python装饰器的应用

Python 中的装饰器是一种语法糖&#xff0c;可以在运行时&#xff0c;动态的给函数或类添加功能。装饰器本质上是一个函数&#xff0c;使用 函数名就是可实现绑定给函数的第二个功能 。它的作用就是在不修改被装饰对象源代码和调用方式的前提下为被装饰对象添加额外的功能。 …

Ownips+Coze海外社媒数据分析实战指南

目录 一、引言二、ISP代理简介三、应用实践——基于Ownips和coze的社媒智能分析助手3.1、Twitter趋势数据采集3.1.1、Twitter趋势数据接口分析3.1.2、Ownips原生住宅ISP选取与配置3.1.3、数据采集 3.2、基于Ownips和Coze的社媒智能助手3.2.1、Ownips数据采集插件集成3.2.2、创建…