Spring MVC 的调用(12)
目录
SpringMVC流程
源码分析
第一步:用户发起请求到前端控制器(DispatcherServlet)
第二步:前端控制器请求处理器映射器(HandlerMappering)去查找处理器(Handle):通过xml配置或者注解进行查找
1. 根据请求的uri拿到对应的HandlerMethod对象
第三步:找到以后处理器映射器(HandlerMappering)像前端控制器返回执行链(HandlerExecutionChain)
第四步:前端控制器(DispatcherServlet)调用处理器适配器(HandlerAdapter)去执行处理器(Handler)
第五步:处理器适配器去执行Handler
第六步:Handler执行完给处理器适配器返回ModelAndView
第七步:处理器适配器向前端控制器返回ModelAndView
ModelAndView演示 :
第八步:前端控制器请求视图解析器(ViewResolver)去进行视图解析,并返回View
第九步:视图解析器像前端控制器返回View
第十步:前端控制器对视图进行渲染
第十一步:前端控制器向用户响应结果
SpringMVC流程

第一步:用户发起请求到前端控制器(DispatcherServlet)
第二步:前端控制器请求处理器映射器(HandlerMappering)去查找处理器(Handle):通过xml配置或者注解进行查找
第三步:找到以后处理器映射器(HandlerMappering)像前端控制器返回执行链(HandlerExecutionChain)
第四步:前端控制器(DispatcherServlet)调用处理器适配器(HandlerAdapter)去执行处理器(Handler)
第五步:处理器适配器去执行Handler
第六步:Handler执行完给处理器适配器返回ModelAndView
第七步:处理器适配器向前端控制器返回ModelAndView
第八步:前端控制器请求视图解析器(ViewResolver)去进行视图解析
第九步:视图解析器像前端控制器返回View
第十步:前端控制器对视图进行渲染
第十一步:前端控制器向用户响应结果
此处需要强调的是
DispatcherServlet:作为前端控制器,整个流程控制的中心,控制其它组件执行,统一调度,降低组件之间的耦合性,提高每个组件的扩展性。
HandlerMapping:通过扩展处理器映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。
HandlAdapter:通过扩展处理器适配器,支持更多类型的处理器。
ViewResolver:通过扩展视图解析器,支持更多类型的视图解析,例如:jsp、freemarker、pdf、excel等。
源码分析
第一步:用户发起请求到前端控制器(DispatcherServlet)
其实就是前端请求调用到了DispatcherServlet的doService方法,而doService方法又调用到了doDispatch 核心方法:

而 doDispatch 方法负责整个Spring MVC的调度与响应,是核心中的核心:

