读书笔记-《Spring技术内幕》(三)MVC与Web环境
前面我们学习了 Spring 最核心的 IoC 与 AOP 模块(读书笔记-《Spring技术内幕》(一)IoC容器的实现、读书笔记-《Spring技术内幕》(二)AOP的实现),接下来继续学习 MVC,其同样也是经典。
我们依旧按照从浅到深的方式来学习,先从程序员的视角看看其简化了哪些工作,帮我们做了什么,再到具体的设计与实现。
01
MVC 概述
在之前 IoC 的笔记中,有这样一张图:

这里依然可以复用,从程序员的视角来看,Spring MVC 带来的最直观的好处是,我们不需要再去写繁琐冗余的 Servlet,而是改写 Controller。
拉长时间线来看,JavaWeb 的技术发展历程大致如下:

总结一下就是,初期使用的技术在业务的发展中逐渐暴露出局限性,于是有了分层思想,按照数据维度分层的 MVC 是最经典的分层模式,Spring MVC 就是 MVC 的实现之一。
除了 MVC,还有按照业务维度分层的 DDD,不过后者用得比较少,其比较适合复杂系统,并且需要所有人员(产品、研发、测试)都有较高水准的业务理解。
02
Spring MVC 概述
了解了 MVC 后,我们可以很快明白 Spring MVC 的重点工作。Model 层的 Bean 初始化,Controller 层的请求处理以及 View 层的视图呈现。具体步骤就是下面三步:
-
初始化:通过 Bean 定义,在 IoC 容器初始化时,建立起 Controller 与 HTTP 请求的映射关系。
-
处理请求:MVC 框架接收到 HTTP 请求,DispatcherServlet 根据 URL 查询到具体的 Controller,Controller 完成请求并生成 ModelAndView 对象。
-
呈现视图:视图对象通过 render 方法完成视图的呈现。
接下来我们就可以展开步骤,来详细看看其实现了。
03
Spring MVC 设计与实现
1.初始化
我们以 Tomcat 的 web.xml 文件为例
<servlet><servlet-name>sample</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><load-on-startup>2</load-on-startup>
</servlet><servlet-mapping><servlet-name>sample</servlet-name><url-pattern>/*</url-pattern>
</servlet-mapping><context-param><param-name>contextConfigLocation</param-name><param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
可以看到,其定义了一个叫 sample 的 servlet,全限定类名正是 DispatcherServlet,且其将处理所有请求。而后,这里还有一个 Bean 定义的配置文件是 WEB-IN 目录下的 applicationContext.xml。最后,有个监听器 ContextLoaderListener,其将负责完成 IoC 容器在 Web 环境中的启动。
从代码上来看,Web 容器中启动 Spring 应用程序的过程如下:
-
ContextLoaderListener.contextInitialized() 初始化根上下文,其中调用父类 ContextLoader 的方法;
-
ContextLoader.initWebApplicationContext() 初始化 Web 应用上下文,其中有挺多异常校验和日志打印;
-
ContextLoader.loadParentContext() 加载双亲上下文;
-
XmlWebApplicationContext.createWebApplicationContext() 创建 Web 应用上下文;
-
XmlWebApplication.refresh() 刷新。这里前面讲 IoC 的时候也讲到了,refresh 方法可以视作整个容器的初始化方法。
在根上下文初始化好后,就可以关注 DispatcherServlet 了。从前面的概述也看得出来,其是 Spring MVC 的核心。DispatcherServlet 的初始化和处理过程大致如下:

上面时序图中,从右到左依次是继承关系,我们来详细描述下上半边:
-
HttpServletBean.init() 基类的初始化,首先会获取 Servlet 的初始化参数,对 Bean 属性进行配置,也就是上面我们举例的配置文件里的 Bean,然后调用子类的方法;
-
FrameworkServlet.initServletBean() 初始化 Servlet Bean;
-
FrameworkServlet.initWebApplicationContext() 从 ServletContext 中获取根上下文,并设置为当前 MVC 上下文的双亲上下文,再把当前上下文设置到 ServletContext 中去(根上下文也就是上面提到的 ContextLoader 设置到 ServletContext 中去的);
-
上面的 FrameworkServlet.initWebApplicationContext() 中会调用自己的 createWebApplicationContext() ,里面就包含了 refresh();
-
DispatcherServlet.onRefresh();
-
DispatcherServlet.initStrategies() 启动框架的初始化,代码很简洁,依次初始化 multipartResolver、localeResolver、themeResolver、handlerMappings、handlerAdapters、handlerExceptionResolvers、requestToViewNameTranslator、viewResolvers;
-
以 initHandlerMappings() 为例,将设置所有的 handlerMapping Bean,这些 Bean 可能在当前 DispatcherServlet 的 IoC 容器中,也可能在双亲上下文中。如果都没有找到,则去 DispatcherServlet.properties 中找默认值。
2.处理请求
前面初始化已完成,接下来就关注上面那张图的下半边,也就是 DispatcherServlet 的 doDispatch() 了。
注意 HandlerMapping 有很多实现,比如通过 Bean 名称的、通过类名称的,我们以SimpleUrlHandlerMapping 为例,先看下关键的数据结构:
// 1.基类定义了方法 getHandler,返回一个 Chain
public interface HandlerMapping {HandlerExecutionChain getHandler(HttpServletRequest req) throws Exception;……
}// 2.Chain 里持有了 handler,也就是我们编写的 Controller
// 还有个拦截器链,对 handler 进行功能增强
public class HandlerExecutionChain {private final Object handler;private HandlerInterceptor[] interceptors;private List<HandlerInterceptor> interceptorList;……
}// 3.关键的成员变量,key 是 url,value 是对应的处理 handler
// 它的赋值是在 SimpleUrlHandlerMapping.initApplicationContext() -> AbstractUrlHandlerMapping.registerHandler() 里
// 它的使用是在 AbstractHandlerMapping.getHandler() -> AbstractUrlHandlerMapping.getHandlerInternal() 里
public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping implements MatchableHandlerMapping {private final Map<String, Object> handlerMap = new LinkedHashMap<String, Object>();……
}
再看看 doDispatch() 的详细时序图:

可以看到,doDispatch() 完成了模型、控制器、视图的耦合处理,从根据请求得到对应的 handler,到调用 handler 的拦截器,到调用适配器的 handle(),最后到 ModelAndView 的呈现。
3.呈现视图
对于最后的视图呈现,除了当时常见的 JSP 视图,还有 Excel 视图、PDF 视图等等。不过现在都不涉及了,主流的应用都通过前后端分离的方式,将数据的展示交给前端开发处理。
关于视图呈现,我们以 JSP 视图为例,过程如下:
-
DispatcherServlet.render() 里面有两种情况,一种是在 ModelAndView 中设置了 View 的名称,需要调用 resolveViewName 方法获取 View,还有一种情况是 ModelAndView 里已经有 View 了,则直接使用(注意这里说的 render 方法是 DispatcherServlet 的,上面时序图里说的 render 方法是 View 的);
-
DispatcherServlet.resolveViewName() 调用 ViewResolver.resolveViewName(),后者会到上下文中通过名称把 View 的 Bean 对象获取到;
-
AbstractView.render() 这里是基类的方法,把所有 Model 进行整合,放在一个 HashMap 里,然后继续往下调用;
-
InternalResourceView.renderMergedOutputModel() 这里接着会往 AbstractView 调用。不过最终就是 InternalResourceView 完成了数据到页面的输出,以及资源的重定向处理;
-
AbstractView.exposeModelAsRequestAttributes() 把 ModelAndView 中的模型数据和请求数据都放到 HttpServletRequest 的属性中去,这样程序员就可以愉快的使用了。

原文链接:读书笔记-《Spring技术内幕》(三)MVC与Web环境
原创不易,点个关注不迷路哟,谢谢!
文章推荐:
- 如何提高核心竞争力
- 读书笔记-《当下的力量》
- 读书笔记-《写给大家看的设计书》
- 赛博朋克2077玩后感
- 程序员工作中常见问题,你遇到过几个?
- 如何设计离线跑批系统
- 读书笔记-《人人都是产品经理》
相关文章:
读书笔记-《Spring技术内幕》(三)MVC与Web环境
前面我们学习了 Spring 最核心的 IoC 与 AOP 模块(读书笔记-《Spring技术内幕》(一)IoC容器的实现、读书笔记-《Spring技术内幕》(二)AOP的实现),接下来继续学习 MVC,其同样也是经典…...
k8s及常用对象简介
文章目录 一、k8s是什么应用程序早期部署形式容器的引入k8s的作用 二、k8s中的常用对象1、Node获取node信息 2、Namespacenamespace的使用 3、Pod生命周期pod的使用 4、DaemonSetDaemonSet的使用 5、Deployment创建deploy 6、ReplicaSet7、StatefulSet创建StatefulSet 8、更新操…...
HTTPS数字证书验证论述
1 概述 网络请求方式通常分为两种,分别是HTTP请求和HTTPS请求,其中HTTP的传输属于明文传输,在传输的过程中容易被人截取并且偷窥其中的内容,而HTTPS是一种在HTTP的基础上加了SSL/TLS层(安全套接层)的安全的…...
【高考志愿】地质资源与地质工程
目录 一、专业概述 1.1 专业定义 1.2 主要课程 1.3 专业培养目标 二、就业前景和考研方向 三、工作特点和挑战 四、如何培养核心竞争力 五、 地质资源与地质工程专业排名 六、结语 关于高考志愿选择地质资源与地质工程专业,以下是一些详细的介绍和参考信息…...
全网最佳硕士研究生复试简历模板
硕士研究生复试简历模板 ✨ 简介 提供了一个适用于国内硕士研究生复试的个人简历模板。该模板通过统一的“样式”形成规范的Word格式,是目前研究生复试的最佳简历模板之一。模板使用“华文中宋”字体,如您的电脑中未安装此字体,请提前安装。…...
Rocky Linux 9 系统OpenSSH CVE-2024-6387 漏洞修复
Rocky Linux 9系统 OpenSSH CVE-2024-6387 漏洞修复 1、漏洞修复2、修复思路3、修复方案3.1、方案一3.2、方案二 4、总结5、参考 1、漏洞修复 CVE-2024-6387:regreSSHion:OpenSSH 服务器中的远程代码执行(RCE),至少在…...
Sping源码(九)—— Bean的初始化(非懒加载)—mergeBeanDefinitionPostProcessor
序言 前几篇文章详细介绍了Spring中实例化Bean的各种方式,其中包括采用FactoryBean的方式创建对象、使用反射创建对象、自定义BeanFactoryPostProcessor以及构造器方式创建对象。 创建对象 这里再来简单回顾一下对象的创建,不知道大家有没有这样一个疑…...
labview技巧——AMC框架安装
AMC工具包的核心概念是队列,队列是一种先进先出(FIFO,First In First Out)的数据结构,适用于处理并发和异步任务。在LabVIEW中,队列可以用于在不同VI之间传递数据,确保消息的有序处理࿰…...
解锁分布式云多集群统一监控的云上最佳实践
作者:在峰 引言 在当今数字化转型加速的时代,随着混合云、多云多集群环境等技术被众多企业广泛应用,分布式云架构已成为众多企业和组织推动业务创新、实现弹性扩展的首选,分布式云容器平台 ACK One(Distributed Clou…...
学会拥抱Python六剑客,提高编程效率
在Python语言中,有六个强大的工具,它们被称为"Python六剑客"。而Python六剑客指的是Python中常用的六种功能强大且灵活的工具,它们分别是“切片(Slicing),推导列表(List Comprehensio…...
mysql 根据当前时间筛选某个时间范围内的数据
1.根据天数筛选 SELECT * FROM coupons WHERE NOW() BETWEEN start_time AND end_time; 在这个查询中,NOW()函数返回当前的日期和时间。BETWEEN操作符用于检查NOW()返回的当前时间是否在start_time和end_time之间(包括这两个时间)。 注意&a…...
Linux 常用指令详解
Linux 是一个强大而灵活的操作系统,掌握常用的 Linux 指令是使用和管理 Linux 系统的基础。本文将介绍一些常用的 Linux 指令,并附上 Vim 和 g 的常用指令说明,帮助你更好地进行开发和操作。 1. 基本文件操作指令 1.1 显示目录内容 ls常用…...
【简单讲解下npm常用命令】
🌈个人主页: 程序员不想敲代码啊 🏆CSDN优质创作者,CSDN实力新星,CSDN博客专家 👍点赞⭐评论⭐收藏 🤝希望本文对您有所裨益,如有不足之处,欢迎在评论区提出指正,让我们共…...
Header Location重定向机制解析与应用
Header Location重定向机制解析与应用 大家好,我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编,也是冬天不穿秋裤,天冷也要风度的程序猿!今天我们将深入探讨HTTP中的Header Location重定向机制,以及在…...
硅纪元AI应用推荐 | 国产创作引擎即梦AI助力创作者探索创作新境界
“硅纪元AI应用推荐”栏目,为您精选最新、最实用的人工智能应用,无论您是AI发烧友还是新手,都能在这里找到提升生活和工作的利器。与我们一起探索AI的无限可能,开启智慧新时代! 在人工智能快速发展的今天,各…...
使用TableGeneration生成已标注的表格数据用于表格识别
利用 TableGeneration 生成多样化表格数据 TableGeneration 简介环境准备chrome浏览器(Linux下推荐)火狐浏览器(Mac下推荐) 生成表格生成表格 参数说明结论 在数据生成和处理领域,表格数据的生成是一个常见需求,尤其是在机器学习和数据分析领域。今天&am…...
赛目科技三度递表:净利率及资产回报率不断下滑,经营成本越来越高
《港湾商业观察》施子夫 5月29日,北京赛目科技股份有限公司(以下简称,赛目科技)第三次递表港交所,公司拟主板上市,独家保荐机构为光银国际。 公开信息显示,赛目科技此前曾于2022年12月&#x…...
【QT】概述|对象树模型|两种控件模式|信号和槽|lambda
目录 什么是QT 特点 QT程序 main函数 QT按钮 纯代码模式 图形化模式 对象树模型 信号和槽 连接与断开 自动连接 断开连接 信号的发射 lambda表达式 基本语法 捕获列表 Lambda表达式用于信号与槽的连接 例如 什么是QT Qt是一个跨平台的C图形用户界面应用…...
Java中的安全编码实践与防御技巧
Java中的安全编码实践与防御技巧 大家好,我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编,也是冬天不穿秋裤,天冷也要风度的程序猿!今天我们将探讨Java中的安全编码实践与防御技巧,这对于开发人员来说…...
linux 常用的命令、文件路径、其他工具或软件包
命令 sudo apt dist-upgrade 解决显示 暂不升级、未被升级dd if/dev/zero of./rootfs.img bs1G count6 制作一个 6G 的空白镜像。bs 是块字节数,count 是 bs 的个数。dd if./rootfs.img of/dev/sdc2 bs512 烧录 rootfs.img 镜像到 /dev/sdc2。bs 是 512 个字节&…...
使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...
Lombok 的 @Data 注解失效,未生成 getter/setter 方法引发的HTTP 406 错误
HTTP 状态码 406 (Not Acceptable) 和 500 (Internal Server Error) 是两类完全不同的错误,它们的含义、原因和解决方法都有显著区别。以下是详细对比: 1. HTTP 406 (Not Acceptable) 含义: 客户端请求的内容类型与服务器支持的内容类型不匹…...
FFmpeg 低延迟同屏方案
引言 在实时互动需求激增的当下,无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作,还是游戏直播的画面实时传输,低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架,凭借其灵活的编解码、数据…...
【Linux】C语言执行shell指令
在C语言中执行Shell指令 在C语言中,有几种方法可以执行Shell指令: 1. 使用system()函数 这是最简单的方法,包含在stdlib.h头文件中: #include <stdlib.h>int main() {system("ls -l"); // 执行ls -l命令retu…...
Python爬虫实战:研究feedparser库相关技术
1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的信息资源。RSS(Really Simple Syndication)作为一种标准化的信息聚合技术,被广泛用于网站内容的发布和订阅。通过 RSS,用户可以方便地获取网站更新的内容,而无需频繁访问各个网站。 然而,互联网…...
1.3 VSCode安装与环境配置
进入网址Visual Studio Code - Code Editing. Redefined下载.deb文件,然后打开终端,进入下载文件夹,键入命令 sudo dpkg -i code_1.100.3-1748872405_amd64.deb 在终端键入命令code即启动vscode 需要安装插件列表 1.Chinese简化 2.ros …...
(二)原型模式
原型的功能是将一个已经存在的对象作为源目标,其余对象都是通过这个源目标创建。发挥复制的作用就是原型模式的核心思想。 一、源型模式的定义 原型模式是指第二次创建对象可以通过复制已经存在的原型对象来实现,忽略对象创建过程中的其它细节。 📌 核心特点: 避免重复初…...
leetcode73-矩阵置零
leetcode 73 思路 记录 0 元素的位置:遍历整个矩阵,找出所有值为 0 的元素,并将它们的坐标记录在数组zeroPosition中置零操作:遍历记录的所有 0 元素位置,将每个位置对应的行和列的所有元素置为 0 具体步骤 初始化…...
深入浅出JavaScript中的ArrayBuffer:二进制数据的“瑞士军刀”
深入浅出JavaScript中的ArrayBuffer:二进制数据的“瑞士军刀” 在JavaScript中,我们经常需要处理文本、数组、对象等数据类型。但当我们需要处理文件上传、图像处理、网络通信等场景时,单纯依赖字符串或数组就显得力不从心了。这时ÿ…...
SFTrack:面向警务无人机的自适应多目标跟踪算法——突破小尺度高速运动目标的追踪瓶颈
【导读】 本文针对无人机(UAV)视频中目标尺寸小、运动快导致的多目标跟踪难题,提出一种更简单高效的方法。核心创新在于从低置信度检测启动跟踪(贴合无人机场景特性),并改进传统外观匹配算法以关联此类检测…...
