Spring MVC 五 - Spring MVC的配置和DispatcherServlet初始化过程
今天的内容是SpringMVC的初始化过程,其实也就是DispatcherServilet的初始化过程。
Special Bean Types
DispatcherServlet委托如下一些特殊的bean来处理请求、并渲染正确的返回。这些特殊的bean是Spring MVC框架管理的bean、按照Spring框架的约定处理相关请求,一般情况下是框架内置的,我们当然也可以定制或扩展他们的功能。
这些特殊bean包括:
- HandlerMapping:根据一定的规则把请求映射到对应的HandlerMapping去处理,HandlerMapping可以包含一系列拦截器,进行前置或后置处理。框架默认提供了RequestMappingHandlerMapping(处理@RequestMapping注解方法的)和SimpleUrlHandlerMapping两个HandlerMapping。
- HandlerAdapter:HandlerMapping匹配到请求之后,调用HandlerAdapter具体处理请求。
- HandlerExceptionResolver:发生异常后的异常处理器。
- ViewResolver:处理返回
- LocaleResolver, LocaleContextResolver:本地化处理器
- ThemeResolver:Theme渲染处理器
- MultipartResolver:Multipart处理器,文件上传下载的处理。
- FlashMapManager:跨请求存储和获取“input”和“output”的处理器
Web MVC Config
DispatcherServlet初始化过程中会根据WebApplicationContext的配置(xml或注解方式,前面两篇文章分析过)完成上述特殊bean的初始化,如果DispatcherServlet在WebApplicationContext中没有发现相应的配置,则采用DispatcherServlet.properties文件中的默认配置完成初始化。
DispatcherServlet.properties文件在Spring web mvc包下:

我们猜想Spring MVC框架是通过DispatcherServlet的init方法完成上述各特殊bean的初始化的,下面我们要详细分析一下具体的初始化过程。
Servlet Config
通过注解方式、或通过xml方式初始化DispatcherServlet的具体方法,前面两篇文章已经做过分析,此处不在赘述。
DispatcherServlet的初始化
众所周知,Servlet容器(比如Tomcat)会通过调用Servlet的init方法完成Servlet的初始化。
我们接下来看一下DispatcherServlet的初始化过程,也就是DispatcherServlet的init方法。
先来看一眼DispatcherServlet的类结构:

