【Spring MVC】Spring MVC启动过程源码分析_springmvc启动过程-程序员宅基地

技术标签: init  spring mvc  web  DispatcherServlet  J2SE  Servlet  

一、Servlet开场

    servlet现在已经算是很基础的内容了,如果还没有看过、分析过servlet的源码,可能就要out了。

    servlet的生命周期通过三个方法init、service、destory来构建,这些方法不用你调用,servlet容器会自动调用。servlet接口里面定义了这些规范,但是在接口实现的抽象类——无协议通用的GenericServelt——中有一些小改动,重载了一个不包含参数的init方法,并且建议你在开发自己的servlet的时候,重写这个不包含参数的方法,GenericServlet是Servlet接口的抽象子类,他并没有实现service方法,而是将这个抽象方法的实现放到子类中完成;再看下一层实现了HTTP协议的子类HttpServlet,他实现了这个public的service方法,但是还重载了一个protected的service方法。

    到这里,Spring MVC启动过程中很多有意思的东西就可以渐渐浮出水面了,Spring MVC的启动,依靠DispatcherServelt,这货继承了HttpServlet。

    1. 关于运行:如果根据jsp/servlet的开发规范来看,他应该重写protected service模板方法中给出的doXxx方法,否则调用抛出异常,但是用过Spring MVC之后,你发现并不是这样的,那么在HttpServlet中public service转调protected service的逻辑必然被重写了(没有必要重写public service的逻辑,public service将无协议转成了HTTP协议),至于为什么重写的是protected的service方法而不是public的,回顾重写的规则再看看Spring MVC servlet层级中的FramworkServlet源码,两相验证即可;

    2. 关于启动:使用Spring MVC,只需要配置DispatcherServlet即可,可以不需要再配置Spring web包中的ContextLoaderListner(配置或不配置都可以,Spring MVC源码逻辑中有相关部分的体现,源码会在后续分析中给出),那么Spring容器在哪里启动呢?这一切必然和Spring MVC重写GenericServlet无参的init方法有关;

    3. 关于销毁:通过Spring web包的ContextLoaderListener将Spring接入web,可以知道当容器销毁的时候需要将Spring的东西从servlet容器中清理掉,既然Spring MVC可以不配置ContextLoaderListner,那么必然要在destory方法中做些什么来释放资源等等;


    DispatcherServlet终究还是一个servlet,因此以servlet的视角来审视他再合理不过了。


二、启动过程

1. 铺垫

    a. 当servlet实例被创建以后,servlet容器会首先调用其init方法;

    b. Spring MVC中servlet的继承体系:DispatcherServlet extends FramworkServlet extends HttpServletBean extends HttpServlet extends GenericServlet implements Servlet;

2. 启动过程简要分析


    转到HttpServletBean中,查看init方法。



    到FraworkServlet中才渐渐接近容器启动的核心方法,其实从servlet的取名就能看出,FramworkServlet类,包含了整个框架的启动逻辑。

