SpringMVC原理(设计原理+启动原理+工作原理)
文章目录
- 前言
- 正文
- 一、设计原理
- 1.1 servlet生命周期简述
- 1.2 设计原理小结
- 二、启动原理
- 2.1 AbstractHandlerMethodMapping 初始化 --RequestMapping注解解析
- 2.2 DispatcherServlet 的初始化
- 2.3 DispatcherServlet#initHandlerMappings(...) 初始化示例说明
- 三、工作原理
前言
本系列文章基本环境如下:
- java8
- springboot2.7
创建项目,使用阿里的源:https://start.aliyun.com
创建前,请先设置好自己的maven环境,java版本。保持网络正常。

选择spring-web的2.7.6 版本:


使用阿里的源创建好项目后,会自动生成的有控制器Controller,启动类,以及一个页面。
如此,准备工作就做好了。
正文
在Java还没有SpringMvc时,使用的是servlet + jsp 的方式,对外提供接口,以及和页面进行数据交互等操作。
但是,这种操作,毕竟还是不方便,功能也不够强大。
曾经的写法,需要配置xml文件,如果页面够多,光配置就是一大堆。
发展到后来,servlet3.0的时候,出现了完全注解版的写法。
关于servlet的描述这里不做过多解释,本文将对对springmvc中使用到的servlet特征进行阐述,继而分析它的设计原理,springmvc启动原理,以及工作原理
等到在Spring框架中的时候,就已经是DispatcherServlet了。
而它本身就是一个servlet,其类关系图如下:

一、设计原理
1.1 servlet生命周期简述
Servlet生命周期分为三个阶段:
- 初始化阶段:调用
init()方法实现初始化工作。 - 运行阶段:处理请求,容器创建代表HTTP请求的
ServletRequest对象和代表HTTP响应的ServletResponse对象,并将它们作为参数传递给Servlet的service()方法。 - 销毁阶段:Servlet将被销毁,生命周期结束。
Servlet本身只是一个接口,在HttpServlet实现类中,对service()方法进行了实现。
而这里的实现是套用了模版方法设计模式,将service的职责拆分了,按照请求方法的类型不同划分。
比如,如果请求方法是 GET请求,则会执行到 HttpServlet的 doGet方法;如果是POST请求,则会执行到 HttpServlet的 doPost方法。
FrameworkServlet 又对HttpServlet中的service方法进行了重写:
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());if (httpMethod != HttpMethod.PATCH && httpMethod != null) {super.service(request, response);} else {this.processRequest(request, response);}}
这里判断了请求方法,默认执行 HttpServlet的 service方法。
但是实际调用的是 doGet,doPost这类方法。而同时,这类方法也被FrameworkServlet重写了

也就是说,servlet 会触发执行到 FrameworkServlet 的 processRequest 方法。如下图所示:
这里会执行FrameworkServlet 的 doService方法。而这是一个抽象方法。其子类DispatcherServlet 对其进行了实现。如此便贯通了。
1.2 设计原理小结
在1.1小节中的分析中,Servlet继承&实现的关系如下:

