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 关系型 数据库无法应对每秒上万次 的读写请求 表中的存储记录 数量有限 无法简单…...

Prompt Tuning、P-Tuning、Prefix Tuning的区别
一、Prompt Tuning、P-Tuning、Prefix Tuning的区别 1. Prompt Tuning(提示调优) 核心思想:固定预训练模型参数,仅学习额外的连续提示向量(通常是嵌入层的一部分)。实现方式:在输入文本前添加可训练的连续向量(软提示),模型只更新这些提示参数。优势:参数量少(仅提…...

【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力
引言: 在人工智能快速发展的浪潮中,快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型(LLM)。该模型代表着该领域的重大突破,通过独特方式融合思考与非思考…...

微信小程序 - 手机震动
一、界面 <button type"primary" bindtap"shortVibrate">短震动</button> <button type"primary" bindtap"longVibrate">长震动</button> 二、js逻辑代码 注:文档 https://developers.weixin.qq…...

c#开发AI模型对话
AI模型 前面已经介绍了一般AI模型本地部署,直接调用现成的模型数据。这里主要讲述讲接口集成到我们自己的程序中使用方式。 微软提供了ML.NET来开发和使用AI模型,但是目前国内可能使用不多,至少实践例子很少看见。开发训练模型就不介绍了&am…...

Mac下Android Studio扫描根目录卡死问题记录
环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中,提示一个依赖外部头文件的cpp源文件需要同步,点…...

2025季度云服务器排行榜
在全球云服务器市场,各厂商的排名和地位并非一成不变,而是由其独特的优势、战略布局和市场适应性共同决定的。以下是根据2025年市场趋势,对主要云服务器厂商在排行榜中占据重要位置的原因和优势进行深度分析: 一、全球“三巨头”…...

搭建DNS域名解析服务器(正向解析资源文件)
正向解析资源文件 1)准备工作 服务端及客户端都关闭安全软件 [rootlocalhost ~]# systemctl stop firewalld [rootlocalhost ~]# setenforce 0 2)服务端安装软件:bind 1.配置yum源 [rootlocalhost ~]# cat /etc/yum.repos.d/base.repo [Base…...
快刀集(1): 一刀斩断视频片头广告
一刀流:用一个简单脚本,秒杀视频片头广告,还你清爽观影体验。 1. 引子 作为一个爱生活、爱学习、爱收藏高清资源的老码农,平时写代码之余看看电影、补补片,是再正常不过的事。 电影嘛,要沉浸,…...

Proxmox Mail Gateway安装指南:从零开始配置高效邮件过滤系统
💝💝💝欢迎莅临我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:「storms…...

【Linux手册】探秘系统世界:从用户交互到硬件底层的全链路工作之旅
目录 前言 操作系统与驱动程序 是什么,为什么 怎么做 system call 用户操作接口 总结 前言 日常生活中,我们在使用电子设备时,我们所输入执行的每一条指令最终大多都会作用到硬件上,比如下载一款软件最终会下载到硬盘上&am…...