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 关系型 数据库无法应对每秒上万次 的读写请求 表中的存储记录 数量有限 无法简单…...
oracle与MySQL数据库之间数据同步的技术要点
Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异,它们的数据同步要求既要保持数据的准确性和一致性,又要处理好性能问题。以下是一些主要的技术要点: 数据结构差异 数据类型差异ÿ…...
TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案
一、TRS收益互换的本质与业务逻辑 (一)概念解析 TRS(Total Return Swap)收益互换是一种金融衍生工具,指交易双方约定在未来一定期限内,基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...
VTK如何让部分单位不可见
最近遇到一个需求,需要让一个vtkDataSet中的部分单元不可见,查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行,是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示,主要是最后一个参数,透明度…...
DBAPI如何优雅的获取单条数据
API如何优雅的获取单条数据 案例一 对于查询类API,查询的是单条数据,比如根据主键ID查询用户信息,sql如下: select id, name, age from user where id #{id}API默认返回的数据格式是多条的,如下: {&qu…...
docker 部署发现spring.profiles.active 问题
报错: org.springframework.boot.context.config.InvalidConfigDataPropertyException: Property spring.profiles.active imported from location class path resource [application-test.yml] is invalid in a profile specific resource [origin: class path re…...
基于Java+MySQL实现(GUI)客户管理系统
客户资料管理系统的设计与实现 第一章 需求分析 1.1 需求总体介绍 本项目为了方便维护客户信息为了方便维护客户信息,对客户进行统一管理,可以把所有客户信息录入系统,进行维护和统计功能。可通过文件的方式保存相关录入数据,对…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
快刀集(1): 一刀斩断视频片头广告
一刀流:用一个简单脚本,秒杀视频片头广告,还你清爽观影体验。 1. 引子 作为一个爱生活、爱学习、爱收藏高清资源的老码农,平时写代码之余看看电影、补补片,是再正常不过的事。 电影嘛,要沉浸,…...
PHP 8.5 即将发布:管道操作符、强力调试
前不久,PHP宣布了即将在 2025 年 11 月 20 日 正式发布的 PHP 8.5!作为 PHP 语言的又一次重要迭代,PHP 8.5 承诺带来一系列旨在提升代码可读性、健壮性以及开发者效率的改进。而更令人兴奋的是,借助强大的本地开发环境 ServBay&am…...
redis和redission的区别
Redis 和 Redisson 是两个密切相关但又本质不同的技术,它们扮演着完全不同的角色: Redis: 内存数据库/数据结构存储 本质: 它是一个开源的、高性能的、基于内存的 键值存储数据库。它也可以将数据持久化到磁盘。 核心功能: 提供丰…...
