learn.lianglianglee.com/文章/Spring MVC 原理.md.html
2022-08-14 03:40:33 +08:00

1925 lines
102 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>Spring MVC 原理.md.html</title>
<!-- Spectre.css framework -->
<link rel="stylesheet" href="/static/index.css">
<!-- theme css & js -->
<meta name="generator" content="Hexo 4.2.0">
</head>
<body>
<div class="book-container">
<div class="book-sidebar">
<div class="book-brand">
<a href="/">
<img src="/static/favicon.png">
<span>技术文章摘抄</span>
</a>
</div>
<div class="book-menu uncollapsible">
<ul class="uncollapsible">
<li><a href="/" class="current-tab">首页</a></li>
</ul>
<ul class="uncollapsible">
<li><a href="../">上一级</a></li>
</ul>
<ul class="uncollapsible">
<li>
<a href="/文章/AQS 万字图文全面解析.md.html">AQS 万字图文全面解析</a>
</li>
<li>
<a href="/文章/Docker 镜像构建原理及源码分析.md.html">Docker 镜像构建原理及源码分析</a>
</li>
<li>
<a href="/文章/ElasticSearch 小白从入门到精通.md.html">ElasticSearch 小白从入门到精通</a>
</li>
<li>
<a href="/文章/JVM CPU Profiler技术原理及源码深度解析.md.html">JVM CPU Profiler技术原理及源码深度解析</a>
</li>
<li>
<a href="/文章/JVM 垃圾收集器.md.html">JVM 垃圾收集器</a>
</li>
<li>
<a href="/文章/JVM 面试的 30 个知识点.md.html">JVM 面试的 30 个知识点</a>
</li>
<li>
<a href="/文章/Java IO 体系、线程模型大总结.md.html">Java IO 体系、线程模型大总结</a>
</li>
<li>
<a href="/文章/Java NIO浅析.md.html">Java NIO浅析</a>
</li>
<li>
<a href="/文章/Java 面试题集锦(网络篇).md.html">Java 面试题集锦(网络篇)</a>
</li>
<li>
<a href="/文章/Java-直接内存 DirectMemory 详解.md.html">Java-直接内存 DirectMemory 详解</a>
</li>
<li>
<a href="/文章/Java中9种常见的CMS GC问题分析与解决.md.html">Java中9种常见的CMS GC问题分析与解决</a>
</li>
<li>
<a href="/文章/Java中9种常见的CMS GC问题分析与解决.md.html">Java中9种常见的CMS GC问题分析与解决</a>
</li>
<li>
<a href="/文章/Java中的SPI.md.html">Java中的SPI</a>
</li>
<li>
<a href="/文章/Java中的ThreadLocal.md.html">Java中的ThreadLocal</a>
</li>
<li>
<a href="/文章/Java线程池实现原理及其在美团业务中的实践.md.html">Java线程池实现原理及其在美团业务中的实践</a>
</li>
<li>
<a href="/文章/Java魔法类Unsafe应用解析.md.html">Java魔法类Unsafe应用解析</a>
</li>
<li>
<a href="/文章/Kafka 源码阅读笔记.md.html">Kafka 源码阅读笔记</a>
</li>
<li>
<a href="/文章/Kafka、ActiveMQ、RabbitMQ、RocketMQ 区别以及高可用原理.md.html">Kafka、ActiveMQ、RabbitMQ、RocketMQ 区别以及高可用原理</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB Buffer Pool.md.html">MySQL · 引擎特性 · InnoDB Buffer Pool</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB IO子系统.md.html">MySQL · 引擎特性 · InnoDB IO子系统</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB 事务系统.md.html">MySQL · 引擎特性 · InnoDB 事务系统</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB 同步机制.md.html">MySQL · 引擎特性 · InnoDB 同步机制</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB 数据页解析.md.html">MySQL · 引擎特性 · InnoDB 数据页解析</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB崩溃恢复.md.html">MySQL · 引擎特性 · InnoDB崩溃恢复</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · 临时表那些事儿.md.html">MySQL · 引擎特性 · 临时表那些事儿</a>
</li>
<li>
<a href="/文章/MySQL 主从复制 半同步复制.md.html">MySQL 主从复制 半同步复制</a>
</li>
<li>
<a href="/文章/MySQL 主从复制 基于GTID复制.md.html">MySQL 主从复制 基于GTID复制</a>
</li>
<li>
<a href="/文章/MySQL 主从复制.md.html">MySQL 主从复制</a>
</li>
<li>
<a href="/文章/MySQL 事务日志(redo log和undo log).md.html">MySQL 事务日志(redo log和undo log)</a>
</li>
<li>
<a href="/文章/MySQL 亿级别数据迁移实战代码分享.md.html">MySQL 亿级别数据迁移实战代码分享</a>
</li>
<li>
<a href="/文章/MySQL 从一条数据说起-InnoDB行存储数据结构.md.html">MySQL 从一条数据说起-InnoDB行存储数据结构</a>
</li>
<li>
<a href="/文章/MySQL 地基基础:事务和锁的面纱.md.html">MySQL 地基基础:事务和锁的面纱</a>
</li>
<li>
<a href="/文章/MySQL 地基基础:数据字典.md.html">MySQL 地基基础:数据字典</a>
</li>
<li>
<a href="/文章/MySQL 地基基础:数据库字符集.md.html">MySQL 地基基础:数据库字符集</a>
</li>
<li>
<a href="/文章/MySQL 性能优化:碎片整理.md.html">MySQL 性能优化:碎片整理</a>
</li>
<li>
<a href="/文章/MySQL 故障诊断:一个 ALTER TALBE 执行了很久,你慌不慌?.md.html">MySQL 故障诊断:一个 ALTER TALBE 执行了很久,你慌不慌?</a>
</li>
<li>
<a href="/文章/MySQL 故障诊断:如何在日志中轻松定位大事务.md.html">MySQL 故障诊断:如何在日志中轻松定位大事务</a>
</li>
<li>
<a href="/文章/MySQL 故障诊断:教你快速定位加锁的 SQL.md.html">MySQL 故障诊断:教你快速定位加锁的 SQL</a>
</li>
<li>
<a href="/文章/MySQL 日志详解.md.html">MySQL 日志详解</a>
</li>
<li>
<a href="/文章/MySQL 的半同步是什么?.md.html">MySQL 的半同步是什么?</a>
</li>
<li>
<a href="/文章/MySQL中的事务和MVCC.md.html">MySQL中的事务和MVCC</a>
</li>
<li>
<a href="/文章/MySQL事务_事务隔离级别详解.md.html">MySQL事务_事务隔离级别详解</a>
</li>
<li>
<a href="/文章/MySQL优化优化 select count().md.html">MySQL优化优化 select count()</a>
</li>
<li>
<a href="/文章/MySQL共享锁、排他锁、悲观锁、乐观锁.md.html">MySQL共享锁、排他锁、悲观锁、乐观锁</a>
</li>
<li>
<a href="/文章/MySQL的MVCC多版本并发控制.md.html">MySQL的MVCC多版本并发控制</a>
</li>
<li>
<a href="/文章/QingStor 对象存储架构设计及最佳实践.md.html">QingStor 对象存储架构设计及最佳实践</a>
</li>
<li>
<a href="/文章/RocketMQ 面试题集锦.md.html">RocketMQ 面试题集锦</a>
</li>
<li>
<a href="/文章/SnowFlake 雪花算法生成分布式 ID.md.html">SnowFlake 雪花算法生成分布式 ID</a>
</li>
<li>
<a href="/文章/Spring Boot 2.x 结合 k8s 实现分布式微服务架构.md.html">Spring Boot 2.x 结合 k8s 实现分布式微服务架构</a>
</li>
<li>
<a href="/文章/Spring Boot 教程:如何开发一个 starter.md.html">Spring Boot 教程:如何开发一个 starter</a>
</li>
<li>
<a class="current-tab" href="/文章/Spring MVC 原理.md.html">Spring MVC 原理</a>
</li>
<li>
<a href="/文章/Spring MyBatis和Spring整合的奥秘.md.html">Spring MyBatis和Spring整合的奥秘</a>
</li>
<li>
<a href="/文章/Spring 帮助你更好的理解Spring循环依赖.md.html">Spring 帮助你更好的理解Spring循环依赖</a>
</li>
<li>
<a href="/文章/Spring 循环依赖及解决方式.md.html">Spring 循环依赖及解决方式</a>
</li>
<li>
<a href="/文章/Spring中眼花缭乱的BeanDefinition.md.html">Spring中眼花缭乱的BeanDefinition</a>
</li>
<li>
<a href="/文章/Vert.x 基础入门.md.html">Vert.x 基础入门</a>
</li>
<li>
<a href="/文章/eBay 的 Elasticsearch 性能调优实践.md.html">eBay 的 Elasticsearch 性能调优实践</a>
</li>
<li>
<a href="/文章/不可不说的Java“锁”事.md.html">不可不说的Java“锁”事</a>
</li>
<li>
<a href="/文章/互联网并发限流实战.md.html">互联网并发限流实战</a>
</li>
<li>
<a href="/文章/从ReentrantLock的实现看AQS的原理及应用.md.html">从ReentrantLock的实现看AQS的原理及应用</a>
</li>
<li>
<a href="/文章/从SpringCloud开始聊微服务架构.md.html">从SpringCloud开始聊微服务架构</a>
</li>
<li>
<a href="/文章/全面了解 JDK 线程池实现原理.md.html">全面了解 JDK 线程池实现原理</a>
</li>
<li>
<a href="/文章/分布式一致性理论与算法.md.html">分布式一致性理论与算法</a>
</li>
<li>
<a href="/文章/分布式一致性算法 Raft.md.html">分布式一致性算法 Raft</a>
</li>
<li>
<a href="/文章/分布式唯一 ID 解析.md.html">分布式唯一 ID 解析</a>
</li>
<li>
<a href="/文章/分布式链路追踪:集群管理设计.md.html">分布式链路追踪:集群管理设计</a>
</li>
<li>
<a href="/文章/动态代理种类及原理,你知道多少?.md.html">动态代理种类及原理,你知道多少?</a>
</li>
<li>
<a href="/文章/响应式架构与 RxJava 在有赞零售的实践.md.html">响应式架构与 RxJava 在有赞零售的实践</a>
</li>
<li>
<a href="/文章/大数据算法——布隆过滤器.md.html">大数据算法——布隆过滤器</a>
</li>
<li>
<a href="/文章/如何优雅地记录操作日志?.md.html">如何优雅地记录操作日志?</a>
</li>
<li>
<a href="/文章/如何设计一个亿级消息量的 IM 系统.md.html">如何设计一个亿级消息量的 IM 系统</a>
</li>
<li>
<a href="/文章/异步网络模型.md.html">异步网络模型</a>
</li>
<li>
<a href="/文章/当我们在讨论CQRS时我们在讨论些神马.md.html">当我们在讨论CQRS时我们在讨论些神马</a>
</li>
<li>
<a href="/文章/彻底理解 MySQL 的索引机制.md.html">彻底理解 MySQL 的索引机制</a>
</li>
<li>
<a href="/文章/最全的 116 道 Redis 面试题解答.md.html">最全的 116 道 Redis 面试题解答</a>
</li>
<li>
<a href="/文章/有赞权限系统(SAM).md.html">有赞权限系统(SAM)</a>
</li>
<li>
<a href="/文章/有赞零售中台建设方法的探索与实践.md.html">有赞零售中台建设方法的探索与实践</a>
</li>
<li>
<a href="/文章/服务注册与发现原理剖析Eureka、Zookeeper、Nacos.md.html">服务注册与发现原理剖析Eureka、Zookeeper、Nacos</a>
</li>
<li>
<a href="/文章/深入浅出Cache.md.html">深入浅出Cache</a>
</li>
<li>
<a href="/文章/深入理解 MySQL 底层实现.md.html">深入理解 MySQL 底层实现</a>
</li>
<li>
<a href="/文章/漫画讲解 git rebase VS git merge.md.html">漫画讲解 git rebase VS git merge</a>
</li>
<li>
<a href="/文章/生成浏览器唯一稳定 ID 的探索.md.html">生成浏览器唯一稳定 ID 的探索</a>
</li>
<li>
<a href="/文章/缓存 如何保证缓存与数据库的双写一致性?.md.html">缓存 如何保证缓存与数据库的双写一致性?</a>
</li>
<li>
<a href="/文章/网易严选怎么做全链路监控的?.md.html">网易严选怎么做全链路监控的?</a>
</li>
<li>
<a href="/文章/美团万亿级 KV 存储架构与实践.md.html">美团万亿级 KV 存储架构与实践</a>
</li>
<li>
<a href="/文章/美团点评Kubernetes集群管理实践.md.html">美团点评Kubernetes集群管理实践</a>
</li>
<li>
<a href="/文章/美团百亿规模API网关服务Shepherd的设计与实现.md.html">美团百亿规模API网关服务Shepherd的设计与实现</a>
</li>
<li>
<a href="/文章/解读《阿里巴巴 Java 开发手册》背后的思考.md.html">解读《阿里巴巴 Java 开发手册》背后的思考</a>
</li>
<li>
<a href="/文章/认识 MySQL 和 Redis 的数据一致性问题.md.html">认识 MySQL 和 Redis 的数据一致性问题</a>
</li>
<li>
<a href="/文章/进阶Dockerfile 高阶使用指南及镜像优化.md.html">进阶Dockerfile 高阶使用指南及镜像优化</a>
</li>
<li>
<a href="/文章/铁总在用的高性能分布式缓存计算框架 Geode.md.html">铁总在用的高性能分布式缓存计算框架 Geode</a>
</li>
<li>
<a href="/文章/阿里云PolarDB及其共享存储PolarFS技术实现分析.md.html">阿里云PolarDB及其共享存储PolarFS技术实现分析</a>
</li>
<li>
<a href="/文章/阿里云PolarDB及其共享存储PolarFS技术实现分析.md.html">阿里云PolarDB及其共享存储PolarFS技术实现分析</a>
</li>
<li>
<a href="/文章/面试最常被问的 Java 后端题.md.html">面试最常被问的 Java 后端题</a>
</li>
<li>
<a href="/文章/领域驱动设计在互联网业务开发中的实践.md.html">领域驱动设计在互联网业务开发中的实践</a>
</li>
<li>
<a href="/文章/领域驱动设计的菱形对称架构.md.html">领域驱动设计的菱形对称架构</a>
</li>
<li>
<a href="/文章/高效构建 Docker 镜像的最佳实践.md.html">高效构建 Docker 镜像的最佳实践</a>
</li>
</ul>
</div>
</div>
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
<div class="sidebar-toggle-inner"></div>
</div>
<script>
function add_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.add('show')
}
function remove_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.remove('show')
}
function sidebar_toggle() {
let sidebar_toggle = document.querySelector('.sidebar-toggle')
let sidebar = document.querySelector('.book-sidebar')
let content = document.querySelector('.off-canvas-content')
if (sidebar_toggle.classList.contains('extend')) { // show
sidebar_toggle.classList.remove('extend')
sidebar.classList.remove('hide')
content.classList.remove('extend')
} else { // hide
sidebar_toggle.classList.add('extend')
sidebar.classList.add('hide')
content.classList.add('extend')
}
}
function open_sidebar() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.add('show')
overlay.classList.add('show')
}
function hide_canvas() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.remove('show')
overlay.classList.remove('show')
}
</script>
<div class="off-canvas-content">
<div class="columns">
<div class="column col-12 col-lg-12">
<div class="book-navbar">
<!-- For Responsive Layout -->
<header class="navbar">
<section class="navbar-section">
<a onclick="open_sidebar()">
<i class="icon icon-menu"></i>
</a>
</section>
</header>
</div>
<div class="book-content" style="max-width: 960px; margin: 0 auto;
overflow-x: auto;
overflow-y: hidden;">
<div class="book-post">
<p id="tip" align="center"></p>
<div><h1>Spring MVC 原理</h1>
<ul>
<li>
<h3>前言</h3>
<p>随着 Spring Boot 逐步全面覆盖到我们的项目之中,我们已经基本忘却当年经典的 Servlet + Spring MVC 的组合,那让人熟悉的 web.xml 配置。而本文,我们想先抛开 Spring Boot 到一旁,回到从前,一起来看看 Servlet 是怎么和 Spring MVC 集成,怎么来初始化 Spring 容器的。</p>
<p>在看源码之前我们先看下 Spring MVC 在 Spring 中的架构依赖,它不是一个单独的项目,它有依赖的爸爸 spring-web 项目,也有两个兄弟 spring-webflux 和 spring-websocket 项目,本文只讲 Spring MVCSpring Webflux 和 Spring WebSocket 日后会更新 Chat。</p>
<p><img src="assets/423ba560-3227-11ea-b8e0-e5f2366b7ae1.jpg.png" alt="img" /></p>
<h3>一、上下文在 Web 容器中的启动</h3>
<h4>1.1 Tomcat 中的应用部署描述文件 web.xml</h4>
<p>以 Tomcat 作为 Web 容器为例进行分析。在 Tomcat 中web.xml 是应用的部署描述文件。</p>
<pre><code>&lt;!-- [1] Spring 配置 --&gt;
&lt;listener&gt;
&lt;listener-class&gt;org.springframework.web.context.ContextLoaderListener&lt;/listener-class&gt;
&lt;/listener&gt;
&lt;!-- 指定 Spring IOC 容器读取 bean 定义的 XML 文件的路径,默认配置在 WEB-INF 目录下 --&gt;
&lt;context-param&gt;
&lt;param-name&gt;contextConfigLocation&lt;/param-name&gt;
&lt;param-value&gt;classpath:config/applicationContext.xml&lt;/param-value&gt;
&lt;/context-param&gt;
&lt;!-- [2] Spring MVC 配置 --&gt;
&lt;servlet&gt;
&lt;servlet-name&gt;spring&lt;/servlet-name&gt;
&lt;servlet-class&gt;org.springframework.web.servlet.DispatcherServlet&lt;/servlet-class&gt;
&lt;!-- 可以自定义servlet.xml配置文件的位置和名称默认为WEB-INF目录下名称为[&lt;servlet-name&gt;]-servlet.xml如spring-servlet.xml--&gt;
&lt;init-param&gt;
&lt;param-name&gt;contextConfigLocation&lt;/param-name&gt;
&lt;param-value&gt;/WEB-INF/spring-servlet.xml&lt;/param-value&gt; // 默认
&lt;/init-param&gt;
&lt;load-on-startup&gt;1&lt;/load-on-startup&gt;
&lt;/servlet&gt;
&lt;servlet-mapping&gt;
&lt;servlet-name&gt;spring&lt;/servlet-name&gt;
&lt;url-pattern&gt;*.do&lt;/url-pattern&gt;
&lt;/servlet-mapping&gt;
</code></pre>
<p>[1] 处,配置了 org.springframework.web.context.ContextLoaderListener 对象。这是一个 javax.servlet.ServletContextListener 对象,会初始化一个 Root Spring WebApplicationContext 容器。</p>
<p>[2] 处,配置了 org.springframework.web.servlet.DispatcherServlet 对象。这是一个 javax.servlet.http.HttpServlet 对象,它除了拦截我们制定的 *.do 请求外,也会初始化一个属于它的 Spring WebApplicationContext 容器。并且,这个容器是以 [1] 处的 Root 容器作为父容器。</p>
<p>在 Servlet 容器启动时,例如 Tomcat、Jetty 启动,则会被 ContextLoaderListener 监听到,从而调用 ContextLoaderListener #contextInitialized(ServletContextEvent event) 方法,初始化 Root WebApplicationContext 容器。</p>
<h4>1.2 IOC 容器启动的基本过程</h4>
<p>先看下 ContextLoaderListener 的类图:</p>
<p><img src="assets/4b2e3250-322c-11ea-9a23-3953d44b4f10.jpg.png" alt="img" /></p>
<p>org.springframework.web.context.ContextLoaderListener实现 ServletContextListener 接口,继承 ContextLoader 类,实现 Servlet 容器启动和关闭时,分别初始化和销毁 WebApplicationContext 容器。(注意,这个 ContextLoaderListener 类,是在 spring-web 项目中。)</p>
<p>ContextLoaderListener 初始化 Root WebApplicationContext 的入口在 ContextLoaderListener#contextInitialized() 方法中,代码如下:</p>
<pre><code>// ContextLoaderListener.java
@Override
public void contextInitialized(ServletContextEvent event) {
// 初始化 Root WebApplicationContext
initWebApplicationContext(event.getServletContext());
}
</code></pre>
<p>跟进到 ContextLoader#initWebApplicationContext() 方法,代码如下:</p>
<pre><code>// ContextLoader.java
// 当前 web 容器
private static volatile WebApplicationContext currentContext;
// 线程上下文类加载器到当前 web 容器的映射
private static final Map&lt;ClassLoader, WebApplicationContext&gt; currentContextPerThread =
new ConcurrentHashMap&lt;ClassLoader, WebApplicationContext&gt;(1);
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
// &lt;1&gt; 若已经存在 ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 对应的 WebApplicationContext 对象,则抛出 IllegalStateException 异常。
// 例如,在 web.xml 中存在多个 ContextLoader。
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
throw new IllegalStateException(
&quot;Cannot initialize context because there is already a root application context present - &quot; +
&quot;check whether you have multiple ContextLoader* definitions in your web.xml!&quot;);
}
// &lt;2&gt; 打印日志
servletContext.log(&quot;Initializing Spring root WebApplicationContext&quot;);
Log logger = LogFactory.getLog(ContextLoader.class);
if (logger.isInfoEnabled()) {
logger.info(&quot;Root WebApplicationContext: initialization started&quot;);
}
// 记录开始时间
long startTime = System.currentTimeMillis();
try {
// Store context in local instance variable, to guarantee that
// it is available on ServletContext shutdown.
if (this.context == null) {
// &lt;3&gt; 初始化 context即创建 context 对象
this.context = createWebApplicationContext(servletContext);
}
// &lt;4&gt; 如果是 ConfigurableWebApplicationContext 的子类,如果未刷新,则进行配置和刷新
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) { // &lt;4.1&gt; 未刷新( 激活 )
// The context has not yet been refreshed -&gt; provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) { // &lt;4.2&gt; 无父容器,则进行加载和设置。
// The context instance was injected without an explicit parent -&gt;
// determine parent for root web application context, if any.
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
// &lt;4.3&gt; 配置 context 对象,并进行刷新
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
// &lt;5&gt; 记录在 servletContext 中
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
// &lt;6&gt; 记录到 currentContext 或 currentContextPerThread 中
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
} else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}
// &lt;7&gt; 打印日志
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info(&quot;Root WebApplicationContext initialized in &quot; + elapsedTime + &quot; ms&quot;);
}
// &lt;8&gt; 返回 context
return this.context;
} catch (RuntimeException | Error ex) {
// &lt;9&gt; 当发生异常,记录异常到 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 中,不再重新初始化。
logger.error(&quot;Context initialization failed&quot;, ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
}
</code></pre>
<ul>
<li>&lt;1&gt; 处,若已经存在 ROOT<em>WEB</em>APPLICATION<em>CONTEXT</em>ATTRIBUTE 对应的 WebApplicationContext 对象,则抛出 IllegalStateException 异常。例如,在 web.xml 中存在多个 ContextLoader。</li>
<li>&lt;2&gt; 处,打印日志。</li>
<li>&lt;3&gt; 处,调用 #createWebApplicationContext(ServletContext sc) 方法,初始化 context即创建 WebApplicationContext 对象。详细解析,胖友先跳到 「3.2.3 createWebApplicationContext」。</li>
<li>&lt;4&gt; 处,如果 context 是 ConfigurableWebApplicationContext 的子类,如果未刷新,则进行配置和刷新。</li>
<li>&lt;4.1&gt; 处,如果未刷新(激活)。默认情况下,是符合这个条件的,所以会往下执行。</li>
<li>&lt;4.2&gt;无父容器则进行加载和设置。默认情况下ContextLoader#loadParentContext(ServletContext servletContext) 方法,返回 null。代码如下</li>
</ul>
<pre><code>// ContextLoader.java
@Nullable
protected ApplicationContext loadParentContext(ServletContext servletContext) {
return null;
}
</code></pre>
<p>这是一个让子类实现的方法。当然,子类 ContextLoaderListener 并没有重写该方法。所以,实际上,&lt;4.2&gt; 处的逻辑,可以暂时忽略。</p>
<ul>
<li>&lt;4.3&gt; 处,调用 <code>#configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc)</code> 方法,配置 ConfigurableWebApplicationContext 对象,并进行刷新。</li>
<li>&lt;5&gt; 处,记录 context 在 ServletContext 中。这样,如果 web.xml 如果定义了多个 ContextLoader就会在 &lt;1&gt; 处报错。</li>
<li>&lt;6&gt; 处,记录到 currentContext 或 currentContextPerThread 中,差异在于类加载器的不同。</li>
<li>&lt;7&gt; 处,打印日志。</li>
<li>&lt;8&gt; 处,返回 context。</li>
<li>&lt;9&gt; 处,当发生异常,记录异常到 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 中,不再重新初始化。即对应到 &lt;1&gt; 处的逻辑。</li>
</ul>
<p>继续跟进到创建 Root WebApplication 的方法,<code>ContextLoader#createWebApplicationContext()</code> 方法,代码如下:</p>
<pre><code>// ContextLoader.java
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
// &lt;1&gt; 获得 context 的类
Class&lt;?&gt; contextClass = determineContextClass(sc);
// &lt;2&gt; 判断 context 的类,是否符合 ConfigurableWebApplicationContext 的类型
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException(&quot;Custom context class [&quot; + contextClass.getName() +
&quot;] is not of type [&quot; + ConfigurableWebApplicationContext.class.getName() + &quot;]&quot;);
}
// &lt;3&gt; 创建 context 的类的对象
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
</code></pre>
<p>&lt;1&gt; 处,调用 ContextLoader#determineContextClass(ServletContext servletContext) 方法,获得 context 的类。代码如下:</p>
<pre><code>// ContextLoader.java
/**
* Config param for the root WebApplicationContext implementation class to use: {@value}.
* @see #determineContextClass(ServletContext)
*/
public static final String CONTEXT_CLASS_PARAM = &quot;contextClass&quot;;
protected Class&lt;?&gt; determineContextClass(ServletContext servletContext) {
// 获得参数 contextClass 的值
String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
// 情况一,如果值非空,则获得该类
if (contextClassName != null) {
try {
return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
} catch (ClassNotFoundException ex) {
throw new ApplicationContextException(
&quot;Failed to load custom context class [&quot; + contextClassName + &quot;]&quot;, ex);
}
// 情况二,从 defaultStrategies 获得该类
} else {
contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
try {
return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
} catch (ClassNotFoundException ex) {
throw new ApplicationContextException(
&quot;Failed to load default context class [&quot; + contextClassName + &quot;]&quot;, ex);
}
}
}
</code></pre>
<p>分成两种情况。前者,从 ServletContext 配置的 context 类;后者,从 ContextLoader.properties 配置的 context 类。</p>
<p>默认情况下,我们不会主动在 ServletContext 配置的 context 类,所以基本是使用 ContextLoader.properties 配置的 context 类,即 XmlWebApplicationContext 类。</p>
<p>&lt;2&gt; 处,判断 context 的类,是否符合 ConfigurableWebApplicationContext 的类型。显然XmlWebApplicationContext 是符合条件的,所以不会抛出 ApplicationContextException 异常。</p>
<p>&lt;3&gt; 处,调用 <code>BeanUtils#instantiateClass(Class&lt;T&gt; clazz)</code> 方法,创建 context 的类的对象。</p>
<p>继续跟进到刷新 Root WebApplicationContext 容器的方法,<code>ContextLoader#configureAndRefreshWebApplicationContext()</code> 方法,代码如下:</p>
<pre><code>// ContextLoader.java
/**
* Config param for the root WebApplicationContext id,
* to be used as serialization id for the underlying BeanFactory: {@value}.
*/
public static final String CONTEXT_ID_PARAM = &quot;contextId&quot;;
/**
* Name of servlet context parameter (i.e., {@value}) that can specify the
* config location for the root context, falling back to the implementation's
* default otherwise.
* @see org.springframework.web.context.support.XmlWebApplicationContext#DEFAULT_CONFIG_LOCATION
*/
public static final String CONFIG_LOCATION_PARAM = &quot;contextConfigLocation&quot;;
public static final String CONTEXT_ID_PARAM = &quot;contextId&quot;;
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
// &lt;1&gt; 如果 wac 使用了默认编号,则重新设置 id 属性
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// The application context id is still set to its original default value
// -&gt; assign a more useful id based on available information
// 情况一,使用 contextId 属性
String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
if (idParam != null) {
wac.setId(idParam);
// 情况二,自动生成
} else {
// Generate default id...
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getContextPath()));
}
}
// &lt;2&gt;设置 context 的 ServletContext 属性
wac.setServletContext(sc);
// &lt;3&gt; 设置 context 的配置文件地址
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
wac.setConfigLocation(configLocationParam);
}
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
}
// &lt;4&gt; 执行自定义初始化 context
customizeContext(sc, wac);
// &lt;5&gt; 刷新 context执行初始化
wac.refresh();
}
</code></pre>
<p>此处,注释上即写了 wac右写了 context实际上是等价的东西。下面的文字我们统一用 wac。</p>
<p>&lt;1&gt; 处,如果 wac 使用了默认编号,则重新设置 id 属性。默认情况下,我们不会对 wac 设置编号所以会执行进去。而实际上id 的生成规则,也分成使用 contextId 在 <context-param /> 标签中设置,和自动生成两种情况。默认情况下,会走第二种情况。</p>
<p>&lt;2&gt; 处,设置 wac 的 ServletContext 属性。</p>
<p>【关键】&lt;3&gt; 处,设置 context 的配置文件地址。例如我们在 「1. 概述」 中所看到的。</p>
<pre><code>&lt;context-param&gt;
&lt;param-name&gt;contextConfigLocation&lt;/param-name&gt;
&lt;param-value&gt;classpath:config/applicationContext.xml&lt;/param-value&gt;
&lt;/context-param&gt;
</code></pre>
<p>&lt;4&gt; 处,调用 #customizeContext(ServletContext sc, ConfigurableWebApplicationContext wac) 方法,执行自定义初始化 wac。 【关键】&lt;5&gt; 处, 刷新 wac执行初始化。此处就会进行一些的 Spring 容器的初始化。</p>
<h3>二、DispatcherServlet 的启动和初始化</h3>
<p>回过头来看一眼 web.xml 的配置。代码如下:</p>
<pre><code>&lt;servlet&gt;
&lt;servlet-name&gt;spring&lt;/servlet-name&gt;
&lt;servlet-class&gt;org.springframework.web.servlet.DispatcherServlet&lt;/servlet-class&gt;
&lt;!-- 可以自定义servlet.xml配置文件的位置和名称默认为WEB-INF目录下名称为[&lt;servlet-name&gt;]-servlet.xml如spring-servlet.xml--&gt;
&lt;init-param&gt;
&lt;param-name&gt;contextConfigLocation&lt;/param-name&gt;
&lt;param-value&gt;/WEB-INF/spring-servlet.xml&lt;/param-value&gt; // 默认
&lt;/init-param&gt;
&lt;load-on-startup&gt;1&lt;/load-on-startup&gt;
&lt;/servlet&gt;
&lt;servlet-mapping&gt;
&lt;servlet-name&gt;spring&lt;/servlet-name&gt;
&lt;url-pattern&gt;*.do&lt;/url-pattern&gt;
&lt;/servlet-mapping&gt;
</code></pre>
<p>即, Servlet WebApplicationContext 容器的初始化,是在 DispatcherServlet 初始化的过程中执行。</p>
<p>DispatcherServlet 的类图如下:</p>
<p><img src="assets/584a3d50-3234-11ea-b8e0-e5f2366b7ae1.jpg.png" alt="img" /></p>
<ul>
<li>HttpServletBean负责将 ServletConfig 设置到当前 Servlet 对象中。</li>
<li>FrameworkServlet负责初始化 Spring Servlet——WebApplicationContext 容器。</li>
<li>DispatcherServlet负责初始化 Spring MVC 的各个组件,以及处理客户端的请求。</li>
</ul>
<p>每一层的 Servlet 实现类,执行对应负责的逻辑,下面我们逐个类来进行解析。</p>
<h4>2.1 HttpServletBean</h4>
<p>HttpServletBean#init() 方法负责将 ServletConfig 设置到当前 Servlet 对象中。代码如下:</p>
<pre><code>// HttpServletBean.java
@Override
public final void init() throws ServletException {
// Set bean properties from init parameters.
// &lt;1&gt; 解析 &lt;init-param /&gt; 标签,封装到 PropertyValues pvs 中
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
// &lt;2.1&gt; 将当前的这个 Servlet 对象,转化成一个 BeanWrapper 对象。从而能够以 Spring 的方式来将 pvs 注入到该 BeanWrapper 对象中
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
// &lt;2.2&gt; 注册自定义属性编辑器,一旦碰到 Resource 类型的属性,将会使用 ResourceEditor 进行解析
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
// &lt;2.3&gt; 空实现,留给子类覆盖
initBeanWrapper(bw);
// &lt;2.4&gt; 以 Spring 的方式来将 pvs 注入到该 BeanWrapper 对象中
bw.setPropertyValues(pvs, true);
} catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error(&quot;Failed to set bean properties on servlet '&quot; + getServletName() + &quot;'&quot;, ex);
}
throw ex;
}
}
// Let subclasses do whatever initialization they like.
// &lt;3&gt; 子类来实现,实现自定义的初始化逻辑。目前,有具体的代码实现。
initServletBean();
}
</code></pre>
<p>&lt;1&gt; 处,解析 Servlet 配置的 <init-param /> 标签,封装到 PropertyValues pvs 中。其中ServletConfigPropertyValues 是 HttpServletBean 的私有静态类,继承 MutablePropertyValues 类ServletConfig 的 PropertyValues 封装实现类。代码如下:</p>
<p>代码简单,实现两方面的逻辑:第一,遍历 ServletConfig 的初始化参数集合,添加到 ServletConfigPropertyValues 中;第二,判断要求的属性是否齐全。如果不齐全,则抛出 ServletException 异常。</p>
<pre><code>// HttpServletBean.java
private static class ServletConfigPropertyValues extends MutablePropertyValues {
/**
* Create new ServletConfigPropertyValues.
* @param config the ServletConfig we'll use to take PropertyValues from
* @param requiredProperties set of property names we need, where
* we can't accept default values
* @throws ServletException if any required properties are missing
*/
public ServletConfigPropertyValues(ServletConfig config, Set&lt;String&gt; requiredProperties)
throws ServletException {
// 获得缺失的属性的集合
Set&lt;String&gt; missingProps = (!CollectionUtils.isEmpty(requiredProperties) ?
new HashSet&lt;&gt;(requiredProperties) : null);
// 遍历 ServletConfig 的初始化参数集合,添加到 ServletConfigPropertyValues 中,并从 missingProps 移除
Enumeration&lt;String&gt; paramNames = config.getInitParameterNames();
while (paramNames.hasMoreElements()) {
String property = paramNames.nextElement();
Object value = config.getInitParameter(property);
// 添加到 ServletConfigPropertyValues 中
addPropertyValue(new PropertyValue(property, value));
// 从 missingProps 中移除
if (missingProps != null) {
missingProps.remove(property);
}
}
// Fail if we are still missing properties.
// 如果存在缺失的属性,抛出 ServletException 异常
if (!CollectionUtils.isEmpty(missingProps)) {
throw new ServletException(
&quot;Initialization from ServletConfig for servlet '&quot; + config.getServletName() +
&quot;' failed; the following required properties were missing: &quot; +
StringUtils.collectionToDelimitedString(missingProps, &quot;, &quot;));
}
}
}
</code></pre>
<p>&lt;2.1&gt; 处,将当前的这个 Servlet 对象,转化成一个 BeanWrapper 对象。从而能够以 Spring 的方式来将 pvs 注入到该 BeanWrapper 对象中。简单来说BeanWrapper 是 Spring 提供的一个用来操作 Java Bean 属性的工具,使用它可以直接修改一个对象的属性。</p>
<p>&lt;2.2&gt; 处,注册自定义属性编辑器,一旦碰到 Resource 类型的属性,将会使用 ResourceEditor 进行解析。</p>
<p>&lt;2.3&gt; 处,空实现,留给子类覆盖。代码如下:</p>
<pre><code>// HttpServletBean.java
/**
* Initialize the BeanWrapper for this HttpServletBean,
* possibly with custom editors.
* &lt;p&gt;This default implementation is empty.
* @param bw the BeanWrapper to initialize
* @throws BeansException if thrown by BeanWrapper methods
* @see org.springframework.beans.BeanWrapper#registerCustomEditor
*/
protected void initBeanWrapper(BeanWrapper bw) throws BeansException {
}
</code></pre>
<p>然而实际上,子类暂时木有任何实现。</p>
<p>&lt;2.4&gt; 处,以 Spring 的方式来将 pvs 注入到该 BeanWrapper 对象中,即设置到当前 Servlet 对象中。可能比较费解,我们还是举个例子。假设如下:</p>
<pre><code>// web.xml
&lt;servlet&gt;
&lt;servlet-name&gt;spring&lt;/servlet-name&gt;
&lt;servlet-class&gt;org.springframework.web.servlet.DispatcherServlet&lt;/servlet-class&gt;
&lt;init-param&gt;
&lt;param-name&gt;contextConfigLocation&lt;/param-name&gt;
&lt;param-value&gt;/WEB-INF/spring-servlet.xml&lt;/param-value&gt;
&lt;/init-param&gt;
&lt;load-on-startup&gt;1&lt;/load-on-startup&gt;
&lt;/servlet&gt;
&lt;servlet-mapping&gt;
&lt;servlet-name&gt;spring&lt;/servlet-name&gt;
&lt;url-pattern&gt;*.do&lt;/url-pattern&gt;
&lt;/servlet-mapping&gt;
</code></pre>
<p>此处有配置了 contextConfigLocation 属性,那么通过 &lt;2.4&gt; 处的逻辑,会反射设置到 FrameworkServlet.contextConfigLocation 属性。代码如下:</p>
<pre><code>// FrameworkServlet.java
/** Explicit context config location. */
@Nullable
private String contextConfigLocation;
public void setContextConfigLocation(@Nullable String contextConfigLocation) {
this.contextConfigLocation = contextConfigLocation;
}
</code></pre>
<p>看懂了这波骚操作了么?</p>
<p>&lt;3&gt; 处,调用 #initServletBean() 方法子类来实现实现自定义的初始化逻辑。目前FrameworkServlet 实现类该方法。代码如下:</p>
<pre><code>// HttpServletBean.java
protected void initServletBean() throws ServletException {
}
</code></pre>
<h4>2.2 FrameworkServlet</h4>
<p>org.springframework.web.servlet.FrameworkServlet实现 ApplicationContextAware 接口,继承 HttpServletBean 抽象类,负责初始化 Spring Servlet WebApplicationContext 容器。同时FrameworkServlet 自身也是一个抽象类。</p>
<p>跟进到 FrameworkServlet#initServletBean() 方法,进一步初始化当前 Servlet 对象。实际上,重心在初始化 Servlet WebApplicationContext 容器。代码如下:</p>
<pre><code>// FrameworkServlet.java
@Override
protected final void initServletBean() throws ServletException {
// 打日志
getServletContext().log(&quot;Initializing Spring &quot; + getClass().getSimpleName() + &quot; '&quot; + getServletName() + &quot;'&quot;);
if (logger.isInfoEnabled()) {
logger.info(&quot;Initializing Servlet '&quot; + getServletName() + &quot;'&quot;);
}
// 记录开始时间
long startTime = System.currentTimeMillis();
try {
// 初始化 WebApplicationContext 对象
this.webApplicationContext = initWebApplicationContext();
// 空实现。子类有需要,可以实现该方法,实现自定义逻辑
initFrameworkServlet();
} catch (ServletException | RuntimeException ex) {
logger.error(&quot;Context initialization failed&quot;, ex);
throw ex;
}
// 打日志
if (logger.isDebugEnabled()) {
String value = this.enableLoggingRequestDetails ?
&quot;shown which may lead to unsafe logging of potentially sensitive data&quot; :
&quot;masked to prevent unsafe logging of potentially sensitive data&quot;;
logger.debug(&quot;enableLoggingRequestDetails='&quot; + this.enableLoggingRequestDetails +
&quot;': request parameters and headers will be &quot; + value);
}
// 打日志
if (logger.isInfoEnabled()) {
logger.info(&quot;Completed initialization in &quot; + (System.currentTimeMillis() - startTime) + &quot; ms&quot;);
}
}
</code></pre>
<p>&lt;1&gt; 处,调用 #initWebApplicationContext() 方法,初始化 Servlet WebApplicationContext 对象。</p>
<p>&lt;2&gt; 处,调用 #initFrameworkServlet() 方法,空实现。子类有需要,可以实现该方法,实现自定义逻辑。代码如下:</p>
<pre><code>// FrameworkServlet.java
/**
* This method will be invoked after any bean properties have been set and
* the WebApplicationContext has been loaded. The default implementation is empty;
* subclasses may override this method to perform any initialization they require.
* @throws ServletException in case of an initialization exception
*/
protected void initFrameworkServlet() throws ServletException {
}
</code></pre>
<p>然而实际上,并没有子类,对该方法重新实现。</p>
<p>继续跟进到 FrameworkServlet#initWebApplicationContext() 方法,初始化 Servlet WebApplicationContext 对象。代码如下: 这个方法的逻辑并不复杂,但是涉及调用的方法的逻辑比较多。同时,也是本文最最最核心的方法了。</p>
<pre><code>// FrameworkServlet.java
protected WebApplicationContext initWebApplicationContext() {
// &lt;1&gt; 获得 Root WebApplicationContext 对象
WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
// &lt;2&gt; 获得 WebApplicationContext wac 变量
WebApplicationContext wac = null;
// 第一种情况,如果构造方法已经传入 webApplicationContext 属性,则直接使用
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -&gt; use it
// 赋值给 wac 变量
wac = this.webApplicationContext;
// 如果是 ConfigurableWebApplicationContext 类型,并且未激活,则进行初始化
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) { // 未激活
// The context has not yet been refreshed -&gt; provide services such as
// setting the parent context, setting the application context id, etc
// 设置 wac 的父 context 为 rootContext 对象
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -&gt; set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
// 配置和初始化 wac
configureAndRefreshWebApplicationContext(cwac);
}
}
}
// 第二种情况,从 ServletContext 获取对应的 WebApplicationContext 对象
if (wac == null) {
// No context instance was injected at construction time -&gt; see if one
// has been registered in the servlet context. If one exists, it is assumed
// that the parent context (if any) has already been set and that the
// user has performed any initialization such as setting the context id
wac = findWebApplicationContext();
}
// 第三种,创建一个 WebApplicationContext 对象
if (wac == null) {
// No context instance is defined for this servlet -&gt; create a local one
wac = createWebApplicationContext(rootContext);
}
// &lt;3&gt; 如果未触发刷新事件,则主动触发刷新事件
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -&gt; trigger initial onRefresh manually here.
onRefresh(wac);
}
// &lt;4&gt; 将 context 设置到 ServletContext 中
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
</code></pre>
<p>&lt;1&gt; 处,调用 WebApplicationContextUtils#getWebApplicationContext((ServletContext sc) 方法,获得 Root WebApplicationContext 对象。</p>
<p>&lt;2&gt; 处,获得 WebApplicationContext wac 变量。下面,会分成三种情况:第一种情况,如果构造方法已经传入 webApplicationContext 属性,则直接使用、第二种情况,从 ServletContext 获取对应的 WebApplicationContext 对象、第三种,创建一个 WebApplicationContext 对象。</p>
<p>&lt;3&gt; 处,如果未触发刷新事件,则调用 FrameworkServlet#onRefresh(ApplicationContext context) 主动触发刷新事件。另外refreshEventReceived 属性,定义如下:</p>
<pre><code>/ FrameworkServlet.java
/**
* Flag used to detect whether onRefresh has already been called.
*
* 标记是否接收到 ContextRefreshedEvent 事件。即 {@link #onApplicationEvent(ContextRefreshedEvent)}
*/
private boolean refreshEventReceived = false;
</code></pre>
<p>&lt;4&gt; 处,如果 publishContext 为 true 时,则将 context 设置到 ServletContext 中。(<code>key = FrameworkServlet.class.getName() + &quot;.CONTEXT.&quot;</code><code>value = wac</code></p>
<p>继续跟进到 FrameworkServlet#onRefresh() 方法,当 Servlet WebApplicationContext 刷新完成后,会触发 Spring MVC 组件的初始化。代码如下:</p>
<pre><code>// FrameworkServlet.java
/**
* Template method which can be overridden to add servlet-specific refresh work.
* Called after successful context refresh.
* &lt;p&gt;This implementation is empty.
* @param context the current WebApplicationContext
* @see #refresh()
*/
protected void onRefresh(ApplicationContext context) {
// For subclasses: do nothing by default.
}
</code></pre>
<p>这是一个空方法,具体的实现,在子类 DispatcherServlet 中。代码如下:</p>
<pre><code>// DispatcherServlet.java
/**
* This implementation calls {@link #initStrategies}.
*/
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
/**
* Initialize the strategy objects that this servlet uses.
* &lt;p&gt;May be overridden in subclasses in order to initialize further strategy objects.
*/
protected void initStrategies(ApplicationContext context) {
// 初始化 MultipartResolver
initMultipartResolver(context);
// 初始化 LocaleResolver
initLocaleResolver(context);
// 初始化 ThemeResolver
initThemeResolver(context);
// 初始化 HandlerMappings
initHandlerMappings(context);
// 初始化 HandlerAdapters
initHandlerAdapters(context);
// 初始化 HandlerExceptionResolvers
initHandlerExceptionResolvers(context);
// 初始化 RequestToViewNameTranslator
initRequestToViewNameTranslator(context);
// 初始化 ViewResolvers
initViewResolvers(context);
// 初始化 FlashMapManager
initFlashMapManager(context);
}
</code></pre>
<h4>2.3 DispatcherServlet</h4>
<p>DispatcherServlet#initStrategies(ApplicationContext context) 方法,初始化 Spring MVC 的各种组件。代码如下:</p>
<pre><code>// DispatcherServlet.java
/** MultipartResolver used by this servlet. */
@Nullable
private MultipartResolver multipartResolver;
/** LocaleResolver used by this servlet. */
@Nullable
private LocaleResolver localeResolver;
/** ThemeResolver used by this servlet. */
@Nullable
private ThemeResolver themeResolver;
/** List of HandlerMappings used by this servlet. */
@Nullable
private List&lt;HandlerMapping&gt; handlerMappings;
/** List of HandlerAdapters used by this servlet. */
@Nullable
private List&lt;HandlerAdapter&gt; handlerAdapters;
/** List of HandlerExceptionResolvers used by this servlet. */
@Nullable
private List&lt;HandlerExceptionResolver&gt; handlerExceptionResolvers;
/** RequestToViewNameTranslator used by this servlet. */
@Nullable
private RequestToViewNameTranslator viewNameTranslator;
/** FlashMapManager used by this servlet. */
@Nullable
private FlashMapManager flashMapManager;
/** List of ViewResolvers used by this servlet. */
@Nullable
private List&lt;ViewResolver&gt; viewResolvers;
/**
* This implementation calls {@link #initStrategies}.
*/
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
/**
* Initialize the strategy objects that this servlet uses.
* &lt;p&gt;May be overridden in subclasses in order to initialize further strategy objects.
*/
protected void initStrategies(ApplicationContext context) {
// 初始化 MultipartResolver
initMultipartResolver(context);
// 初始化 LocaleResolver
initLocaleResolver(context);
// 初始化 ThemeResolver
initThemeResolver(context);
// 初始化 HandlerMappings
initHandlerMappings(context);
// 初始化 HandlerAdapters
initHandlerAdapters(context);
// 初始化 HandlerExceptionResolvers
initHandlerExceptionResolvers(context);
// 初始化 RequestToViewNameTranslator
initRequestToViewNameTranslator(context);
// 初始化 ViewResolvers
initViewResolvers(context);
// 初始化 FlashMapManager
initFlashMapManager(context);
}
</code></pre>
<p>一共有 9 个组件。下面,我们对这 9 个组件,做一个简单的介绍。</p>
<h5><strong>2.3.1 MultipartResolver</strong></h5>
<p>org.springframework.web.multipart.MultipartResolver内容类型Content-Type为 multipart/* 的请求的解析器接口。</p>
<p>例如文件上传请求MultipartResolver 会将 HttpServletRequest 封装成 MultipartHttpServletRequest这样从 MultipartHttpServletRequest 中获得上传的文件。</p>
<p>MultipartResolver 接口,代码如下:</p>
<pre><code>// MultipartResolver.java
public interface MultipartResolver {
/**
* 是否为 multipart 请求
*/
boolean isMultipart(HttpServletRequest request);
/**
* 将 HttpServletRequest 请求封装成 MultipartHttpServletRequest 对象
*/
MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException;
/**
* 清理处理 multipart 产生的资源,例如临时文件
*
*/
void cleanupMultipart(MultipartHttpServletRequest request);
}
</code></pre>
<h5><strong>2.3.2 LocaleResolver</strong></h5>
<p>org.springframework.web.servlet.LocaleResolver本地化国际化解析器接口。代码如下</p>
<pre><code>// LocaleResolver.java
public interface LocaleResolver {
/**
* 从请求中,解析出要使用的语言。例如,请求头的 &quot;Accept-Language&quot;
*/
Locale resolveLocale(HttpServletRequest request);
/**
* 设置请求所使用的语言
*/
void setLocale(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Locale locale);
}
</code></pre>
<h5><strong>2.3.3 ThemeResolver</strong></h5>
<p>org.springframework.web.servlet.ThemeResolver主题解析器接口。代码如下</p>
<pre><code>// ThemeResolver.java
public interface ThemeResolver {
/**
* 从请求中,解析出使用的主题。例如,从请求头 User-Agent判断使用 PC 端,还是移动端的主题
*/
String resolveThemeName(HttpServletRequest request);
/**
* 设置请求,所使用的主题。
*/
void setThemeName(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable String themeName);
}
</code></pre>
<p>当然,因为现在的前端,基本和后端做了分离,所以这个功能已经越来越少用了。</p>
<h5><strong>2.3.4 HandlerMapping</strong></h5>
<p>org.springframework.web.servlet.HandlerMapping处理器匹配接口根据请求handler获得其的处理器handler和拦截器们HandlerInterceptor 数组)。代码如下:</p>
<pre><code>// HandlerMapping.java
public interface HandlerMapping {
String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + &quot;.pathWithinHandlerMapping&quot;;
String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + &quot;.bestMatchingPattern&quot;;
String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + &quot;.introspectTypeLevelMapping&quot;;
String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + &quot;.uriTemplateVariables&quot;;
String MATRIX_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + &quot;.matrixVariables&quot;;
String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + &quot;.producibleMediaTypes&quot;;
/**
* 获得请求对应的处理器和拦截器们
*/
@Nullable
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}
</code></pre>
<p>返回的对象类型是 HandlerExecutionChain它包含处理器handler和拦截器们HandlerInterceptor 数组)。简单代码如下:</p>
<pre><code>// HandlerExecutionChain.java
/**
* 处理器
*/
private final Object handler;
/**
* 拦截器数组
*/
@Nullable
private HandlerInterceptor[] interceptors;
</code></pre>
<p>注意,处理器的类型可能和我们想的不太一样,是个 Object 类型。</p>
<h5><strong>2.3.5 HandlerAdapter</strong></h5>
<p>org.springframework.web.servlet.HandlerAdapter处理器适配器接口。代码如下</p>
<pre><code>// HandlerAdapter.java
public interface HandlerAdapter {
/**
* 是否支持该处理器
*/
boolean supports(Object handler);
/**
* 执行处理器,返回 ModelAndView 结果
*/
@Nullable
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
/**
* 返回请求的最新更新时间。
*
* 如果不支持该操作,则返回 -1 即可
*/
long getLastModified(HttpServletRequest request, Object handler);
}
</code></pre>
<p>因为,处理器 handler 的类型是 Object 类型,需要有一个调用者来实现 handler 是怎么被使用,怎么被执行。而 HandlerAdapter 的用途就在于此。可能如果接口名改成 HandlerInvoker笔者觉得会更好理解。</p>
<p>三个接口,代码比较好理解,胖友瞅一眼,就不细讲了。</p>
<h5><strong>2.3.6 HandlerExceptionResolver</strong></h5>
<p>org.springframework.web.servlet.HandlerExceptionResolver处理器异常解析器接口将处理器handler执行时发生的异常解析转换成对应的 ModelAndView 结果。代码如下:</p>
<pre><code>// HandlerExceptionResolver.java
public interface HandlerExceptionResolver {
/**
* 解析异常,转换成对应的 ModelAndView 结果
*/
@Nullable
ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
}
</code></pre>
<h5><strong>2.3.7 RequestToViewNameTranslator</strong></h5>
<p>org.springframework.web.servlet.RequestToViewNameTranslator请求到视图名的转换器接口。代码如下</p>
<pre><code>// RequestToViewNameTranslator.java
public interface RequestToViewNameTranslator {
/**
* 根据请求,获得其视图名
*/
@Nullable
String getViewName(HttpServletRequest request) throws Exception;
}
</code></pre>
<p>粗略这么一看,有点不太好理解。捉摸了一下,还是放在后面一起讲解源码的时候,在详细讲解。</p>
<h5><strong>2.3.8 ViewResolver</strong></h5>
<p>org.springframework.web.servlet.ViewResolver实体解析器接口根据视图名和国际化获得最终的视图 View 对象。代码如下:</p>
<pre><code>// ViewResolver.java
public interface ViewResolver {
/**
* 根据视图名和国际化,获得最终的 View 对象
*/
@Nullable
View resolveViewName(String viewName, Locale locale) throws Exception;
}
</code></pre>
<p>ViewResolver 的实现类比较多例如说InternalResourceViewResolver 负责解析 JSP 视图FreeMarkerViewResolver 负责解析 Freemarker 视图。当然,详细的,我们后续文章解析。</p>
<h5><strong>2.3.9 FlashMapManager</strong></h5>
<p>org.springframework.web.servlet.FlashMapManagerFlashMap 管理器接口,负责重定向时,保存参数到临时存储中。代码如下:</p>
<pre><code>// FlashMapManager.java
public interface FlashMapManager {
/**
* 恢复参数,并将恢复过的和超时的参数从保存介质中删除
*/
@Nullable
FlashMap retrieveAndUpdate(HttpServletRequest request, HttpServletResponse response);
/**
* 将参数保存起来
*/
void saveOutputFlashMap(FlashMap flashMap, HttpServletRequest request, HttpServletResponse response);
}
</code></pre>
<p>默认情况下,这个临时存储会是 Session。也就是说</p>
<ul>
<li>重定向前,保存参数到 Seesion 中。</li>
<li>重定向后,从 Session 中获得参数,并移除。</li>
<li>实际场景下,使用的非常少,特别是前后端分离之后。</li>
</ul>
<h3>三、MVC 是怎么处理 HTTP 分发请求的</h3>
<p>一个用户的请求,是如何被 DispatcherServlet 处理的。如下图所示:</p>
<p><img src="assets/2480b4f0-323d-11ea-924d-0fd6db928ace.jpg.png" alt="img" /></p>
<p>摘自《Spring MVC 原理探秘——一个请求的旅行过程》</p>
<p>整体流程实际不复杂,但是涉及的全部代码会非常多,所以下面重点在于解析整体的流程。</p>
<h4>3.1 不同 HttpMethod 的请求处理</h4>
<p>从整体流程图,我们看到请求首先是被 DispatcherServlet 所处理但是实际上FrameworkServlet 才是真正的入门。FrameworkServlet 会实现:</p>
<pre><code>#doGet(HttpServletRequest request, HttpServletResponse response)
#doPost(HttpServletRequest request, HttpServletResponse response)
#doPut(HttpServletRequest request, HttpServletResponse response)
#doDelete(HttpServletRequest request, HttpServletResponse response)
#doOptions(HttpServletRequest request, HttpServletResponse response)
#doTrace(HttpServletRequest request, HttpServletResponse response)
#service(HttpServletRequest request, HttpServletResponse response)
</code></pre>
<p>等方法。而这些实现,最终会调用 <code>#processRequest(HttpServletRequest request, HttpServletResponse response)</code> 方法,处理请求。</p>
<p><code>FrameworkServlet#service(HttpServletRequest request, HttpServletResponse response)</code> 方法,代码如下:</p>
<pre><code>// FrameworkServlet.java
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// &lt;1&gt; 获得请求方法
HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
// &lt;2.1&gt; 处理 PATCH 请求
if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
processRequest(request, response);
// &lt;2.2&gt; 调用父类,处理其它请求
} else {
super.service(request, response);
}
}
</code></pre>
<p>&lt;1&gt; 处,获得请求方法。</p>
<p>&lt;2.1&gt; 处,若请求方法是 HttpMethod.PATCH调用 #processRequest(HttpServletRequest request, HttpServletResponse response) 方法,处理请求。因为 HttpServlet 默认没提供 #doPatch(HttpServletRequest request, HttpServletResponse response) 方法,所以只能通过父类的 #service(...) 方法,从而实现。另外,关于 processRequest 的详细解析,见 「2.2 processRequest」。</p>
<p>&lt;2.2&gt; 处,其它类型的请求方法,还是调用父类的 HttpServlet#service(HttpServletRequest request, HttpServletResponse response) 方法,进行处理。代码如下:</p>
<pre><code>// HttpServlet.java
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String method = req.getMethod();
if (method.equals(METHOD_GET)) {
long lastModified = getLastModified(req);
if (lastModified == -1) {
// servlet doesn't support if-modified-since, no reason
// to go through further expensive logic
doGet(req, resp);
} else {
long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
if (ifModifiedSince &lt; lastModified) {
// If the servlet mod time is later, call doGet()
// Round down to the nearest second for a proper compare
// A ifModifiedSince of -1 will always be less
maybeSetLastModified(resp, lastModified);
doGet(req, resp);
} else {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
} else if (method.equals(METHOD_HEAD)) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);
} else if (method.equals(METHOD_POST)) {
doPost(req, resp);
} else if (method.equals(METHOD_PUT)) {
doPut(req, resp);
} else if (method.equals(METHOD_DELETE)) {
doDelete(req, resp);
} else if (method.equals(METHOD_OPTIONS)) {
doOptions(req,resp);
} else if (method.equals(METHOD_TRACE)) {
doTrace(req,resp);
} else {
//
// Note that this means NO servlet supports whatever
// method was requested, anywhere on this server.
//
String errMsg = lStrings.getString(&quot;http.method_not_implemented&quot;);
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}
</code></pre>
<p>可能会有胖友有疑惑,为什么不在 <code>#service(HttpServletRequest request, HttpServletResponse response)</code> 方法,直接调用 <code>#processRequest(HttpServletRequest request, HttpServletResponse response)</code> 方法就好列?因为针对不同的请求方法,处理略微有所不同。</p>
<h4>3.2 针对 doGet &amp; doPost &amp; doPut &amp; doDelete 请求方式的处理</h4>
<p>这四个方法,都是直接调用 <code>#processRequest(HttpServletRequest request, HttpServletResponse response)</code> 方法,处理请求。代码如下:</p>
<pre><code>// FrameworkServlet.java
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
@Override
protected final void doPut(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
@Override
protected final void doDelete(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
</code></pre>
<h4>3.3 针对 doOptions 请求的处理</h4>
<pre><code>// FrameworkServlet.java
/** Should we dispatch an HTTP OPTIONS request to {@link #doService}?. */
private boolean dispatchOptionsRequest = false;
/**
* Delegate OPTIONS requests to {@link #processRequest}, if desired.
* &lt;p&gt;Applies HttpServlet's standard OPTIONS processing otherwise,
* and also if there is still no 'Allow' header set after dispatching.
* @see #doService
*/
@Override
protected void doOptions(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 如果 dispatchOptionsRequest 为 true则处理该请求
if (this.dispatchOptionsRequest || CorsUtils.isPreFlightRequest(request)) {
// 处理请求
processRequest(request, response);
// 如果响应 Header 包含 &quot;Allow&quot;,则不需要交给父方法处理
if (response.containsHeader(&quot;Allow&quot;)) {
// Proper OPTIONS response coming from a handler - we're done.
return;
}
}
// Use response wrapper in order to always add PATCH to the allowed methods
// 调用父方法,并在响应 Header 的 &quot;Allow&quot; 增加 PATCH 的值
super.doOptions(request, new HttpServletResponseWrapper(response) {
@Override
public void setHeader(String name, String value) {
if (&quot;Allow&quot;.equals(name)) {
value = (StringUtils.hasLength(value) ? value + &quot;, &quot; : &quot;&quot;) + HttpMethod.PATCH.name();
}
super.setHeader(name, value);
}
});
}
</code></pre>
<p>OPTIONS 请求方法,实际场景下用的少。</p>
<h4>3.4 针对 doTrace 请求的处理</h4>
<pre><code>// FrameworkServlet.java
/** Should we dispatch an HTTP TRACE request to {@link #doService}?. */
private boolean dispatchTraceRequest = false;
/**
* Delegate TRACE requests to {@link #processRequest}, if desired.
* &lt;p&gt;Applies HttpServlet's standard TRACE processing otherwise.
* @see #doService
*/
@Override
protected void doTrace(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 如果 dispatchTraceRequest 为 true则处理该请求
if (this.dispatchTraceRequest) {
// 处理请求
processRequest(request, response);
// 如果响应的内容类型为 &quot;message/http&quot;,则不需要交给父方法处理
if (&quot;message/http&quot;.equals(response.getContentType())) {
// Proper TRACE response coming from a handler - we're done.
return;
}
}
// 调用父方法
super.doTrace(request, response);
}
</code></pre>
<p>TRACE 请求方法,实际场景下用的少。</p>
<h4>3.5 处理请求</h4>
<p><code>#processRequest(HttpServletRequest request, HttpServletResponse response)</code> 方法,处理请求。代码如下:</p>
<pre><code>// FrameworkServlet.java
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// &lt;1&gt; 记录当前时间,用于计算 web 请求的处理时间
long startTime = System.currentTimeMillis();
// &lt;2&gt; 记录异常
Throwable failureCause = null;
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request);
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
initContextHolders(request, localeContext, requestAttributes);
try {
// &lt;3&gt; 执行真正的逻辑
doService(request, response);
} catch (ServletException | IOException ex) {
failureCause = ex; // &lt;8&gt;
throw ex;
} catch (Throwable ex) {
failureCause = ex; // &lt;8&gt;
throw new NestedServletException(&quot;Request processing failed&quot;, ex);
} finally {
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
// &lt;4&gt; 打印请求日志,并且日志级别为 DEBUG
logResult(request, response, failureCause, asyncManager);
// &lt;5&gt; 发布 ServletRequestHandledEvent 事件
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
</code></pre>
<h4>3.6 DispatcherServlet 处理请求</h4>
<p><code>#doService(HttpServletRequest request, HttpServletResponse response)</code> 方法DispatcherServlet 的处理请求的入口方法,代码如下:</p>
<pre><code>// DispatcherServlet.java
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
// &lt;1&gt; 打印请求日志,并且日志级别为 DEBUG
logRequest(request);
// Keep a snapshot of the request attributes in case of an include,
// to be able to restore the original attributes after the include.
Map&lt;String, Object&gt; attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap&lt;&gt;();
Enumeration&lt;?&gt; attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}
// Make framework objects available to handlers and view objects.
// &lt;2&gt; 设置 Spring 框架中的常用对象到 request 属性中
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
// &lt;3&gt; flashMapManager
if (this.flashMapManager != null) {
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
}
try {
// &lt;4&gt; 执行请求的分发
doDispatch(request, response);
} finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
}
</code></pre>
<h4>3.7 请求的分发</h4>
<p>跟进到 DispatcherServlet#doDispatch() 方法,执行请求的分发。代码如下:</p>
<pre><code>// DispatcherServlet.java
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
&lt;1&gt; TODO
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
&lt;2&gt; 检查是否是上传请求。如果是,则封装成 MultipartHttpServletRequest 对象。
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
// &lt;3&gt; 获得请求对应的 HandlerExecutionChain 对象
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) { // &lt;3.1&gt; 如果获取不到,则根据配置抛出异常或返回 404 错误
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
// &lt;4&gt; 获得当前 handler 对应的 HandlerAdapter 对象
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
// &lt;4.1&gt; last-modified
String method = request.getMethod();
boolean isGet = &quot;GET&quot;.equals(method);
if (isGet || &quot;HEAD&quot;.equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) &amp;&amp; isGet) {
return;
}
}
// &lt;5&gt; 前置处理 拦截器
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
// &lt;6&gt; 真正的调用 handler 方法,并返回视图
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// &lt;7&gt; TODO
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
// &lt;8&gt; 视图
applyDefaultViewName(processedRequest, mv);
// &lt;9&gt; 后置处理 拦截器
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception ex) {
dispatchException = ex; // &lt;10&gt; 记录异常
} catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException(&quot;Handler dispatch failed&quot;, err); // &lt;10&gt; 记录异常
}
// &lt;11&gt; 处理正常和异常的请求调用结果。
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
} catch (Exception ex) {
// &lt;12&gt; 已完成 拦截器
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
} catch (Throwable err) {
// &lt;12&gt; 已完成 拦截器
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException(&quot;Handler processing failed&quot;, err));
} finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
} else {
// &lt;13&gt; Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
</code></pre>
<p>&lt;2&gt; 处,调用 <code>#checkMultipart(HttpServletRequest request)</code> 方法,检查是否是上传请求。如果是,则封装成 MultipartHttpServletRequest 对象。</p>
<p>&lt;3&gt; 处,调用 <code>#getHandler(HttpServletRequest request)</code> 方法,返回请求对应的是 HandlerExecutionChain 对象它包含处理器handler和拦截器们HandlerInterceptor 数组)。代码如下:</p>
<pre><code>// DispatcherServlet.java
/** List of HandlerMappings used by this servlet. */
@Nullable
private List&lt;HandlerMapping&gt; handlerMappings;
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
// 遍历 HandlerMapping 数组
for (HandlerMapping mapping : this.handlerMappings) {
// 获得请求对应的 HandlerExecutionChain 对象
HandlerExecutionChain handler = mapping.getHandler(request);
// 获得到,则返回
if (handler != null) {
return handler;
}
}
}
return null;
}
</code></pre>
<p>&lt;3.1&gt; 处,如果获取不到,则调用 <code>#noHandlerFound(HttpServletRequest request, HttpServletResponse response)</code> 根据配置抛出异常或返回 404 错误。</p>
<p>&lt;4&gt; 处,调用 <code>#getHandlerAdapter(Object handler)</code> 方法,获得当前 handler 对应的 HandlerAdapter 对象。代码如下:</p>
<pre><code>// DispatcherServlet.java
/** List of HandlerAdapters used by this servlet. */
@Nullable
private List&lt;HandlerAdapter&gt; handlerAdapters;
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
// 遍历 HandlerAdapter 数组
for (HandlerAdapter adapter : this.handlerAdapters) {
// 判断是否支持当前处理器
if (adapter.supports(handler)) {
// 如果支持,则返回
return adapter;
}
}
}
// 没找到对应的 HandlerAdapter 对象,抛出 ServletException 异常
throw new ServletException(&quot;No adapter for handler [&quot; + handler +
&quot;]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler&quot;);
}
</code></pre>
<p>【前置拦截器】&lt;5&gt; 处,调用 <code>HandlerExecutionChain#applyPreHandle(HttpServletRequest request, HttpServletResponse response)</code> 方法,拦截器的前置处理,即调用 <code>HandlerInterceptor#preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)</code> 方法。</p>
<p>【Controller】&lt;6&gt; 处,调用 <code>HandlerAdapter#handle(HttpServletRequest request, HttpServletResponse response, Object handler)</code> 方法,真正的调用 handler 方法,并返回视图。这里,一般就会调用我们定义的 Controller 的方法。</p>
<p>&lt;8&gt; 处,调用 <code>#applyDefaultViewName(HttpServletRequest request, ModelAndView mv)</code> 方法,当无视图的情况下,设置默认视图。代码如下:</p>
<pre><code>// DispatcherServlet.java
/** RequestToViewNameTranslator used by this servlet. */
@Nullable
private RequestToViewNameTranslator viewNameTranslator;
private void applyDefaultViewName(HttpServletRequest request, @Nullable ModelAndView mv) throws Exception {
if (mv != null &amp;&amp; !mv.hasView()) { // 无视图
// 获得默认视图
String defaultViewName = getDefaultViewName(request);
// 设置默认视图
if (defaultViewName != null) {
mv.setViewName(defaultViewName);
}
}
}
@Nullable
protected String getDefaultViewName(HttpServletRequest request) throws Exception {
// 从请求中,获得视图
return (this.viewNameTranslator != null ? this.viewNameTranslator.getViewName(request) : null);
}
</code></pre>
<p>【后置拦截器】&lt;9&gt; 处,调用 <code>HandlerExecutionChain#applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv)`` 方法,拦截器的后置处理,即调用 HandlerInterceptor#postHandle(HttpServletRequest request, HttpServletResponse response, Object handler)</code> 方法。</p>
<p>&lt;10&gt; 处,记录异常。注意,此处仅仅记录,不会抛出异常,而是统一交给 &lt;11&gt; 处理。</p>
<p>&lt;11&gt; 处,调用 <code>#processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception)</code> 方法,处理正常和异常的请求调用结果。注意,正常的、异常的,都会进行处理。</p>
<p>【已完成拦截器】&lt;12&gt; 处,调用 <code>#triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, Exception ex)</code> 方法,拦截器的已完成处理,即调用 <code>HandlerInterceptor#triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex)</code> 方法。</p>
<p>&lt;13&gt; 处,如果是上传请求,则调用 <code>#cleanupMultipart(HttpServletRequest request)</code> 方法,清理资源。</p>
<h3>四、MVC 视图的呈现</h3>
<h4>4.1 处理请求响应的结果数据</h4>
<pre><code>DispatcherServlet#processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception)
</code></pre>
<p>方法,处理正常和异常的请求调用结果。代码如下:</p>
<pre><code>// DispatcherServlet.java
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
// &lt;1&gt; 标记,是否是生成的 ModelAndView 对象
boolean errorView = false;
// &lt;2&gt; 如果是否异常的结果
if (exception != null) {
// 情况一,从 ModelAndViewDefiningException 中获得 ModelAndView 对象
if (exception instanceof ModelAndViewDefiningException) {
logger.debug(&quot;ModelAndViewDefiningException encountered&quot;, exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
// 情况二,处理异常,生成 ModelAndView 对象
} else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
// 标记 errorView
errorView = (mv != null);
}
}
// Did the handler return a view to render?
if (mv != null &amp;&amp; !mv.wasCleared()) {
// &lt;3.1&gt; 渲染页面
render(mv, request, response);
// &lt;3.2&gt; 清理请求中的错误消息属性
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
} else {
if (logger.isTraceEnabled()) {
logger.trace(&quot;No view rendering, null ModelAndView returned.&quot;);
}
}
// &lt;4&gt; TODO
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
// &lt;5&gt; 已完成处理 拦截器
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
</code></pre>
<p>&lt;1&gt;errorView 属性,标记是否是生成的 ModelAndView 对象。</p>
<p>&lt;2&gt; 处,如果是否异常的结果。</p>
<p>&lt;2.1&gt; 处,情况一,从 ModelAndViewDefiningException 中获得 ModelAndView 对象。</p>
<p>&lt;2.2&gt; 处,情况二,调用 <code>#processHandlerException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)</code> 方法,处理异常,生成 ModelAndView 对象。代码如下:</p>
<pre><code>// DispatcherServlet.java
@Nullable
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
@Nullable Object handler, Exception ex) throws Exception {
// Success and error responses may use different content types
// 移除 PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE 属性
request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
// Check registered HandlerExceptionResolvers...
// &lt;a&gt; 遍历 HandlerExceptionResolver 数组,解析异常,生成 ModelAndView 对象
ModelAndView exMv = null;
if (this.handlerExceptionResolvers != null) {
// 遍历 HandlerExceptionResolver 数组
for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
// 解析异常,生成 ModelAndView 对象
exMv = resolver.resolveException(request, response, handler, ex);
// 生成成功,结束循环
if (exMv != null) {
break;
}
}
}
// 情况一,生成了 ModelAndView 对象,进行返回
if (exMv != null) {
// ModelAndView 对象为空,则返回 null
if (exMv.isEmpty()) {
request.setAttribute(EXCEPTION_ATTRIBUTE, ex); // 记录异常到 request 中
return null;
}
// We might still need view name translation for a plain error model...
// 设置默认视图
if (!exMv.hasView()) {
String defaultViewName = getDefaultViewName(request);
if (defaultViewName != null) {
exMv.setViewName(defaultViewName);
}
}
// 打印日志
if (logger.isTraceEnabled()) {
logger.trace(&quot;Using resolved error view: &quot; + exMv, ex);
}
if (logger.isDebugEnabled()) {
logger.debug(&quot;Using resolved error view: &quot; + exMv);
}
// 设置请求中的错误消息属性
WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
return exMv;
}
// 情况二,未生成 ModelAndView 对象,则抛出异常
throw ex;
}
</code></pre>
<p><a> 处,遍历 HandlerExceptionResolver 数组,调用 HandlerExceptionResolver#resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) 方法,解析异常,生成 ModelAndView 对象。</p>
<p><b> 处,情况一,生成了 ModelAndView 对象,进行返回。当然,这里的后续代码还有 10 多行,比较简单,胖友自己瞅瞅就 OK 啦。</p>
<p><c> 处,情况二,未生成 ModelAndView 对象,则抛出异常。</p>
<p>&lt;3.1&gt; 处,调用 <code>#render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response)</code> 方法,渲染页面。</p>
<p>&lt;3.2&gt; 处,当是 &lt;2&gt; 处的情况二时,则调用 <code>WebUtils#clearErrorRequestAttributes(HttpServletRequest request)</code> 方法,清理请求中的错误消息属性。为什么会有这一步呢?答案在 <code>#processHandlerException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)</code> 方法中,会调用 <code>WebUtils#exposeErrorRequestAttributes(HttpServletRequest request, Throwable ex, String servletName)</code> 方法,设置请求中的错误消息属性。</p>
<p>&lt;4&gt;TODO 【拦截器】&lt;5&gt; 处,调用 <code>#triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, Exception ex)</code> 方法,拦截器的已完成处理,即调用 <code>HandlerInterceptor#triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex)</code> 方法。</p>
<h4>4.2 渲染页面</h4>
<p>跟进到 <code>DispatcherServlet#render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response)</code> 方法,渲染 ModelAndView。代码如下</p>
<pre><code>// DispatcherServlet.java
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Determine locale for request and apply it to the response.
// &lt;1&gt; TODO 从 request 中获得 Locale 对象,并设置到 response 中
Locale locale = (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
response.setLocale(locale);
// 获得 View 对象
View view;
String viewName = mv.getViewName();
// 情况一,使用 viewName 获得 View 对象
if (viewName != null) {
// We need to resolve the view name.
// &lt;2.1&gt; 使用 viewName 获得 View 对象
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
if (view == null) { // 获取不到,抛出 ServletException 异常
throw new ServletException(&quot;Could not resolve view with name '&quot; + mv.getViewName() +
&quot;' in servlet with name '&quot; + getServletName() + &quot;'&quot;);
}
// 情况二,直接使用 ModelAndView 对象的 View 对象
} else {
// No need to lookup: the ModelAndView object contains the actual View object.
// 直接使用 ModelAndView 对象的 View 对象
view = mv.getView();
if (view == null) { // 获取不到,抛出 ServletException 异常
throw new ServletException(&quot;ModelAndView [&quot; + mv + &quot;] neither contains a view name nor a &quot; +
&quot;View object in servlet with name '&quot; + getServletName() + &quot;'&quot;);
}
}
// Delegate to the View object for rendering.
// 打印日志
if (logger.isTraceEnabled()) {
logger.trace(&quot;Rendering view [&quot; + view + &quot;] &quot;);
}
try {
// &lt;3&gt; 设置响应的状态码
if (mv.getStatus() != null) {
response.setStatus(mv.getStatus().value());
}
// &lt;4&gt; 渲染页面
view.render(mv.getModelInternal(), request, response);
} catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug(&quot;Error rendering view [&quot; + view + &quot;]&quot;, ex);
}
throw ex;
}
}
</code></pre>
<p>&lt;1&gt; 处,调用 <code>LocaleResolver#resolveLocale(HttpServletRequest request)</code> 方法,从 request 中获得 Locale 对象,并设置到 response 中。</p>
<p>&lt;2&gt; 处,获得 View 对象。分成两种情况,代码比较简单,胖友自己瞅瞅。</p>
<p>&lt;2.1&gt; 处,调用 <code>#resolveViewName(String viewName, Map&lt;String, Object&gt; model, Locale locale, HttpServletRequest request)</code> 方法,使用 viewName 获得 View 对象。代码如下:</p>
<pre><code>// DispatcherServlet.java
@Nullable
protected View resolveViewName(String viewName, @Nullable Map&lt;String, Object&gt; model,
Locale locale, HttpServletRequest request) throws Exception {
if (this.viewResolvers != null) {
// 遍历 ViewResolver 数组
for (ViewResolver viewResolver : this.viewResolvers) {
// 根据 viewName + locale 参数,解析出 View 对象
View view = viewResolver.resolveViewName(viewName, locale);
// 解析成功,直接返回 View 对象
if (view != null) {
return view;
}
}
}
// 返回空
return null;
}
</code></pre>
<p>&lt;3&gt; 处,设置响应的状态码。</p>
<p>&lt;4&gt; 处,调用 <code>View#render(Map&lt;String, ?&gt; model, HttpServletRequest request, HttpServletResponse response)</code> 方法,渲染视图。</p>
<h3>总结</h3>
<p>Spring MVC 的初始化及请求分发处理请求的流程总结:</p>
<ul>
<li>ContextLoaderListener 监听 Servlet 容器tomcat、jetty、jboss 等)的启动事件,调用 <code>ContextLoaderListener #contextInitialized(ServletContextEvent event)</code> 方法,初始化 Root WebApplicationContext 容器。</li>
<li>HttpServletBean 将 ServletConfig 设置到当前 Servlet 对象中,解析 Servlet 配置的 <init-param /> 标签,封装到 PropertyValues 中。</li>
<li>FrameworkServlet#initServletBean() 方法,进一步初始化当前 Servlet 对象。调用 #initWebApplicationContext() 方法,初始化 Servlet WebApplicationContext 对象,设置父容器为 Root WebApplicationContext 容器。</li>
<li>DispatcherServlet#initStrategies() 方法,初始化 MultipartResolver、LocaleResolver、ThemeResolver、HandlerMappings、HandlerAdapters、HandlerExceptionResolvers 、RequestToViewNameTranslator、ViewResolvers、FlashMapManager 组件。</li>
<li>FrameworkServlet 处理 POST、GET、PUT、DELETE、OPTIONS、TRANCE 类型的 HTTP 请求。</li>
<li>DispatcherServlet 执行请求的分发,根据请求获得请求对应的 HandlerExecutionChain 对象,对请求 handle 进行适配获得 HandlerAdapter 对象,执行前置处理器,执行 controller 中的 handle() 方法并返回视图,生成 ModelAndView 视图,执行后置处理器。</li>
<li>DispatcherServlet#processDispatchResult() 方法处理请求响应结果并渲染页面。(封装 response、响应状态码、渲染视图</li>
</ul>
</li>
</ul>
</div>
</div>
<div>
<div style="float: left">
<a href="/文章/Spring Boot 教程:如何开发一个 starter.md.html">上一页</a>
</div>
<div style="float: right">
<a href="/文章/Spring MyBatis和Spring整合的奥秘.md.html">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"7099801ec9e18b66","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
</body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-NPSEEVD756');
var path = window.location.pathname
var cookie = getCookie("lastPath");
console.log(path)
if (path.replace("/", "") === "") {
if (cookie.replace("/", "") !== "") {
console.log(cookie)
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
}
} else {
setCookie("lastPath", path)
}
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return "";
}
</script>
</html>