文档章节

Spring源码分析 从web.xml 中ContextLoaderListener看Spring 容器与web容器联系

0909
 0909
发布于 2016/08/10 16:36
字数 1414
阅读 105
收藏 5
点赞 1
评论 0

Spring IOC容器如何与web容器建立联系,使得在web环境下能运用Spring 容器去管理对象,这要从web.xml配置文件中的ContextLoaderListener说起。它是Spring容器与web容器建立联系的入口,这里就先抛砖引玉啦。

先来看看web.xml配置文件中的

      <context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>
			classpath:spring/spring-context.xml,
			classpath:spring/spring-datasource.xml,
			classpath:spring/spring-context-shiro.xml
		</param-value>
	</context-param>
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

这个大家一字很熟悉,这便是Spring容器与web容器建立联系的入口。来看看ContextLoaderListener的源码

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
	public ContextLoaderListener() {
	}
	public ContextLoaderListener(WebApplicationContext context) {
		super(context);
	}
	@Override
	public void contextInitialized(ServletContextEvent event) {
		initWebApplicationContext(event.getServletContext());
	}
	@Override
	public void contextDestroyed(ServletContextEvent event) {
		closeWebApplicationContext(event.getServletContext());
		ContextCleanupListener.cleanupAttributes(event.getServletContext());
	}
}

可以看到其实现了ServletContextListener 接口,这样便可以从web.xml文件中加载ServletContext一些配置信息,兼听ServletContext上下文的变化情况。ServletContextListener 有两个方法contextInitialized 与contextDestroyed,contextInitialized 在容器启动时调用,contextDestroyed在容器关闭前调用。 可以看到在ContextLoaderListener中 contextInitialized 内部调用了父类ContextLoader 的initWebApplicationContext方法,这便是Spring容器初始化的入口。

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
	//判断是否已初始化过Spring容器,如果有抛出异常
	if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
			throw new IllegalStateException(
					"Cannot initialize context because there is already a root application context present - " +
					"check whether you have multiple ContextLoader* definitions in your web.xml!");
		}

		Log logger = LogFactory.getLog(ContextLoader.class);
		servletContext.log("Initializing Spring root WebApplicationContext");
		if (logger.isInfoEnabled()) {
			logger.info("Root WebApplicationContext: initialization started");
		}
		long startTime = System.currentTimeMillis();

		try {
			// Store context in local instance variable, to guarantee that
			// it is available on ServletContext shutdown.
			if (this.context == null) {
                               //创建Spring容器,默认情况下为XmlWebApplicationContext
				this.context = createWebApplicationContext(servletContext);
			}
			if (this.context instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
				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 ->
						// determine parent for root web application context, if any.
						ApplicationContext parent = loadParentContext(servletContext);
						cwac.setParent(parent);
					}
                                        //配置Spring容器,加载servletContext的一些配置文件
					configureAndRefreshWebApplicationContext(cwac, servletContext);
				}
			}
//将WebApplicationContext对象保存在ServletContext上下文中以便使用
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

			ClassLoader ccl = Thread.currentThread().getContextClassLoader();
			if (ccl == ContextLoader.class.getClassLoader()) {
				currentContext = this.context;
			}
			else if (ccl != null) {
				currentContextPerThread.put(ccl, this.context);
			}

			if (logger.isDebugEnabled()) {
				logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
						WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
			}
			if (logger.isInfoEnabled()) {
				long elapsedTime = System.currentTimeMillis() - startTime;
				logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
			}

			return this.context;
		}

这里忽视一些细节,重点看看initWebApplicationContext方法中

  1. this.context = createWebApplicationContext(servletContext);
  2. configureAndRefreshWebApplicationContext(cwac, servletContext); 3.servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

createWebApplicationContext //创建Spring容器,默认情况下为XmlWebApplicationContext this.context = createWebApplicationContext(servletContext) context是ContextLoader的成员变量,其类型为WebApplicationContext,WebApplicationContext续承ApplicationContext为Spring的容器。 再来看看源码

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
	//采用什么类型的FactoryBean创建Spring 容器
Class<?> contextClass = determineContextClass(sc);
		if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
			throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
					"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
		}
		return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
	}

再看determineContextClass源码

protected Class<?> determineContextClass(ServletContext servletContext) {
		String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
		if (contextClassName != null) {
			try {
				return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
			}
			catch (ClassNotFoundException ex) {
				throw new ApplicationContextException(
						"Failed to load custom context class [" + contextClassName + "]", ex);
			}
		}
		else {
			contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
			try {
				return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
			}
			catch (ClassNotFoundException ex) {
				throw new ApplicationContextException(
						"Failed to load default context class [" + contextClassName + "]", ex);
			}
		}
	}

默认情况下在web.xml中不会设置SevletContext的contextClass参数,便根据defaultStrategies.getProperty(WebApplicationContext.class.getName())查找Spring容器对应的实现类。 defaultStrategies为ContextLoader的类型为Properties的静态成员变量,ContextLoader内有一个静态初始化块对其进行初始化。

