`

小读spring ioc源码(二)——ContextLoaderListener

阅读更多
实际开发中,比较多的项目是web项目,这时候加载spring,是在web.xml里配置一个Listener
<listener>
    	<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

这个ContextLoaderListener就是web应用中,加载spring的入口

如果不是web应用,一般通过实例化一个ApplicationContext的子类来加载spring。过程是大同小异的,这部分在后面的博客里再说,不是这篇的重点

接下来就从ContextLoaderListener说起:

入口是contextInitialized方法
public void contextInitialized(ServletContextEvent event) {
		this.contextLoader = createContextLoader();
		if (this.contextLoader == null) {
			this.contextLoader = this;
		}
		this.contextLoader.initWebApplicationContext(event.getServletContext());
	}

这里面的createContextLoader()是一个@Deprecated方法,直接返回null。这里ContextLoaderListener是继承自ContextLoader,所以有一句看起来比较奇怪的代码
this.contextLoader = this;

为什么这么做的原因,我想可能是这样的话,就可以把大部分的功能代码移到ContextLoader里,可以保持ContextLoaderListener自身的代码比较简单

然后接下来关键的代码
this.contextLoader.initWebApplicationContext(event.getServletContext());

这个initWebApplicationContext()方法是在ContextLoader里定义的
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
		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) {
				this.context = createWebApplicationContext(servletContext);
			}
			if (this.context instanceof ConfigurableWebApplicationContext) {
				configureAndRefreshWebApplicationContext((ConfigurableWebApplicationContext)this.context, 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;
		}
		catch (RuntimeException ex) {
			logger.error("Context initialization failed", ex);
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
			throw ex;
		}
		catch (Error err) {
			logger.error("Context initialization failed", err);
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
			throw err;
		}
	}

比较长,可以分解开来看,首先是:
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();

首先检查是不是已经注册了ApplicationContext作为ServletContext的一个属性,如果是的话,就抛出异常,避免重复注册

然后获取日志组件,这里其实有个问题,就是Common Logging实际上比较老,现在很多开源框架,都改用slf4j作为日志组件的facade,但是spring这里把它写死了,所以就很难无缝地引入slf4j了

然后记录了启动日志,并记录开始启动的时间

接下来就是本方法的实体,省略了try/catch,只看业务部分
if (this.context == null) {
				this.context = createWebApplicationContext(servletContext);
			}
			if (this.context instanceof ConfigurableWebApplicationContext) {
				configureAndRefreshWebApplicationContext((ConfigurableWebApplicationContext)this.context, 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;

这段代码里,有几个方法是比较重要的,createWebApplicationContext()实际创建了WebApplicationContext,configureAndRefreshWebApplicationContext()初始化了刚创建的WebApplicationContext

在创建和初始化完成之后,将其设置为ServletContext的一个属性,这是WebApplicationContextUtils能够获取到spring容器的基础

然后是对ClassLoader的一些处理
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
			if (ccl == ContextLoader.class.getClassLoader()) {
				currentContext = this.context;
			}
			else if (ccl != null) {
				currentContextPerThread.put(ccl, this.context);
			}

这段我没有看懂它是要干嘛

之后又是日志记录和时间记录

从上面我们可以看到,ContextLoader的加载,整个流程是很清晰的。其中的核心是WebApplicationContext的创建和初始化,然后将其设置为ServletContext的一个属性,这样就可以很容易地从工具类中获取到spring容器了

接下来就看一下WebApplicationContext是怎么创建和初始化的
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
		Class<?> contextClass = determineContextClass(sc);
		if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
			throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
					"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
		}
		ConfigurableWebApplicationContext wac =
				(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
		return wac;
	}

这个方法是比较简单的,首先通过determineContextClass()方法,来确定WebApplicationContext的类型
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中特别配置ServletContext的InitParameter的话,会返回XmlWebApplicationContext,这也是99%会遇到的情况

然后就通过BeanUtils.instantiateClass()方法,创建XmlWebApplicationContext并返回。XmlWebApplicationContext是WebApplicationContext接口一个最下层的实现类,这条继承路径上,自上而下分别是WebApplicationContext-->ConfigurableWebApplicationContext-->AbstractRefreshableWebApplicationContext-->XmlWebApplicationContext。ApplicationContext接口的继承体系是比较复杂的,具体的可以看第一篇博客里的ea图

创建完毕之后,就要对刚创建的XmlWebApplicationContext进行初始化:
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
		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 information
			String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
			if (idParam != null) {
				wac.setId(idParam);
			}
			else {
				// Generate default id...
				if (sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) {
					// Servlet <= 2.4: resort to name specified in web.xml, if any.
					wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
							ObjectUtils.getDisplayString(sc.getServletContextName()));
				}
				else {
					wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
							ObjectUtils.getDisplayString(sc.getContextPath()));
				}
			}
		}

		// Determine parent for root web application context, if any.
		ApplicationContext parent = loadParentContext(sc);

		wac.setParent(parent);
		wac.setServletContext(sc);
		String initParameter = sc.getInitParameter(CONFIG_LOCATION_PARAM);
		if (initParameter != null) {
			wac.setConfigLocation(initParameter);
		}
		customizeContext(sc, wac);
		wac.refresh();
	}

这个方法也比较长,需要拆开看
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 information
			String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
			if (idParam != null) {
				wac.setId(idParam);
			}
			else {
				// Generate default id...
				if (sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) {
					// Servlet <= 2.4: resort to name specified in web.xml, if any.
					wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
							ObjectUtils.getDisplayString(sc.getServletContextName()));
				}
				else {
					wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
							ObjectUtils.getDisplayString(sc.getContextPath()));
				}
			}
		}

这个部分是给XmlWebApplicationContext设置一个id,作为一个标识,不是很重要

然后是设置继承体系:
ApplicationContext parent = loadParentContext(sc);

		wac.setParent(parent);

这里也是依赖ServletContext的InitParameter,也就是说:一般来说不会发生

然后是给XmlWebApplicationContext设置ServletContext,这步很简单,但这样设置之后,从spring容器内部,就可以获取到ServletContext了,所以是很重要的
wac.setServletContext(sc);

接下来是设置spring配置文件的路径
String initParameter = sc.getInitParameter(CONFIG_LOCATION_PARAM);
		if (initParameter != null) {
			wac.setConfigLocation(initParameter);
		}

如果没有配置的话,之后就默认会在WEB-INF下寻找applicationContext.xml,如果配置了的话:
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value> 
        	WEB-INF/beans.xml
        	WEB-INF/cxf.xml
        </param-value>
  </context-param>

就会在指定的路径加载配置文件

然后是一个留给用户扩展的方法customizeContext(),大部分情况下,在这个方法内,什么事情也不会发生

最后就是核心方法
wac.refresh();

这个refresh()方法,是在ApplicationContext继承体系中,比较上层的ConfigurableApplicationContext里定义的,是XmlWebApplicationContext初始化的核心,在这个方法里,会完成资源文件的加载、配置文件解析、Bean定义的注册、组件的初始化等核心工作,在下一篇博客里,再详细介绍
分享到:
评论
3 楼 kyfxbl 2012-08-06  
哦,理解错了,以为你问这个方法在哪调用的

这个方法的定义,没记错的话是在AbstractApplicationContext里
2 楼 kyfxbl 2012-08-06  
在ContextLoader里
1 楼 liuwuhen 2012-08-06  
lz configureAndRefreshWebApplicationContext这个方法是那个类中?

相关推荐

    Spring3.1.3 Ioc在Web容器中的建立

    标题 "Spring3.1.3 Ioc在Web容器中的建立" 涉及的是Spring框架的一个关键特性——依赖注入(Dependency Injection,简称DI),以及如何在Web应用环境中配置和使用它。Spring是Java开发中最流行的轻量级框架之一,...

    s2sh struts2 Spring hibernate整合实例(附带所有jar包,及源码)

    Spring作为轻量级的IOC(Inversion of Control,控制反转)和AOP(Aspect Oriented Programming,面向切面编程)容器,管理着应用中的对象。它通过依赖注入将对象的创建和依赖关系交由Spring管理,使得代码更加解耦...

    【原创】Mybatis学习笔记(一)——Spring集成Mybatis

    在本篇【原创】Mybatis学习笔记(一)——Spring集成Mybatis中,我们将探讨如何将流行的持久层框架Mybatis与Spring框架进行整合,以便在实际项目开发中实现灵活、高效的数据库操作。以下是对相关知识点的详细说明: ...

    Spring mvc + Spring + Spring jdbc 整合 demo.rar

    2. **Spring 框架**:Spring的核心是IoC(Inversion of Control,控制反转)和DI(Dependency Injection,依赖注入),它允许开发者通过配置文件或注解来管理对象的生命周期和依赖关系。AOP(Aspect-Oriented ...

    Spring之Spring2.5集成Struts2.2

    标题“Spring之Spring2.5集成Struts2.2”涉及到的是在Web开发中整合两个非常流行的开源框架——Spring和Struts2的过程。这个过程旨在利用Spring的强大IoC(Inversion of Control,控制反转)和AOP(Aspect Oriented ...

    spring源代码解析

    Spring的ContextLoader是提供这样性能的类,我们可以使用 ContextLoaderServlet或者ContextLoaderListener的启动时载入的Servlet来实例化Spring IOC容器 – 为什么会有两个不同的类来装载它呢,这是因为它们的使用...

    spring第三天.pdf

    在本课程中,我们将深入探讨Spring框架的核心特性,包括IoC容器、AOP(面向切面编程)以及动态代理的设计模式。课程的目标是使学员能够独立阅读并理解Spring框架内部的关键源码,以便更好地掌握其工作原理。 首先,...

    apache wink集成spring 开发rest服务

    集成Spring和Apache Wink的主要目标是利用Spring的IOC容器管理Wink的组件,如资源、过滤器和消息处理器,以及利用Spring的数据访问和事务管理功能。以下是一些关键知识点: 1. **RESTful服务基础**:REST...

    专题资料(2021-2022年)Java项目教学第一学期SSM框架讲义1Spring的基本应用.docx

    Spring作为一站式框架,涵盖了Java EE应用的各个层次,如Web层的Spring MVC、Service层的IOC容器以及DAO层的JDBC Template和ORM框架的集成。 1.2 Spring版本与目录结构 Spring的主要版本包括4.x系列。其目录结构中...

    使用注解将 Vaadin 6.7.3 和 Spring 3.0.5 整合 (一)

    同时,还需要配置Spring的ContextLoaderListener,以便初始化Spring上下文。 综上所述,整合Vaadin 6.7.3和Spring 3.0.5涉及使用注解将Vaadin UI组件注册为Spring bean,配置Spring的VaadinServlet,以及设置适当的...

    CXF WebService整合Spring

    5. **启动WebService**:在CXF的Servlet配置中,使用Spring的`ContextLoaderListener`加载Spring上下文,然后配置CXF的Servlet,将它映射到特定的URL上,以启动WebService服务。 6. **测试与调用**:通过CXF提供的...

    dwr和ssh的集成源码

    - **Spring** 是一个全面的企业级应用开发框架,提供了IOC(Inversion of Control,控制反转)和AOP(Aspect-Oriented Programming,面向切面编程)等特性,以及事务管理、数据访问集成等服务。 - **Hibernate** 是...

    整合Struts+Spring+Hibernate简单例子开发

    4. **整合步骤**: 在web.xml中配置Spring的ContextLoaderListener和Struts的ActionServlet,确保在Web应用启动时加载Spring上下文,并在处理请求时使用Struts的控制器。 5. **测试应用**: 创建JSP页面作为视图,...

    jsf spring

    而Spring框架则以其强大的IoC(Inversion of Control)和DI(Dependency Injection)功能著称,可以简化应用的复杂性,并提供了事务管理、数据访问集成、AOP等众多服务。 将JSF与Spring结合,开发者可以在JSF的视图...

    struts2+spring+mybatis整合

    - Spring是Java企业级应用的核心框架,提供IOC(Inversion of Control,控制反转)和AOP(Aspect Oriented Programming,面向切面编程)两大核心特性。 - IOC允许开发者将对象的创建和依赖关系的管理交由Spring...

    MyEclipse下SSH集成(超详细,含源码)

    Spring的核心在于它的IoC(Inversion of Control)容器,使得组件之间的依赖关系可以通过配置来管理,而不是硬编码。 2. Struts:这是一个基于MVC(Model-View-Controller)设计模式的Java Web框架,主要用于构建...

    springmybatis

    mybatis实战教程mybatis in action之五与spring3集成附源码 mybatis实战教程mybatis in action之六与Spring MVC 的集成 mybatis实战教程mybatis in action之七实现mybatis分页源码下载 mybatis实战教程mybatis in ...

    ContextLoader 加载xml

    在源码层面,`ContextLoader`主要由`ContextLoaderListener`和`ContextLoader`两个类协作完成这些工作。`ContextLoaderListener`监听`ServletContext`事件,当服务器启动时,它触发`ContextLoader`的初始化过程。 `...

Global site tag (gtag.js) - Google Analytics