也就是说,在servlet处理请求时,对于springmvc而言,就是执行 doService方法。
二、启动原理
这一小节,主要分析SpringBoot项目启动时,对SpringMvc部分的处理。
2.1 AbstractHandlerMethodMapping 初始化 --RequestMapping注解解析
谈起SpringMvc,最先想起来的俩注解应该是 Controller 和 RequestMapping。
而关于启动项目时,框架对这俩注解的处理,基本都体现在AbstractHandlerMethodMapping中。
public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {// ...省略其他方法public void afterPropertiesSet() {this.initHandlerMethods();}
}
首先,AbstractHandlerMethodMapping是个抽象类,它的子类会放到Spring容器中。
而在它的子类 RequestMappingHandlerMapping 中,对初始化方法进行了重写,具体内容如下:
public void afterPropertiesSet() {this.config = new RequestMappingInfo.BuilderConfiguration();this.config.setTrailingSlashMatch(this.useTrailingSlashMatch());this.config.setContentNegotiationManager(this.getContentNegotiationManager());if (this.getPatternParser() != null) {this.config.setPatternParser(this.getPatternParser());Assert.isTrue(!this.useSuffixPatternMatch && !this.useRegisteredSuffixPatternMatch, "Suffix pattern matching not supported with PathPatternParser.");} else {this.config.setSuffixPatternMatch(this.useSuffixPatternMatch());this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch());this.config.setPathMatcher(this.getPathMatcher());}super.afterPropertiesSet();
}
在设置了一堆配置之后,最终调用的还是 AbstractHandlerMethodMapping 的initHandlerMethods 方法。
protected void initHandlerMethods() {// 获取spring容器中的beanNameString[] var1 = this.getCandidateBeanNames();int var2 = var1.length;for(int var3 = 0; var3 < var2; ++var3) {String beanName = var1[var3];if (!beanName.startsWith("scopedTarget.")) {// 通过beanName映射出methodHandlerthis.processCandidateBean(beanName);}}// 初始化handlerMethodsthis.handlerMethodsInitialized(this.getHandlerMethods());}protected void processCandidateBean(String beanName) {Class<?> beanType = null;try {// 通过beanName获取当前的类型beanType = this.obtainApplicationContext().getType(beanName);} catch (Throwable var4) {if (this.logger.isTraceEnabled()) {this.logger.trace("Could not resolve type for bean '" + beanName + "'", var4);}}// 当前beanName对应的类定义不为空,并且带有Controller 或 RequestMapping注解时,对其进行处理if (beanType != null && this.isHandler(beanType)) {this.detectHandlerMethods(beanName);}}
可以看到,最终处理控制器时,是调用了detectHandlerMethods 方法。具体内容如下:
protected void detectHandlerMethods(Object handler) {// 通过beanName获取到对应的类型Class<?> handlerType = handler instanceof String ? this.obtainApplicationContext().getType((String)handler) : handler.getClass();if (handlerType != null) {// 获取你自己定义的控制器类型Class<?> userType = ClassUtils.getUserClass(handlerType);// 将类中符合条件(标注了RequestMapping注解)的 method 映射为 RequestMappingInfo 对象,并放入map中;这一步的实现,在其子类中。并且聚合它们的请求路径。Map<Method, T> methods = MethodIntrospector.selectMethods(userType, (method) -> {try {return this.getMappingForMethod(method, userType);} catch (Throwable var4) {throw new IllegalStateException("Invalid mapping on handler class [" + userType.getName() + "]: " + method, var4);}});// 记录日志if (this.logger.isTraceEnabled()) {this.logger.trace(this.formatMappings(userType, methods));} else if (this.mappingsLogger.isDebugEnabled()) {this.mappingsLogger.debug(this.formatMappings(userType, methods));}// 方法注册methods.forEach((method, mapping) -> {Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);this.registerHandlerMethod(handler, invocableMethod, mapping);});}}
这里对方法的处理分了两步,第一步,根据类型简单处理注解,主要是聚合了请求路径。聚合后的结果如下:

可以看到,这里的路径属性已经有值了。
然后就是注册方法了。
而真正注册的方法registerHandlerMethod 是在其子类中实现的。具体内容如下:

在其父级的实现中,注册的结果如下:

最后,简单处理RequestBody注解(如果使用了该注解,参数必填)。
2.2 DispatcherServlet 的初始化
框架中定义了自动配置类 DispatcherServletAutoConfiguration。其有个内部类 DispatcherServletConfiguration 对 DispatcherServlet 进行了配置。具体如下:
@Conditional({DefaultDispatcherServletCondition.class})
@ConditionalOnClass({ServletRegistration.class})
@EnableConfigurationProperties({WebMvcProperties.class})
protected static class DispatcherServletConfiguration {protected DispatcherServletConfiguration() {}@Bean(name = {"dispatcherServlet"})public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {DispatcherServlet dispatcherServlet = new DispatcherServlet();dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());return dispatcherServlet;}@Bean@ConditionalOnBean({MultipartResolver.class})@ConditionalOnMissingBean(name = {"multipartResolver"})public MultipartResolver multipartResolver(MultipartResolver resolver) {return resolver;}
}
在第一节设计原理的时候,提到过DispatcherServlet 也是一个servlet 。那么它的初始化,也就包含在servlet的生命周期中。
在servlet生命周期中,有 init 方法,进行初始化。FrameworkServlet 有一个父类HttpServletBean,其中定义了初始化方法。虽然不是原生的servlet 初始化方法,但是也是会间接调用到的(通过模版方法设计模式,由子类代为实现)
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware
在FrameworkServlet 触发初始化时,会执行到 initServletBean 方法。其中有两个比较关键初始化方法,具体如下:

篇幅原因,这里只拿出关键代码。initWebApplicationContext()内,有这样一段,在处理请求时,会执行到:
if (!this.refreshEventReceived) {synchronized(this.onRefreshMonitor) {this.onRefresh(wac);}
}
而 onRefresh 由子类DispatcherServlet 重写后,就成了这样:
protected void onRefresh(ApplicationContext context) {this.initStrategies(context);
}protected void initStrategies(ApplicationContext context) {// 初始化上传文件解析器initMultipartResolver(context);// 初始化本地解析器initLocaleResolver(context);// 主题处理器initThemeResolver(context);// 映射处理器initHandlerMappings(context);// 处理适配器initHandlerAdapters(context);// 异常处理器initHandlerExceptionResolvers(context);// 请求到视图名的翻译器initRequestToViewNameTranslator(context);// 视图解析器initViewResolvers(context);// 初始化FlashManagerinitFlashMapManager(context);
}
如果大家想看看这里执行的内容,以及初始化后的结果,可以自行打断点查看。这里因为东西较多,我就不截图了。
PS: 下一小节以 HandlerMappings 为例,进行说明
2.3 DispatcherServlet#initHandlerMappings(…) 初始化示例说明
private void initHandlerMappings(ApplicationContext context) {this.handlerMappings = null;if (this.detectAllHandlerMappings) {// 获取所有的handlerMappingMap<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);// 对handlerMapping进行排序if (!matchingBeans.isEmpty()) {this.handlerMappings = new ArrayList(matchingBeans.values());AnnotationAwareOrderComparator.sort(this.handlerMappings);}} else {try {// 获取名字是 handlerMapping 的handlerMappingHandlerMapping hm = (HandlerMapping)context.getBean("handlerMapping", HandlerMapping.class);this.handlerMappings = Collections.singletonList(hm);} catch (NoSuchBeanDefinitionException var4) {}}// handlerMappings为空,需要设置默认的handlerMappingif (this.handlerMappings == null) {this.handlerMappings = this.getDefaultStrategies(context, HandlerMapping.class);if (this.logger.isTraceEnabled()) {this.logger.trace("No HandlerMappings declared for servlet '" + this.getServletName() + "': using default strategies from DispatcherServlet.properties");}}Iterator var6 = this.handlerMappings.iterator();while(var6.hasNext()) {HandlerMapping mapping = (HandlerMapping)var6.next();if (mapping.usesPathPatterns()) {this.parseRequestPath = true;break;}}
}
默认情况下,如果配置了使用全部,会有以下handlerMapping:

另外补充一句,如果使用默认的handlerMapping,需要配置DispatcherServlet.properties。
三、工作原理
这一小节,以DispatcherServlet 为起点,分析SpringMvc的工作原理。
// todo 待完善–预计2024春节后补充,春节期间玩去了。
相关文章:
SpringMVC原理(设计原理+启动原理+工作原理)
文章目录 前言正文一、设计原理1.1 servlet生命周期简述1.2 设计原理小结 二、启动原理2.1 AbstractHandlerMethodMapping 初始化 --RequestMapping注解解析2.2 DispatcherServlet 的初始化2.3 DispatcherServlet#initHandlerMappings(...) 初始化示例说明 三、工作原理 前言 …...
Java+SpringBoot构建智能捐赠管理平台
✍✍计算机编程指导师 ⭐⭐个人介绍:自己非常喜欢研究技术问题!专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目:有源码或者技术上的问题欢迎在评论区一起讨论交流! ⚡⚡ Java实战 |…...
ubuntu远程桌面配置以及常见问题
ubuntu桌面系统配置 ubuntu远程桌面配置如下 第一步,安装xrdp sudo apt-get isntall xrdp安装完检查一下服务是否可以正常启动, sudo systemctl status xrdp如果看到active应该就正常启动了 第二步,开启Ubuntu桌面共享 好接下来我们测试一…...
数据结构:并查集讲解
并查集 1.并查集原理2.并查集实现3.并查集应用4.并查集的路径压缩 1.并查集原理 在一些应用问题中,需要将n个不同的元素划分成一些不相交的集合。开始时,每个元素自成一个单元素集合,然后按一定的规律将归于同一组元素的集合合并。在此过程中…...
Stable Diffusion主流UI详细介绍
Stable Diffusion目前主流的操作界面有WebUI、ComfyUI以及Fooocus 这里webui和fooocus在人机交互上的逻辑是一样的,fooocus界面更加简洁。 comfyui是在人机交互上是采用流程节点的交互逻辑,和上面略有区别。 界面分别如下: WebUI界面如下 we…...
webpack5 常用插件使用
webpack5常用插件使用 1. CleanWebpackPlugin2. HtmlWebpackPlugin3. DefinePlugin4.CopyWebpackPlugin 1. CleanWebpackPlugin 问题:每次打包完都需要手动删除掉dist文件目录,使用CleanWebpackPlugin就可自动清除dist目录。作用:自动清除di…...
利用Python和pandas库进行股票技术分析:移动平均线和MACD指标
利用Python和pandas库进行股票技术分析:移动平均线和MACD指标 介绍准备工作数据准备计算移动平均线计算MACD指标结果展示完整代码演示 介绍 在股票市场中,技术分析是一种常用的方法,它通过对股票价格和交易量等历史数据的分析,来…...
whisperspeech 英文TTS的实现
以下代码成功运行在 colab 中,需要修改运行时类型为 T4 GPU。 !pip install -Uqq WhisperSpeech def is_colab():try: import google.colab; return Trueexcept: return Falseimport torch # if not torch.cuda.is_available(): # if is_colab(): raise BaseEx…...
P1000 超级玛丽游戏(洛谷)
题目背景 本题是洛谷的试机题目,可以帮助了解洛谷的使用。 建议完成本题目后继续尝试 P1001、P1008。 另外强烈推荐新用户必读贴 题目描述 超级玛丽是一个非常经典的游戏。请你用字符画的形式输出超级玛丽中的一个场景。 ********************####....#.#..###…...
数据卷的常见命令,如何创建Nginx容器,修改nginx容器内的html目录下的index.html文件
数据卷 什么是数据卷 数据卷(volume)是一个虚拟目录,是容器内目录与宿主机**目录**之间映射的桥梁。 以Nginx为例,我们知道Nginx中有两个关键的目录: html:放置一些静态资源 conf:放置配置文…...
CFS三层靶机
参考博客: CFS三层内网靶场渗透记录【详细指南】 - FreeBuf网络安全行业门户 CFS三层靶机搭建及其内网渗透【附靶场环境】 | TeamsSix CFS三层网络环境靶场实战 - PANDA墨森 - 博客园 (cnblogs.com) CFS三层靶机实战--内网横向渗透 - 知乎 (zhihu.com) CFS靶机…...
C语言——oj刷题——获取月份天数
题目: 描述 KiKi想获得某年某月有多少天,请帮他编程实现。输入年份和月份,计算这一年这个月有多少天。 输入描述: 多组输入,一行有两个整数,分别表示年份和月份,用空格分隔。 输出描述&…...
Java面试题2024(Java面试八股文)
文章目录 基础Springspring Mybatis数据库Mysql redis并发编程网络通信消息队列MQ分布式分布式事务 设计模式 更新中 基础 Java基础 Java对象的创建 集合 HashMap详解 HashMap实现原理 ConcurrentHashMap原理详解 反射 JAVA反射详解 异常 Java 的异常体系 泛型 Java泛型详解 …...
Uniapp(uni-app)学习与快速上手教程
Uniapp(uni-app)学习与快速上手教程 1. 简介 Uniapp是一个跨平台的前端框架,允许您使用Vue.js语法开发小程序、H5、安卓和iOS应用。下面是快速上手的步骤。 2. 创建项目 2.1 可视化界面创建 1、打开 HBuilderX,这是一款专为uni…...
如何开始深度学习,从实践开始
将“如何开始深度学习”这个问题喂给ChatGPT和文心一言,会给出很有专业水准的答案,比如: 要开始深度学习,你可以遵循以下步骤: 学习Python编程语言的基础知识,因为它在深度学习框架中经常被使用。 熟悉线性…...
PostgreSQL的学习心得和知识总结(一百二十九)|深入理解PostgreSQL数据库GUC参数 update_process_title 的使用和原理
目录结构 注:提前言明 本文借鉴了以下博主、书籍或网站的内容,其列表如下: 1、参考书籍:《PostgreSQL数据库内核分析》 2、参考书籍:《数据库事务处理的艺术:事务管理与并发控制》 3、PostgreSQL数据库仓库链接,点击前往 4、日本著名PostgreSQL数据库专家 铃木启修 网站…...
【并发编程】ThreadPoolExecutor类
📝个人主页:五敷有你 🔥系列专栏:并发编程⛺️稳重求进,晒太阳 ThreadPoolExecutor 1) 线程池状态 ThreadPoolExecutor 使用 int 的高 3 位来表示线程池状态,低 29 位表示线程数量 状态名 高三位 …...
auto关键字详讲
目录 1.问题思考 2.auto关键字介绍 3. 早期auto的缺陷: 4.什么叫自动存储器? 5. c标准auto关键字 5.1auto的使用细节 5.2 auto什么时候不能推导变量的类型呢? 5.3基于范围的for循环 5.3.1范围for的用法 5.3.2 范围for的使用条件 6.…...
8 scala的伴生对象
1 单例对象 在编写 Java 程序时,我们经常会通过编写静态方法代码,去封装常用的 Utility 类。 在 Scala 中没有静态成员这一概念,所以,如果我们要定义静态属性或方法,就需要使用 Scala 的单例对象 object。Scala 的对…...
Redis相关介绍
概念 Redis:非关系型数据库(non-relational),Mysql是关系型数据库(RDBMS) Redis是当今非常流行的基于KV结构的作为Cache使用的NoSQL数据库 为什么使用NoSQL 关系型 数据库无法应对每秒上万次 的读写请求 表中的存储记录 数量有限 无法简单…...
多模态2025:技术路线“神仙打架”,视频生成冲上云霄
文|魏琳华 编|王一粟 一场大会,聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中,汇集了学界、创业公司和大厂等三方的热门选手,关于多模态的集中讨论达到了前所未有的热度。其中,…...
synchronized 学习
学习源: https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖,也要考虑性能问题(场景) 2.常见面试问题: sync出…...
进程地址空间(比特课总结)
一、进程地址空间 1. 环境变量 1 )⽤户级环境变量与系统级环境变量 全局属性:环境变量具有全局属性,会被⼦进程继承。例如当bash启动⼦进程时,环 境变量会⾃动传递给⼦进程。 本地变量限制:本地变量只在当前进程(ba…...
基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真
目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销,平衡网络负载,延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...
【JVM】- 内存结构
引言 JVM:Java Virtual Machine 定义:Java虚拟机,Java二进制字节码的运行环境好处: 一次编写,到处运行自动内存管理,垃圾回收的功能数组下标越界检查(会抛异常,不会覆盖到其他代码…...
智能在线客服平台:数字化时代企业连接用户的 AI 中枢
随着互联网技术的飞速发展,消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁,不仅优化了客户体验,还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用,并…...
使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装
以下是基于 vant-ui(适配 Vue2 版本 )实现截图中照片上传预览、删除功能,并封装成可复用组件的完整代码,包含样式和逻辑实现,可直接在 Vue2 项目中使用: 1. 封装的图片上传组件 ImageUploader.vue <te…...
vue3 定时器-定义全局方法 vue+ts
1.创建ts文件 路径:src/utils/timer.ts 完整代码: import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...
让AI看见世界:MCP协议与服务器的工作原理
让AI看见世界:MCP协议与服务器的工作原理 MCP(Model Context Protocol)是一种创新的通信协议,旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天,MCP正成为连接AI与现实世界的重要桥梁。…...
汇编常见指令
汇编常见指令 一、数据传送指令 指令功能示例说明MOV数据传送MOV EAX, 10将立即数 10 送入 EAXMOV [EBX], EAX将 EAX 值存入 EBX 指向的内存LEA加载有效地址LEA EAX, [EBX4]将 EBX4 的地址存入 EAX(不访问内存)XCHG交换数据XCHG EAX, EBX交换 EAX 和 EB…...