static {
		// Load default strategy implementations from properties file.
		// This is currently strictly internal and not meant to be customized
		// by application developers.
		try {
			ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
			defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
		}
		catch (IOException ex) {
			throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
		}
	}
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class)

这一行代码可以看出,初始化块将找DEFAULT_STRATEGIES_PATH这个路径的文件,实际上是ClassPathResource将到ContextLoader包对应的路径下面找到ContextLoader.properties文件。 输入图片说明

再打ContextLoader.properties看看其内容: 输入图片说明

可以看到前面说的Spring容器的默认实现类XmlWebApplicationContext,最后这个通过反射createWebApplicationContext将返回XmlWebApplicationContext的对象给ContextLoader的成员变量context。 到这里**1. this.context = createWebApplicationContext(servletContext);**分析结束。

再来看看**2. configureAndRefreshWebApplicationContext(cwac, servletContext);**的分析 configureAndRefreshWebApplicationContext源码:

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
		if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
			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()));
			}
		}
		wac.setServletContext(sc);
                //得到web.xml中ServletContext参数contextConfigLocation对应的配置信息
		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);
		}
		customizeContext(sc, wac);
                //Spring容器真正初始化的地方
		wac.refresh();
	}
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);

这行代码将得到在web.xml中的Spring配置文件的信息,就是一开始 的web.xml的这部分

 <context-param>
 		<param-name>contextConfigLocation</param-name>
		<param-value>
			classpath:spring/spring-context.xml,
			classpath:spring/spring-datasource.xml,
			classpath:spring/spring-context-shiro.xml
		</param-value>
	</context-param>

再来分析一下**wac.setConfigLocation(configLocationParam)**这行代码 从上面的分析知道默认情况下wac为XmlWebApplicationContext类型的对象。 在XmlWebApplicationContext的父类AbstractRefreshableWebApplicationContext实现了ConfigurableWebApplicationContext接口,而另一方面AbstractRefreshableWebApplicationContext又续承了AbstractRefreshableConfigApplicationContext类。AbstractRefreshableConfigApplicationContext类里面实现了setConfigLocation方法。

public void setConfigLocation(String location) {
		setConfigLocations(StringUtils.tokenizeToStringArray(location, CONFIG_LOCATION_DELIMITERS));
	}

String CONFIG_LOCATION_DELIMITERS = ",; \t\n"; 从这里可以看出,在web.xml里面设置contextConfigLocation的param-value时,可以用",; \t\n"来分隔多个配置文件。

public void setConfigLocations(String... locations) {
		if (locations != null) {
			Assert.noNullElements(locations, "Config locations must not be null");
			this.configLocations = new String[locations.length];
			for (int i = 0; i < locations.length; i++) {
				this.configLocations[i] = resolvePath(locations[i]).trim();
			}
		}
		else {
			this.configLocations = null;
		}
	}

再回到configureAndRefreshWebApplicationContext方法中wac.refresh()这一行,这才是Spring容器真正初始化的地方,由于后面涉及的东西比较多将在后面专门分析一下。 到这里2. configureAndRefreshWebApplicationContext(cwac, servletContext);的分析结束。 再来看看3.servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context); 这个比较简单就是将上面用反射方法创建的XmlWebApplicationContext对象保存在ServletContext上下文中。由于本人水平有限不正确的地方,欢迎大家指正。

© 著作权归作者所有

共有 人打赏支持
0909
粉丝 22
博文 16
码字总数 25047
作品 0
无锡
spring mvc 配置失效了?

版本:spring 3.0以上 项目中有两个spring的配置xml,如下 项目中的web.xml web.xml的配置如下: 1、ContextLoaderListener加载applicationContext-service-database.xml 2、DispatcherServl...

听柳 ⋅ 05/31 ⋅ 0

spring4.0之二:@Configuration的使用

从Spring3.0,@Configuration用于定义配置类,可替换xml配置文件,被注解的类内部包含有一个或多个被@Bean注解的方法,这些方法将会被AnnotationConfigApplicationContext或AnnotationConfi...

文文1 ⋅ 06/01 ⋅ 0

Spring mvc ContextLoaderListener 原理解析

对于熟悉Spring MVC功能,首先应从web.xml 开始,在web.xml 文件中我们需要配置一个监听器 ContextLoaderListener,如下。 ContextLoaderListener有什么用?提供什么功能呢?我们下面通过源码...

轨迹_ ⋅ 06/22 ⋅ 0

Spring Boot整合模板引擎jsp

jsp也算是一种模板引擎吧。整合jsp前,先说一下运行SpringBoot项目的几种方式 1. 运行SpringBoot项目的几种方式 1.1 使用内嵌Tomcat运行项目 在IDE中右键运行启动类,也就是直接直接运行App...

yysue ⋅ 06/15 ⋅ 0

Spring Boot干货系列:(五)开发Web应用JSP篇

        前言   上一篇介绍了Spring Boot中使用Thymeleaf模板引擎,今天来介绍一下如何使用SpringBoot官方不推荐的jsp,虽然难度有点大,但是玩起来还是蛮有意思的。   正文   先...