protected WebApplicationContext initWebApplicationContext() {
	// 1-------------
	// 如果配置了ContextLoaderListener,这里就能够拿到XmlWebApplicationContext的实例
	// 也就表明,Spring容器随着ServletContext的启动已经启动过了
	WebApplicationContext rootContext =
			WebApplicationContextUtils.getWebApplicationContext(getServletContext());
	// 2-------------
	// 对于属性webApplicationContext为不为null,我尝试追了一下源码,启动中这个属性应该是位null的
	WebApplicationContext wac = null;
	if (this.webApplicationContext != null) {
		// A context instance was injected at construction time -> use it
		wac = 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, etc
				if (cwac.getParent() == null) {
					// The context instance was injected without an explicit parent -> set
					// the root application context (if any; may be null) as the parent
					cwac.setParent(rootContext);
				}
				configureAndRefreshWebApplicationContext(cwac);
			}
		}
	}
	// 3-------------
	// 从WebApplicationContextUtils工具类获取applicationcontext,和方法第一句代码一样
	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 id
		wac = findWebApplicationContext();
	}
	// 4-------------
	// 上述找了一轮,没找到,那么就要开始创建了
	if (wac == null) {
		// No context instance is defined for this servlet -> create a local one
		wac = createWebApplicationContext(rootContext);
	}
	// 5-------------
	// 到这里,才是Spring MVC的初始化过程,此时会跳转到DispatcherServlet来进行
	// 处理器映射器、处理器适配器、试图解析器等等Spring MVC组件都是在这里完成初始化
	if (!this.refreshEventReceived) {
		// Either the context is not a ConfigurableApplicationContext with refresh
		// support or the context injected at construction time had already been
		// refreshed -> trigger initial onRefresh manually here.
		onRefresh(wac);
	}

	if (this.publishContext) {
		// Publish the context as a servlet context attribute.
		String attrName = getServletContextAttributeName();
		getServletContext().setAttribute(attrName, wac);
		if (this.logger.isDebugEnabled()) {
			this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
					"' as ServletContext attribute with name [" + attrName + "]");
		}
	}

	return wac;
}
    如贴出的代码,1~4步都是Spring容器初始化的过程,直到第五步,才是Spring MVC组件初始化的过程。

    第一步,正如之前所说,Spring通过Spring-web包接入web,实际上是配置ContextLoaderListener来使得Spring容器随着web容器的启动而启动,而使用Spring MVC之后,是否还需要配置ContextLoaderListener呢?

    对于Spring接入web的源码和原理分析,建议先看一看,也是一个很有意思的过程,参看点击打开链接点击打开链接

    对于是否需要配置ContextLoaderListener,实际上配不配都可以,配置的话关联一下能用,不配置的话第四步会创建,两种方式实现的差异在于如果没有配置ContextLoadlistener,那么就在第四步中创建一个用;如果配置了ContextLoaderListener,那么就用ContextLoaderListener方式创建出来的ApplicationContext。当然,这是在一般情况下,也就是FrameworkServlet的属性this.webApplicationContext没有被注入的情况下。要追踪this.webApplication是否被注入,从对象初始化的角度来追查,在web.xml中配置的DispatcherServlet的创建,用的是默认的构造器(自己写个类继承一下然后配置可验证),也就是说在大多数场景下this.webApplicationContext不会在构造的时候被注入。实际上这一段的逻辑我自己也没有完全走通,代码做什么很简单,但是为什么这么做的逻辑没有看明白。通过以下列表来罗列可能出现的情形并追踪,可以知道配置与否对系统造成的影响(一般场景是第二和第四列的内容)。

是否配置ContextLoaderListener 是否注入this.webApplictcationContext 此时DispatcherServlet中ApplicationContext的情况
是,则rootContext有值 是,则wac有值 rootContext成为wac的parent
是,则rootContext有值 否,wac=null rootContext成为wac的parent
否,rootContext=null 是,则wac有值 使用wac
否,rootContext=null 否,wac=null 创建并使用wac

    第二步,判断WebApplicationContext webApplicationContext是否为null,不为null,则会在做一些设置之后重启容器,wac.refresh()。实际上,通过配置DispatcherServlet来看,使用的是DispatcherServlet默认构造方法,是不需要传递参数,改构造功能方法中默认super(),调用FrameworkServlet的默认构造方法,改默认构造不会实例化这个属性,因此,这个属性还是为null。参看下图,整个FramworkServlet中使用到该属性的代码。


    无论是否配置ContextLoaderListener,都不会通过set方法或构造函数注入该属性,因此第二步的逻辑似乎永远不会被执行,我也没想到相关的场景能触发这一段逻辑的执行,没想明白这一段逻辑的触发,有大神能解答,感激不敬。

    关于这一段逻辑,和ContextLoaderListener中极度相似,简直就是copy and paste,只是我没能找到触发的场景,即this.webApplicationContext不为null。

    第三步,这一部的逻辑和第一部一个造型,都是从WebApplicationContextUtils中获取wac,和第二步的关系承上启下。


    第四步,正常情况会走这一步的,这段代码的逻辑是这样的,wac=createWebApplicationContext(null);,FramworkServlet中对象初始化的时候,就完成了contextClass=XmlWebApplicationContext.class,除非你在web.xml中配置DispatcherServlet时另行平直了contextClass,否则默认会通过反射来实例化XmlWebApplicationContext实例,然后再现实的调用refresh方法启动Spring容器。


    进入configureAndRefreshWebApplicationContext方法,第一感觉就是粘贴复制,和ContextLoaderListener的代码一毛一样,最主要的是显式的调用了refresh来启动或刷新Spring容器。

    第五步,这一步,才是所谓的Spring MVC的主要逻辑,跳转到DispatcherServlet中执行。


    这里就是正式的初始化Spring MVC的各个组件,在此之前,DispatcherServlet有一个静态代码块,加载了Spring MVC中组件的初始化策略,也就是在initStrategies方法中,除了initMultipartResolver没有默认策略之外,其他八个都有默认的初始化策略,而对于initMultipartResolver,需要显示的配置对应的bean来支持文件上传。


    Spring MVC默认的初始化策略,即DispatcherServlet.properties的内容。

# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
	org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
	org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
	org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter

org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
	org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
	org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

    到这里,基本已经能用了,但是还有最后一步,就是要将创建出来的XmlWebApplicationContext放置到全应用都可以访问的地方,即ServletContext,但是这里存放的key和ContextLoadListener中存放的key是不一样的,因此如果没有配置ContextLoaderListener方式启动Servlet容器,那么通过WebApplicationContextUtils是拿不到东西的,但是可以直接从ServletContext中获取。


    至此,无论是Spring容器还是Spring MVC组件都已经全部启动完毕,整个应用可以对外提供服务了。

三、销毁


    销毁方法最终调用applicationcontext的close逻辑进行Spring容器的销毁,这里就不再介绍了。


四、补充

    如何拿到ApplicationContext对象。

    方式一:如果只配置了DispatcherServlet,则可以先获取ServletContext,然后通过key=FrameworkServlet.SERVLET_CONTEXT_PREFIX+"DispatcherServlet在web.xml在配置的名字";

    方式二:如果既配置了ContextLoaderListener,又配置了DispatcherServlet,表现层的ApplicationContext通过方式一可以获取,业务层的ApplicationContext(即表现层Context的parent),可以通过WebApplicationContextUtils.getWebApplicationContext(getServletContext());获取;

    方式三:request.getAttribute(DispatcherServelt.WEB_APPLICATION_CONTEXT_ATTRIBUTE);,不过该方法只能获取表现层的,获取到ApplicationContext之后,也可以获取业务层的parent;


附注:

    本文如有错漏,烦请不吝指正,谢谢!

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/reliveIT/article/details/48209005

智能推荐

分布式光纤传感器的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告_预计2026年中国分布式传感器市场规模有多大-程序员宅基地

文章浏览阅读3.2k次。本文研究全球与中国市场分布式光纤传感器的发展现状及未来发展趋势,分别从生产和消费的角度分析分布式光纤传感器的主要生产地区、主要消费地区以及主要的生产商。重点分析全球与中国市场的主要厂商产品特点、产品规格、不同规格产品的价格、产量、产值及全球和中国市场主要生产商的市场份额。主要生产商包括:FISO TechnologiesBrugg KabelSensor HighwayOmnisensAFL GlobalQinetiQ GroupLockheed MartinOSENSA Innovati_预计2026年中国分布式传感器市场规模有多大

07_08 常用组合逻辑电路结构——为IC设计的延时估计铺垫_基4布斯算法代码-程序员宅基地

文章浏览阅读1.1k次,点赞2次,收藏12次。常用组合逻辑电路结构——为IC设计的延时估计铺垫学习目的:估计模块间的delay,确保写的代码的timing 综合能给到多少HZ,以满足需求!_基4布斯算法代码

OpenAI Manager助手(基于SpringBoot和Vue)_chatgpt网页版-程序员宅基地

文章浏览阅读3.3k次,点赞3次,收藏5次。OpenAI Manager助手(基于SpringBoot和Vue)_chatgpt网页版

关于美国计算机奥赛USACO,你想知道的都在这_usaco可以多次提交吗-程序员宅基地

文章浏览阅读2.2k次。USACO自1992年举办,到目前为止已经举办了27届,目的是为了帮助美国信息学国家队选拔IOI的队员,目前逐渐发展为全球热门的线上赛事,成为美国大学申请条件下,含金量相当高的官方竞赛。USACO的比赛成绩可以助力计算机专业留学,越来越多的学生进入了康奈尔,麻省理工,普林斯顿,哈佛和耶鲁等大学,这些同学的共同点是他们都参加了美国计算机科学竞赛(USACO),并且取得过非常好的成绩。适合参赛人群USACO适合国内在读学生有意向申请美国大学的或者想锻炼自己编程能力的同学,高三学生也可以参加12月的第_usaco可以多次提交吗

MySQL存储过程和自定义函数_mysql自定义函数和存储过程-程序员宅基地

文章浏览阅读394次。1.1 存储程序1.2 创建存储过程1.3 创建自定义函数1.3.1 示例1.4 自定义函数和存储过程的区别1.5 变量的使用1.6 定义条件和处理程序1.6.1 定义条件1.6.1.1 示例1.6.2 定义处理程序1.6.2.1 示例1.7 光标的使用1.7.1 声明光标1.7.2 打开光标1.7.3 使用光标1.7.4 关闭光标1.8 流程控制的使用1.8.1 IF语句1.8.2 CASE语句1.8.3 LOOP语句1.8.4 LEAVE语句1.8.5 ITERATE语句1.8.6 REPEAT语句。_mysql自定义函数和存储过程

