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) 总结 💯感谢 💖 什么是可扩展性? 可扩展性是指系统能够在需要时轻松地适应更多的工作负载和资源…...
云计算——弹性云计算器(ECS)
弹性云服务器:ECS 概述 云计算重构了ICT系统,云计算平台厂商推出使得厂家能够主要关注应用管理而非平台管理的云平台,包含如下主要概念。 ECS(Elastic Cloud Server):即弹性云服务器,是云计算…...
盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来
一、破局:PCB行业的时代之问 在数字经济蓬勃发展的浪潮中,PCB(印制电路板)作为 “电子产品之母”,其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透,PCB行业面临着前所未有的挑战与机遇。产品迭代…...
《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》
引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...
Mybatis逆向工程,动态创建实体类、条件扩展类、Mapper接口、Mapper.xml映射文件
今天呢,博主的学习进度也是步入了Java Mybatis 框架,目前正在逐步杨帆旗航。 那么接下来就给大家出一期有关 Mybatis 逆向工程的教学,希望能对大家有所帮助,也特别欢迎大家指点不足之处,小生很乐意接受正确的建议&…...
大语言模型如何处理长文本?常用文本分割技术详解
为什么需要文本分割? 引言:为什么需要文本分割?一、基础文本分割方法1. 按段落分割(Paragraph Splitting)2. 按句子分割(Sentence Splitting)二、高级文本分割策略3. 重叠分割(Sliding Window)4. 递归分割(Recursive Splitting)三、生产级工具推荐5. 使用LangChain的…...
ios苹果系统,js 滑动屏幕、锚定无效
现象:window.addEventListener监听touch无效,划不动屏幕,但是代码逻辑都有执行到。 scrollIntoView也无效。 原因:这是因为 iOS 的触摸事件处理机制和 touch-action: none 的设置有关。ios有太多得交互动作,从而会影响…...
【JavaWeb】Docker项目部署
引言 之前学习了Linux操作系统的常见命令,在Linux上安装软件,以及如何在Linux上部署一个单体项目,大多数同学都会有相同的感受,那就是麻烦。 核心体现在三点: 命令太多了,记不住 软件安装包名字复杂&…...
什么是Ansible Jinja2
理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具,可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板,允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板,并通…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
OPENCV形态学基础之二腐蚀
一.腐蚀的原理 (图1) 数学表达式:dst(x,y) erode(src(x,y)) min(x,y)src(xx,yy) 腐蚀也是图像形态学的基本功能之一,腐蚀跟膨胀属于反向操作,膨胀是把图像图像变大,而腐蚀就是把图像变小。腐蚀后的图像变小变暗淡。 腐蚀…...