后端编程嘟 ⋅ 2017/03/14 ⋅ 0

springboot 是如何帮我们省去web.xml配置的

概述 最开始使用原生的springmvc时,总是免不了有如下xml配置 但是,切换到springboot之后,web.xml之类的繁琐的配置基本上都不见了。出于好奇研究了下springboot究竟帮我们做了什么,我们可...

端吉 ⋅ 前天 ⋅ 0

springboot学习(二)——springmvc配置使用

以下内容,如有问题,烦请指出,谢谢 上一篇讲解了springboot的helloworld部分,这一篇开始讲解如何使用springboot进行实际的应用开发,基本上寻着spring应用的路子来讲,从springmvc以及web...

嘻哈开发者 ⋅ 04/26 ⋅ 0

Spring Framework体系结构简介

说明:以下转自Spring官方文档,用的版本为4.3.11版本。 一、引用官方文档 所述核心容器由以下部分组成, ,,,和(弹簧表达式语言)模块。 的和模块提供框架的基本零件,包括IOC和依赖注入...

easonjim ⋅ 2017/09/16 ⋅ 0

Spring mvc DispatchServlet 实现原理

在Spring中, ContextLoaderListener只是辅助类,在web 容器启动的时候查找并创建WebApplicationContext对象,通过该对象进行加载spring的配置文件。而真正的逻辑实现其实是在DispatcherSer...

轨迹_ ⋅ 前天 ⋅ 0

微服务架构之深入浅出SpringMVC

为开发团队选择一款优秀的MVC框架是件难事儿,在众多可行的方案中决择需要很高的经验和水平。你的一个决定会影响团队未来的几年。要考虑方面太多: 1、简单易用,以提高开发效率。使小部分的...

qq5acb179de594e ⋅ 04/14 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

数据结构与算法之双向链表

一、双向链表 1.双向链表的结点结构 typedef struct DualNode{ ElemType data; struct DualNode *prior; // 前驱结点 struct DualNode *next; // 后继结点}DualNode, *DuL...

aibinxiao ⋅ 22分钟前 ⋅ 0

五大最核心的大数据技术

大数据技术有5个核心部分,数据采集、数据存储、数据清洗、数据挖掘、数据可视化。关于这5个部分,有哪些核心技术?这些技术有哪些潜在价值?看完今天的文章就知道了。 大数据学习群:7165810...

董黎明 ⋅ 23分钟前 ⋅ 0

PhpStorm 头部注释、类注释和函数注释的设置

首先,PhpStorm中文件、类、函数等注释的设置在:setting-》Editor-》FIle and Code Template-》Includes下设置即可,其中方法的默认是这样的: /**${PARAM_DOC}#if (${TYPE_HINT} != "v...

nsns ⋅ 23分钟前 ⋅ 0

spring.net AOP

http://www.springframework.net/doc-latest/reference/html/aop-quickstart.html https://www.cnblogs.com/wujy/archive/2013/04/06/3003120.html...

whoisliang ⋅ 28分钟前 ⋅ 0

【HAVENT原创】创建 Dockerfile 生成新的镜像,并发布到 DockerHub

注意:Win7 与 Win10 的版本存在差异,Win7 版本使用 Docker Quickstart Terminal 进入控制台,Win10下面直接用管理员权限打开控制台或者 PowerShell 即可;另外 Win7 下面只能访问 C盘,/ap...

HAVENT ⋅ 28分钟前 ⋅ 0

pom.xml出现web.xml is missing ...解决方案

提示信息应该能看懂。也就是缺少了web.xml文件,<failOnMissingWebXml>被设置成true了。 搜索了一下,Stack Overflow上的答案解决了问题,分享一下。 目前被顶次数最多的回答原文如下: This...

源哥L ⋅ 29分钟前 ⋅ 0

js时间戳与日期格式之间相互转换

1. 将时间戳转换成日期格式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 // 简单的一句代码 var date = new Date(时间戳); //获取一个时间对象 /** 1. 下面是获取时间日期的方法,需要什么样的格式自己...

Jack088 ⋅ 34分钟前 ⋅ 0

web添加log4j

添加xml配置log4j.properties # Global logging configuration---root日志设置#log4j.rootLogger=info,dailyRollingFile,stdoutlog4j.rootLogger=debug,stdout,dailyRollingFile---......

黄柳淞 ⋅ 35分钟前 ⋅ 0

如何解决生产环境宕机问题

写在前面:该篇宕机问题的排查难度远比上一篇(记一次解决线上OOM的心路历程)大的太多,上一篇中内存泄漏的问题是有迹可循的,本次的宕机在业务日志上没有任何征兆,另外本文有许多值得深挖...

寰宇01 ⋅ 47分钟前 ⋅ 0

Redis查漏补缺:最易错过的技术要点大扫盲

作者介绍 孤独烟,中国平安研发工程师,目前负责规则云平台架构设计以及需求研发工作。毕业后一直从事Java开发工作,在Web开发、架构设计上有多年的实战经验。在MySQL性能优化、JVM调优、分布...

kim_o ⋅ 48分钟前 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部