半导体基础知识与PN结_本征半导体电流为0-程序员宅基地

文章浏览阅读188次。半导体二极管——集成电路最小组成单元。_本征半导体电流为0

随便推点

【Unity3d Shader】水面和岩浆效果_unity 岩浆shader-程序员宅基地

文章浏览阅读2.8k次,点赞3次,收藏18次。游戏水面特效实现方式太多。咱们这边介绍的是一最简单的UV动画(无顶点位移),整个mesh由4个顶点构成。实现了水面效果(左图),不动代码稍微修改下参数和贴图可以实现岩浆效果(右图)。有要思路是1,uv按时间去做正弦波移动2,在1的基础上加个凹凸图混合uv3,在1、2的基础上加个水流方向4,加上对雾效的支持,如没必要请自行删除雾效代码(把包含fog的几行代码删除)S..._unity 岩浆shader

广义线性模型——Logistic回归模型(1)_广义线性回归模型-程序员宅基地

文章浏览阅读5k次。广义线性模型是线性模型的扩展,它通过连接函数建立响应变量的数学期望值与线性组合的预测变量之间的关系。广义线性模型拟合的形式为:其中g(μY)是条件均值的函数(称为连接函数)。另外,你可放松Y为正态分布的假设,改为Y 服从指数分布族中的一种分布即可。设定好连接函数和概率分布后,便可以通过最大似然估计的多次迭代推导出各参数值。在大部分情况下,线性模型就可以通过一系列连续型或类别型预测变量来预测正态分布的响应变量的工作。但是,有时候我们要进行非正态因变量的分析,例如:(1)类别型.._广义线性回归模型

HTML+CSS大作业 环境网页设计与实现(垃圾分类) web前端开发技术 web课程设计 网页规划与设计_垃圾分类网页设计目标怎么写-程序员宅基地

文章浏览阅读69次。环境保护、 保护地球、 校园环保、垃圾分类、绿色家园、等网站的设计与制作。 总结了一些学生网页制作的经验:一般的网页需要融入以下知识点:div+css布局、浮动、定位、高级css、表格、表单及验证、js轮播图、音频 视频 Flash的应用、ul li、下拉导航栏、鼠标划过效果等知识点,网页的风格主题也很全面:如爱好、风景、校园、美食、动漫、游戏、咖啡、音乐、家乡、电影、名人、商城以及个人主页等主题,学生、新手可参考下方页面的布局和设计和HTML源码(有用点赞△) 一套A+的网_垃圾分类网页设计目标怎么写

C# .Net 发布后,把dll全部放在一个文件夹中,让软件目录更整洁_.net dll 全局目录-程序员宅基地

文章浏览阅读614次,点赞7次,收藏11次。之前找到一个修改 exe 中 DLL地址 的方法, 不太好使,虽然能正确启动, 但无法改变 exe 的工作目录,这就影响了.Net 中很多获取 exe 执行目录来拼接的地址 ( 相对路径 ),比如 wwwroot 和 代码中相对目录还有一些复制到目录的普通文件 等等,它们的地址都会指向原来 exe 的目录, 而不是自定义的 “lib” 目录,根本原因就是没有修改 exe 的工作目录这次来搞一个启动程序,把 .net 的所有东西都放在一个文件夹,在文件夹同级的目录制作一个 exe._.net dll 全局目录

BRIEF特征点描述算法_breif description calculation 特征点-程序员宅基地

文章浏览阅读1.5k次。本文为转载,原博客地址:http://blog.csdn.net/hujingshuang/article/details/46910259简介 BRIEF是2010年的一篇名为《BRIEF:Binary Robust Independent Elementary Features》的文章中提出,BRIEF是对已检测到的特征点进行描述,它是一种二进制编码的描述子,摈弃了利用区域灰度..._breif description calculation 特征点

房屋租赁管理系统的设计和实现,SpringBoot计算机毕业设计论文_基于spring boot的房屋租赁系统论文-程序员宅基地

文章浏览阅读4.1k次,点赞21次,收藏79次。本文是《基于SpringBoot的房屋租赁管理系统》的配套原创说明文档,可以给应届毕业生提供格式撰写参考,也可以给开发类似系统的朋友们提供功能业务设计思路。_基于spring boot的房屋租赁系统论文