第二步:前端控制器请求处理器映射器(HandlerMappering)去查找处理器(Handle):通过xml配置或者注解进行查找
其实,就是在doDispatch内部调用了 getHandler 方法,它会获取到所有的handlerMapping对象进行遍历,找到符合匹配规则的handlerMapping就直接返回:
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {//handlerMappering实例if (this.handlerMappings != null) {for (HandlerMapping mapping : this.handlerMappings) {//获取HandlerMethod和过滤器链的包装类HandlerExecutionChain handler = mapping.getHandler(request);if (handler != null) {return handler;}}}return null;}
注意: 以上的for循环遍历所有的handlerMapping类,并且调用每个类的都调用了getHandler方法,是一个策略设计模式,可以避免大量的 if 判断。
这里,我们需要关注一下requestMappingHandlerMapping这个对象,因为上一篇博客中,我们提到了是在requestMappingHandlerMapping创建完以后建立映射关系的。遍历到requestMappingHandlerMapping对象后进入getHanlder方法 内部:

1. 根据请求的uri拿到对应的HandlerMethod对象
其实,就是调用 getHandlerInternal 方法的过程,上一讲我们提到根据 URL 和 requestMappingInfo建立了映射,而requestMappingInfo又和HandlerMethod建立了映射,此处肯定也是按照这个规则逐步去查找HandlerMethod对象的。 下面进行源码验证:

进入这个方法内部:
首先,我们发现它根据URL 拿到了 requestMappingInfo 对象:

其次,根据requestMappingInfo拿HandlerMethod对象,进入这个方法内部进行确认:

第三步:找到以后处理器映射器(HandlerMappering)像前端控制器返回执行链(HandlerExecutionChain)
主方法:

进入这个方法内部,看看具体是如何 获取 HandlerExecutionChain 对象的
补充说明一下:拦截器是我们自己定义的,它有3个方法分别问前置过滤器、中置过滤器 和 后置过滤器:
package com.xiangxue.jack.interceptor;import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;@Component
public class UserInterceptor implements HandlerInterceptor {//前置过滤器@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("======UserInterceptor用户权限校验=========");return true;}//中置过滤器@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {System.out.println("========UserInterceptor修改modelAndView======");}//后置过滤器@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {System.out.println("========UserInterceptor资源释放======");}
}
而拦截器是在 有 @EnableWebMvc的类, 并且这个类继承了 WebMvcConfigurerAdapter类 中添加进去的。

第四步:前端控制器(DispatcherServlet)调用处理器适配器(HandlerAdapter)去执行处理器(Handler)
回到核心方法 doDispatch 中,在这个方法内部调用了 getHandlerAdapter 进行获取处理器适配器的:

进入这个方法内部, 看看具体是如何获取到处理器适配器的:

第五步:处理器适配器去执行Handler
第六步:Handler执行完给处理器适配器返回ModelAndView
第七步:处理器适配器向前端控制器返回ModelAndView
第五、六、七步可以合起来一起分析。第三步的时候说到了拦截器,其实在执行hand方法之前会进行 拦截器 中的前置过滤器的调用:

成功进入到了我们自己写的拦截器类中,并且调用到了前置过滤器方法:

言归正传,处理器去执行handler,其实就是调用到我们的Controller类的对应方法而已。就是进入到业务类中,进行实际的业务调用。最终返回的数据类型包装成ModelAndView返回给处理器适配器, 而处理器适配器向前端控制器返回ModelAndView。

还需要补充一点,中置过滤器的调用时序, 是当 ha.handle 掉完以后, 也就是 Controller 里面具体方法调用完以后才轮到中置过滤器调用。 而中置拦截器可以修改 modelAndView。

成功调到中置过滤器 
ModelAndView就是最原始的Spring MVC返回值,如果使用了@ResponseBody注解或者其他注解改变返回值,那就没有ModelAndView了。

ModelAndView演示 :
首先定义jsp, 因为ModelAndView需要按照特定的规则去解析,需要有对应的jsp文件

重写中置过滤器

设置MVC 的jsp路径:

Controller 类:

调用结果:
URL: http://localhost:9090/user/queryUser?language=tw
页面:
URL: http://localhost:9090/user/queryUser?language=zh
页面:

第八步:前端控制器请求视图解析器(ViewResolver)去进行视图解析,并返回View
还是核心方法 doDispatch方法体中,在执行完中置过滤器以后,我们就开始进行视图渲染的方法调用了。

源码如下:
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,@Nullable Exception exception) throws Exception {boolean errorView = false;//如果异常不为空if (exception != null) {if (exception instanceof ModelAndViewDefiningException) {logger.debug("ModelAndViewDefiningException encountered", exception);mv = ((ModelAndViewDefiningException) exception).getModelAndView();}else {Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);mv = processHandlerException(request, response, handler, exception);errorView = (mv != null);}}// Did the handler return a view to render?//视图渲染,响应视图if (mv != null && !mv.wasCleared()) {render(mv, request, response);if (errorView) {WebUtils.clearErrorRequestAttributes(request);}}else {if (logger.isTraceEnabled()) {logger.trace("No view rendering, null ModelAndView returned.");}}if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {// Concurrent handling started during a forwardreturn;}//后置过滤器if (mappedHandler != null) {mappedHandler.triggerAfterCompletion(request, response, null);}}
核心方法就是 render(mv, request, response), 它负责视图的渲染与响应

获取到视图解析器:

进入方法以后,我们发现视图解析器会根据我们实际业务方法,生成各种各样的View,然后根据当前的方法获取到最优的View并进行返回,直到返回到前端控制器(DispatcherServlet)中.

我们根据上图中的步骤逐步进行源码解析:
1、创建视图View

调用 createView 方法创建新视图 View:

最终会创建视图View,并且对视图进行各种属性的设置,比如视图的URL:

2. 根据ContentType 以及 ViewName等很多的条件,选取最佳的 View

而ContentType 则是定义在jsp页面中的:

第九步:视图解析器像前端控制器返回View
这一步最简单,就是根据第八步选取的View一直进行返回,直到DispatcherServlet中。

第十步:前端控制器对视图进行渲染

核心方法:
@Overrideprotected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {//把响应数据设置到request对象中// Expose the model object as request attributes.exposeModelAsRequestAttributes(model, request);// Expose helpers as request attributes, if any.exposeHelpers(request);//获取到跳转的地址// Determine the path for the request dispatcher.String dispatcherPath = prepareForRendering(request, response);// Obtain a RequestDispatcher for the target resource (typically a JSP).RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);if (rd == null) {throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +"]: Check that the corresponding file exists within your web application archive!");}// If already included or response already committed, perform include, else forward.if (useInclude(request, response)) {response.setContentType(getContentType());if (logger.isDebugEnabled()) {logger.debug("Including [" + getUrl() + "]");}rd.include(request, response);}else {// Note: The forwarded resource is supposed to determine the content type itself.if (logger.isDebugEnabled()) {logger.debug("Forwarding to [" + getUrl() + "]");}rd.forward(request, response);}}
其实,就是一个forward请求转发到对应的 jsp 页面而已。
第十一步:前端控制器向用户响应结果
请求都转发到 jsp 页面了, 这一步确实不知道该说些什么了。 就此结束本篇。
后置过滤器:
后置过滤器是最后调用的,目的就是释放一些资源,总之就是最后干的事情。最终会调到我们自己定义的拦截器中的后置过滤器的方法中:

自定义的后置拦截器中的后置过滤器方法:

我们在使用Spring mvc的时候,经常会使用各种各样的注解进行参数的传递,比如 @RequestParam 、@PathVariable、@ModelAttribute、@CookieValue、@RequestHeader等各种各样的注解。下一篇会专门针对这些注解,挑出几个常用的进行分析。
相关文章:
Spring MVC 的调用(12)
目录 SpringMVC流程 源码分析 第一步:用户发起请求到前端控制器(DispatcherServlet) 第二步:前端控制器请求处理器映射器(HandlerMappering)去查找处理器(Handle):通过xml配置或者…...
死磕内存篇 --- JAVA进程和linux内存间的大小关系
运行个JAVA 用sleep去hold住 package org.hjb.test; public class TestOnly { public static void main(String[] args) { System.out.println("sleep .."); try { Thread.sleep(10000000); } catch (InterruptedException e) { e.printStackTrace(); } } } java -…...
信号完整性分析:关于传输线的三十个问题解答(三)
21.FR4 中 50 欧姆传输线的单位长度电感是多少?如果阻抗加倍怎么办?(What is the inductance per length of a 50-Ohm transmission line in FR4? What if the impedance doubles?) FR4 中的所有 50 欧姆传输线的单位长度电感约…...
Java基础:Stream流常用方法
获取Stream流的方式 java.util.stream.Stream 是Java 8新加入的流接口。(并不是一个函数式接口) 获取一个流非常简单,有以下几种常用的方式: 所有 Collection 集合都可通过 stream 默认方法获取流(顺序流)…...
ImageNet使用方法(细节)自用!
学习记录,自用。 1. 下载数据集 点击以下链接下载种子文件,然后使用迅雷进行下载,仅下载勾选的文件即可。 https://hyper.ai/datasets/4889/c107755f6de25ba43c190f37dd0168dbd1c0877e 2. 解压 找到下载好的ILSVRC2012_img_train.tar 和…...
C/C++外观模式解析:简化复杂子系统的高效方法
C外观模式揭秘:简化复杂子系统的高效方法 引言设计模式的重要性外观模式简介与应用场景外观模式在现代软件设计中的地位与价值 外观模式基本概念外观模式的定义与核心思想提供简单接口隐藏复杂子系统设计原则与外观模式的关系外观模式实现外观模式的UML图 外观模式的…...
追梦之旅【数据结构篇】——详解小白如何使用C语言实现堆数据结构
详解小白如何使用C语言实现堆数据结构 “痛”撕堆排序~😎 前言🙌什么是堆?堆的概念及结构 堆的性质:堆的实现堆向下调整算法画图分析:堆向下调整算法源代码分享:向下调整建小堆向下调整建大堆 堆向上调整算…...
cocoscreator性能优化4-Sprite颜色数据去除
前言 Sprite是游戏内容的一个基本组成元素,包括ui、道具、立绘等各种地方都会用到。大部分情况下美术会帮我们调好图片颜色,我们只要把图片直接放到游戏里就行了。Sprite默认的渲染顶点数据中包含了颜色数据,由于我们并不需要去修改颜色&…...
系统接口幂等性设计探究
前言: 刚开始工作的时候写了一个带UI页面的工具,需要设计登录功能,登录功能也很简单,输入用户名密码点击登录,触发后台查询并比对密码,如果登录成功则返回消息给前端,前端把消息弹出提示一下。…...
C learning_7
目录 1.for循环 1.虽然while循环和for循环本质上都可以实现循环,但是它们在使用方法和场合上还是有一些区别的。 2.while循环中存在循环的三个必须条件,但是由于风格的问题使得三个部分很可能偏离较远,这样 查找修改就不够集中和方便。所以…...
PageRank算法介绍
互联网上有数百亿个网页,可以分为这么几类:不含有用信息的,比如垃圾邮件;少数人比较感兴趣的,但范围不是很广的,比如个人博客、婚礼公告或家庭像册;很多人感兴趣的并且十分有用的,比…...
springboot+vue职称评审管理系统(源码+文档)
风定落花生,歌声逐流水,大家好我是风歌,混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的职称评审管理系统。项目源码请联系风歌,文末附上联系信息 。 目前有各类成品java毕设,需要请看文末联系方式 …...
腾讯云4核8G轻量服务器12M支持多少访客同时在线?并发数怎么算?
腾讯云轻量4核8G12M轻量应用服务器支持多少人同时在线?通用型-4核8G-180G-2000G,2000GB月流量,系统盘为180GB SSD盘,12M公网带宽,下载速度峰值为1536KB/s,即1.5M/秒,假设网站内页平均大小为60KB…...
图片英文翻译成中文转换器-中文翻译英文软件
您正在准备一份重要的英文资料或文件,但是您还不是很熟练地掌握英文,需要翻译才能完成您的任务吗?哪个软件能够免费把英文文档翻译成中文?让我们带您了解如何使用我们的翻译软件来免费翻译英文文档为中文。 我们的翻译软件是一款功…...
月薪10k和40k的程序员差距有多大?
程序员的薪资一直是大家关注的焦点,相较于其他行业,程序员的高薪也是有目共睹的,而不同等级的程序员处理问题的方式与他们的薪资直接挂钩。 接下来就一起看一下月薪10k、20k、30k、40k的程序员面对问题都是怎么处理的吧! 场景一 …...
gateway整合knife4j(微服务在线文档)
文章目录 knife4j 微服务整合一、微服务与单体项目文档整合的区别二、开始整合1. 搭建一个父子maven模块的微服务,并引入gateway2.开始整合文档 总结 knife4j 微服务整合 由于单个服务的knife4j 整合之前已经写过了,那么由于效果比较好,然后微服务的项目中也想引入,所以开始微…...
ASP.NET 记录 HttpRequest HttpResponse HttpServerUtility
纯属个人记录,会有错误 HttpRequest Browser是获取客户端浏览器的信息 Cookies是获取客户端的Cookies QueryString是获取客户端提交的数据 ServerVariables是获取服务器端或客户端的环境变量信息 Browser 语法格式: Request.Browser[“浏览器特性名”] 常见的特性名 名称说…...
Python 人工智能:11~15
原文:Artificial Intelligence with Python 协议:CC BY-NC-SA 4.0 译者:飞龙 本文来自【ApacheCN 深度学习 译文集】,采用译后编辑(MTPE)流程来尽可能提升效率。 不要担心自己的形象,只关心如何…...
辉煌优配|军工板块逆市上涨,16只概念股已披露一季度业绩预喜
今日,军工股逆市上涨。 4月21日,A股三大股指低开低走,半导体、AI使用、信创工业、软件等科技属性概念领跌,国防军工、食品饮料和电力设备等板块上涨。 工业互联网中心工业规模超1.2万亿元 据央视新闻报道,本年是《工业…...
看板与 Scrum:有什么区别?
看板和Scrum是项目管理方法论,以小增量完成项目任务并强调持续改进。但是他们用来实现这些目标的过程是不同的。看板以可视化任务和连续流程为中心,而Scrum更多是关于为每个交付周期实施时间表和分配设定角色。 在看板和Scrum之间做出选择并不总是必要…...
React hook之useRef
React useRef 详解 useRef 是 React 提供的一个 Hook,用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途,下面我将全面详细地介绍它的特性和用法。 基本概念 1. 创建 ref const refContainer useRef(initialValue);initialValu…...
三维GIS开发cesium智慧地铁教程(5)Cesium相机控制
一、环境搭建 <script src"../cesium1.99/Build/Cesium/Cesium.js"></script> <link rel"stylesheet" href"../cesium1.99/Build/Cesium/Widgets/widgets.css"> 关键配置点: 路径验证:确保相对路径.…...
STM32标准库-DMA直接存储器存取
文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA(Direct Memory Access)直接存储器存取 DMA可以提供外设…...
大模型多显卡多服务器并行计算方法与实践指南
一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...
Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
html-<abbr> 缩写或首字母缩略词
定义与作用 <abbr> 标签用于表示缩写或首字母缩略词,它可以帮助用户更好地理解缩写的含义,尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时,会显示一个提示框。 示例&#x…...
2025季度云服务器排行榜
在全球云服务器市场,各厂商的排名和地位并非一成不变,而是由其独特的优势、战略布局和市场适应性共同决定的。以下是根据2025年市场趋势,对主要云服务器厂商在排行榜中占据重要位置的原因和优势进行深度分析: 一、全球“三巨头”…...
嵌入式学习之系统编程(九)OSI模型、TCP/IP模型、UDP协议网络相关编程(6.3)
目录 一、网络编程--OSI模型 二、网络编程--TCP/IP模型 三、网络接口 四、UDP网络相关编程及主要函数 编辑编辑 UDP的特征 socke函数 bind函数 recvfrom函数(接收函数) sendto函数(发送函数) 五、网络编程之 UDP 用…...
使用SSE解决获取状态不一致问题
使用SSE解决获取状态不一致问题 1. 问题描述2. SSE介绍2.1 SSE 的工作原理2.2 SSE 的事件格式规范2.3 SSE与其他技术对比2.4 SSE 的优缺点 3. 实战代码 1. 问题描述 目前做的一个功能是上传多个文件,这个上传文件是整体功能的一部分,文件在上传的过程中…...
字符串哈希+KMP
P10468 兔子与兔子 #include<bits/stdc.h> using namespace std; typedef unsigned long long ull; const int N 1000010; ull a[N], pw[N]; int n; ull gethash(int l, int r){return a[r] - a[l - 1] * pw[r - l 1]; } signed main(){ios::sync_with_stdio(false), …...