init方法在他的父类HttpServletBean中:
@Overridepublic final void init() throws ServletException {// Set bean properties from init parameters.PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);if (!pvs.isEmpty()) {try {BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));initBeanWrapper(bw);bw.setPropertyValues(pvs, true);}catch (BeansException ex) {if (logger.isErrorEnabled()) {logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);}throw ex;}}// Let subclasses do whatever initialization they like.initServletBean();}
上面的代码是对当前Servlet属性的处理,与我们的目标无关,初始化逻辑在最下面的方法initServletBean中,在他的子类(也是DispatcherServlet的直接父类)FrameworkServlet中:
protected final void initServletBean() throws ServletException {...省略部分代码try {this.webApplicationContext = initWebApplicationContext();initFrameworkServlet();}catch (ServletException | RuntimeException ex) {logger.error("Context initialization failed", ex);throw ex;}
该方法中有很多打印log的代码,忽略掉,剩下的就是两个方法的调用:一个是创建webApplicationContext的,一个是initFrameworkServlet,这个initFrameworkServlet是空方法,所以,DispatcherServlet的初始化逻辑,关键就在这个initWebApplicationContext()方法中。
initWebApplicationContext方法很长,我们分段分析一下。
protected WebApplicationContext initWebApplicationContext() {WebApplicationContext rootContext =WebApplicationContextUtils.getWebApplicationContext(getServletContext());WebApplicationContext wac = null;...
首先获取当前ServletContext的RootContext,有关RootContext,参见前面的文章 Spring MVC 四:Context层级。
然后:
if (this.webApplicationContext != null) {// A context instance was injected at construction time -> use itwac = this.webApplicationContext;if (wac instanceof ConfigurableWebApplicationContext) {ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;if (!cwac.isActive()) {// The context has not yet been refreshed -> provide services such as// setting the parent context, setting the application context id, etcif (cwac.getParent() == null) {// The context instance was injected without an explicit parent -> set// the root application context (if any; may be null) as the parentcwac.setParent(rootContext);}configureAndRefreshWebApplicationContext(cwac);}}}
判断如果DispatcherServlet对象创建的时候,如果在构造方法中已经初始化过WebApplicationContext了,那么就使用该WebApplicationContext,设置上面获取到的RootContext为当前WebApplicationContext的父容器。并且判断该Context是否已经刷新过,如果没有刷新过的话,调用configureAndRefreshWebApplicationContext方法配置并刷新该Context。
前面文章Spring MVC 三 :基于注解配置中我们分析过DispatcherServlet的创建过程,确实在创建的时候就通过构造函数的参数传过来已经创建好的ServletContext了:
protected void registerDispatcherServlet(ServletContext servletContext) {String servletName = getServletName();Assert.hasLength(servletName, "getServletName() must not return null or empty");WebApplicationContext servletAppContext = createServletApplicationContext();Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());...省略代码
所以如果是通过注解方式配置的话,会通过createServletApplicationContext()方法创建ServletContext:
@Overrideprotected WebApplicationContext createServletApplicationContext() {AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();Class<?>[] configClasses = getServletConfigClasses();if (!ObjectUtils.isEmpty(configClasses)) {context.register(configClasses);}return context;}
最终创建的ServletContext是AnnotationConfigWebApplicationContext。
所以如果通过注解方式配置,那就是要走到上面这段逻辑中来的。
否则,如果不是通过注解、而是通过xml配置,也就是说DispactherServlet创建的时候并没有ServletContext,会走到下面的逻辑中:
if (wac == null) {// No context instance was injected at construction time -> 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 idwac = findWebApplicationContext();}if (wac == null) {// No context instance is defined for this servlet -> create a local onewac = createWebApplicationContext(rootContext);}
如果wac为空(DispatcherServlet创建的时候没有设置),那么就判断容器中是否已经注册进来了,如果已经注册了的话,那么Spring framework就会认为其父容器已经设置过了,也做过初始化以及refresh了,直接拿过来用就OK。(我们的应用如果不主动注册的话,就不会有注册进来的Context,所以这段代码就跑不到)。
然后看下面的代码,如果没有发现,就调用createWebApplicationContext创建,createWebApplicationContext方法在创建WebApplicationContext之后,也会设置其父容器为RootContext,之后也会调用configureAndRefreshWebApplicationContext配置和刷新容器,走到和上面第一步(通过注解方式配置,DispatcherServlet创建的时候已经通过构造器设置了一个Context)一致的逻辑中了。
createWebApplicationContext:
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {Class<?> contextClass = getContextClass();if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {throw new ApplicationContextException("Fatal initialization error in servlet with name '" + getServletName() +"': custom WebApplicationContext class [" + contextClass.getName() +"] is not of type ConfigurableWebApplicationContext");}ConfigurableWebApplicationContext wac =(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);wac.setEnvironment(getEnvironment());wac.setParent(parent);String configLocation = getContextConfigLocation();if (configLocation != null) {wac.setConfigLocation(configLocation);}configureAndRefreshWebApplicationContext(wac);return wac;}
首先调用getContextClass()方法获取contextClass:
public static final Class<?> DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;private Class<?> contextClass = DEFAULT_CONTEXT_CLASS;public Class<?> getContextClass() {return this.contextClass;}
可以看到,如果不是通过注解方式启动、而是通过xml配置方式启动的话,创建的ServletContext应该就是这个XmlWebApplicationContext。
创建ServletContext之后,与xml配置方式一样:设置父容器,然后调用configureAndRefreshWebApplicationContext方法配置及刷新容器。
接下来我们看configureAndRefreshWebApplicationContext方法。
configureAndRefreshWebApplicationContext
目前为止,我们前面的猜测:通过DispatcherServlet的init方法初始化各个特殊bean。尚未的到证实 — 在DispatcherServlet的init方法中,我们尚未看到相关的初始化代码。
不过代码还没分析完,还有一个configureAndRefreshWebApplicationContext,我们继续分析。
代码比较长,我们还是分段分析:
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {if (ObjectUtils.identityToString(wac).equals(wac.getId())) {// The application context id is still set to its original default value// -> assign a more useful id based on available informationif (this.contextId != null) {wac.setId(this.contextId);}else {// Generate default id...wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());}}
为WebApplicationContext设置Id,无关紧要,继续看下面的代码:
wac.setServletContext(getServletContext());wac.setServletConfig(getServletConfig());wac.setNamespace(getNamespace());wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
设置ServletContext、ServletConfig、以及namespace,之后新增了一个监听器:ContextRefreshListener()。
然后:
// The wac environment's #initPropertySources will be called in any case when the context// is refreshed; do it eagerly here to ensure servlet property sources are in place for// use in any post-processing or initialization that occurs below prior to #refreshConfigurableEnvironment env = wac.getEnvironment();if (env instanceof ConfigurableWebEnvironment) {((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());}postProcessWebApplicationContext(wac);applyInitializers(wac);wac.refresh();}
设置环境变量,以及获取初始化参数,最后调用WebApplicationContext的refresh方法。
依然没有看到DispatcherServlet对特殊bean的初始化!而且现在的代码逻辑是转到了ApplicationContext中,是Spring Framework的内容、并不是Spring MVC的内容。
别急,马上就要摸到开关了!
目前的代码确实是转悠到Spring Framework中来了。所以说Spring全家桶,不管是Spring MVC、还是SpringBoot、还是Spring Security,统统都是以Spring Framework为基础的。掌握Spring Framework是掌握Spring全家桶的基础。
ApplicationContext的refresh方法我们很熟悉了,是Spring Framework的关键方法,在AbstractApplicationContext类中实现,该方法最后会调用到finishRefresh()方法:

finishRefresh()方法最后会发布ContextRefreshedEvent事件。
没错,前面代码分析过程中,我们确实是在WebApplicationContext容器中注册了一个针对该事件的监听器ContextRefreshListener:
private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {@Overridepublic void onApplicationEvent(ContextRefreshedEvent event) {FrameworkServlet.this.onApplicationEvent(event);}}
该监听器是定义在FrameworkServlet中的一个内部类,其onApplicationEvent方法会调用到FrameworkServlet的onApplicationEvent方法,这样,通过监听机制,代码逻辑就再次转回到了DispatcherServlet(确切说是他的父类FrameworkServlet)中来了:
public void onApplicationEvent(ContextRefreshedEvent event) {this.refreshEventReceived = true;synchronized (this.onRefreshMonitor) {onRefresh(event.getApplicationContext());}}
最终会调用到DispatcherServlet中来:
@Overrideprotected void onRefresh(ApplicationContext context) {initStrategies(context);}
查看DispatcherServlet代码我们会发现,这个initStrategies正式我们要找的方法,方法参数Context是通过事件传递过来的,因此,DispatcherSerlet在进行初始化的时候可以持有ApplicationContext对象,然后,随心所欲地完成Spring MVC特殊bean的初始化。
篇幅原因,关于DispatcherServlet的具体初始化过程,我们后面分析。
上一篇 Spring MVC 四:Context层级
相关文章:
Spring MVC 五 - Spring MVC的配置和DispatcherServlet初始化过程
今天的内容是SpringMVC的初始化过程,其实也就是DispatcherServilet的初始化过程。 Special Bean Types DispatcherServlet委托如下一些特殊的bean来处理请求、并渲染正确的返回。这些特殊的bean是Spring MVC框架管理的bean、按照Spring框架的约定处理相关请求&…...
Ramp 有点意思的题目
粗一看都不知道这个要干什么,这 B 装得不错。 IyEvdXNyL2Jpbi9lbnYgcHl0aG9uMwoKJycnCktlZXAgdXMgb3V0IG9mIGdvb2dsZSBzZWFyY2ggcmVzdWx0cy4uCgokIG9kIC1kIC9kZXYvdXJhbmRvbSB8IGhlYWQKMDAwMDAwMCAgICAgNjAyMTUgICAyODc3OCAgIDI5MjI3ICAgMjg1NDggICA2MjY4NiAgIDQ1MT…...
算法通关村14关 | 堆在数组中找第k大的元素应用
1. 在数组中找第k大元素 题目 LeetCode215:给定整数数组nums和整数k,请返回数组中第k个最大的元素, 思路 解题思路用三个,选择法,堆查找和快速排序。 我们选择用大堆小堆解决问题,“找最大用小堆ÿ…...
Unity 顶点vertices,uv,与图片贴图,与mesh
mesh就是组成3d物体的三角形们。 mesh由顶点组成的三角形组成,三角形的大小 并不 需要一样,由顶点之间的位置决定。 mesh可以是一个或者多个面。 贴图的原点在左下角,uv是贴图的坐标,数量和顶点数一样(不是100%确定…...
Shell编程之函数
目录 基本概念 自定义函数 系统函数 1.read 2.basename 3.dirname 基本概念 将一段代码组合封装在一起实现某个特定的功能或返回某个特定的值,然后给这段代码取个名字,也就是函数名,在需要实现某个特定功能的时候直接调用函数名即可。 函…...
10.物联网LWIP之TCP状态转变
一。TCP状态机 1.青粗线:理想TCP状态转变(服务器视角下) 2.虚线:被动TCP状态转变(服务器视角下) 3.细实线:不经常出现的TCP状态转变(类似于边界处理) 1.青粗线解释--》服…...
Img标签的src地址自动拼接本地域名(localhost:8080)导致图片不显示问题
摘要:做Vueelement ui项目的时候,发现使用element ui的upload上传图片时,不显示的问题。我项目的图片是上传到七牛云,长传成功后返回存储在七牛云中的地址。后面发现是因为返回的地址是外部地址,需要完整的URL…...
数据结构入门 — 栈
本文属于数据结构专栏文章,适合数据结构入门者学习,涵盖数据结构基础的知识和内容体系,文章在介绍数据结构时会配合上动图演示,方便初学者在学习数据结构时理解和学习,了解数据结构系列专栏点击下方链接。 博客主页&am…...
Unity Android 之 在Unity 中引入 OkHttp的操作注意(OKHttp4.xx- kotlin 的包)简单记录
Unity Android 之 在Unity 中引入 OkHttp的操作注意(OKHttp4.xx- kotlin 的包)简单记录 目录 Unity Android 之 在Unity 中引入 OkHttp的操作注意(OKHttp4.xx- kotlin 的包)简单记录 一、简单介绍 二、OKHttp 4.xx 的 SDK 封装 aar 给 Unity 的使用注意 三、附录 OKHttp 的…...
内嵌功能强大、低功耗STM32WB55CEU7、STM32WB55CGU7 射频微控制器 - MCU, 48-UFQFN
一、概述: STM32WB55xx多协议无线和超低功耗器件内嵌功能强大的超低功耗无线电模块(符合蓝牙 低功耗SIG规范5.0和IEEE 802.15.4-2011标准)。该器件内含专用的Arm Cortex -M0,用于执行所有的底层实时操作。这些器件基于高性能Arm …...
【测试】笔试03
文章目录 1. 哪种测试模型把测试过程作为需求分析、概要设计、详细设计及编码之后的阶段( )2. 在下面所列举的逻辑测试覆盖中,测试覆盖最强的是?3. 网络管理员编写了shell程序prog1.sh,测试时程序死循环无法结束,可以通过下列方式…...
JavaScript的while和for循环
一、循环语句 1.认识循环 在开发中我们经常需要做各种各样的循环操作: 比如把一个列表中的商品、歌曲、视频依次输出进行展示;比如对一个列表进行累加计算;比如运行相同的代码将数字 1 到 10 逐个输出; 循环 是一种重复运行同…...
mqtt安卓客户端
1.MQTT(消息队列遥测传输协议),是一种基于 发布/订阅 (publish/subscribe)模式的"轻量级"通讯协议, 该协议构建于TCP/IP协议上 。MQTT最大优点在于,可以以极少的代码和有限的带宽&…...
pdf怎么删除其中一页?
pdf怎么删除其中一页?现在,pdf文件已经深入影响着我们的工作和学习,如果你是一个上班族,那么几乎每天都会使用到pdf格式的电脑文件。当我们阅读一个页数众多的PDF文件时,可能会发现实际上只需要其中的一小部分内容。很…...
10.Redis 渐进式遍历
Redis 渐进式遍历 渐进式遍历scan 渐进式遍历 keys 命令一次性的把整个redis中所有的key都获取到,keys *但这个操作比较危险,可能会一下子得到太多的key,阻塞 redis 服务器。 通过渐进式遍历,就可以做到,既可以获取到所有的 key&…...
字符函数和字符串函数(2)
目录 memcpy memmove memcmp memcpy void * memcpy ( void * destination, const void * source, size_t num ); 1.函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。 2.这个函数在遇到 \0 的时候并不会停下来。 3.如果source和destination有…...
目录扫描+JS文件中提取URL和子域+403状态绕过+指纹识别(dirsearch_bypass403)
dirsearch_bypass403 在安全测试时,安全测试人员信息收集中时可使用它进行目录枚举,目录进行指纹识别,枚举出来的403状态目录可尝试进行绕过,绕过403有可能获取管理员权限。不影响dirsearch原本功能使用 运行流程 dirsearch进行…...
【UE 材质】常用向量运算节点——点积、叉积、归一化
目录 一、点积 二、叉积 三、归一化 一、点积 点积,也称为内积或数量积,是一种用于计算两个向量之间关系的操作。对于两个三维向量 A(a1,a2,a3)和 B(b1,b2,b3),它们的点积可以用以下公式表示: ABa1⋅…...
音视频 ffmpeg命令提取PCM数据
提取PCM ffmpeg -i buweishui.mp3 -ar 48000 -ac 2 -f s16le 48000_2_s16le ffmpeg -i buweishui.mp3 -ar 48000 -ac 2 -sample_fmt s16 out_s16.wav ffmpeg -i buweishui.mp3 -ar 48000 -ac 2 -codec:a pcm_s16le out2_s16le.wav ffmpeg -i buweishui.mp3 -ar 48000 -ac 2 -f…...
【MySQL】实现可扩展性:构建高性能的系统
什么是可扩展性?可扩展性的好处扩展方式纵向扩展(Scaling Up)横向扩展(Scaling Out) 总结 💯感谢 💖 什么是可扩展性? 可扩展性是指系统能够在需要时轻松地适应更多的工作负载和资源…...
AI-调查研究-01-正念冥想有用吗?对健康的影响及科学指南
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...
CMake基础:构建流程详解
目录 1.CMake构建过程的基本流程 2.CMake构建的具体步骤 2.1.创建构建目录 2.2.使用 CMake 生成构建文件 2.3.编译和构建 2.4.清理构建文件 2.5.重新配置和构建 3.跨平台构建示例 4.工具链与交叉编译 5.CMake构建后的项目结构解析 5.1.CMake构建后的目录结构 5.2.构…...
《通信之道——从微积分到 5G》读书总结
第1章 绪 论 1.1 这是一本什么样的书 通信技术,说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号(调制) 把信息从信号中抽取出来&am…...
页面渲染流程与性能优化
页面渲染流程与性能优化详解(完整版) 一、现代浏览器渲染流程(详细说明) 1. 构建DOM树 浏览器接收到HTML文档后,会逐步解析并构建DOM(Document Object Model)树。具体过程如下: (…...
SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现
摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序,以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务,提供稳定高效的数据处理与业务逻辑支持;利用 uniapp 实现跨平台前…...
C++ 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...
Caliper 配置文件解析:config.yaml
Caliper 是一个区块链性能基准测试工具,用于评估不同区块链平台的性能。下面我将详细解释你提供的 fisco-bcos.json 文件结构,并说明它与 config.yaml 文件的关系。 fisco-bcos.json 文件解析 这个文件是针对 FISCO-BCOS 区块链网络的 Caliper 配置文件,主要包含以下几个部…...
浅谈不同二分算法的查找情况
二分算法原理比较简单,但是实际的算法模板却有很多,这一切都源于二分查找问题中的复杂情况和二分算法的边界处理,以下是博主对一些二分算法查找的情况分析。 需要说明的是,以下二分算法都是基于有序序列为升序有序的情况…...
图表类系列各种样式PPT模版分享
图标图表系列PPT模版,柱状图PPT模版,线状图PPT模版,折线图PPT模版,饼状图PPT模版,雷达图PPT模版,树状图PPT模版 图表类系列各种样式PPT模版分享:图表系列PPT模板https://pan.quark.cn/s/20d40aa…...
蓝桥杯3498 01串的熵
问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798, 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...
