一、上下文加载器
要在项目中使用Spring框架,需要在web.xml做如下配置:
<!--contextConfigLocation在 ContextLoaderListener类中的默认值是 /WEB-INF/applicationContext.xml-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
<!-- <param-value>classpath:applicationContext*.xml</param-value> -->
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
以下为类ContextLoaderListener的声明:
public class ContextLoaderListener extends ContextLoader implements ServletContextListener{
// 上下文加载器
private ContextLoader contextLoader;
// 无参构造放
public ContextLoaderListener() {
}
// 带参构造函数
public ContextLoaderListener(WebApplicationContext context){
super(context);
}
// ......其它代码
}
ServletContextListener是Servlet API中的一个接口,它能够坚挺ServletContext对象的生命周期,也就是监听整个Web应用的生命周期。在Servlet容器启动时,会触发ServletContextEvent事件,这个事件就由ServletContextListener来处理。在ServletContextListener接口定义了两个处理ServletContextEvent事件的方法,分别是Web应用初始化时的contextInitialized方法和Web应用销毁时contextDestroyed方法。
/**
* Initialize the root web application context.
*/
public void contextInitialized(ServletContextEvent event) {
this.contextLoader = createContextLoader();
if (this.contextLoader == null) {
this.contextLoader = this;
}
this.contextLoader.initWebApplicationContext(event.getServletContext());
}
上面的代码是Spring的ContextLoaderListener中覆写的初始化监听方法,所有支撑Spring工作的初始化工作,都在这个方法中完成。在这个方法中,第一行通过createContextLoader()创建一个上下文的加载器,不过在Spring中,这个方法的实现仅仅返回了一个null,同时注释中提示,它可以被子类覆写。另外一方面,这个方法已经过时。所以,整个初始化工作的关键,就是最后一行代码:
this.contextLoader.initWebApplicationContext(event.getServletContext());
由于createContextLoader()方法的不作为,实际上this.contextLoader就是对象本身的this。也就是说,如果没有覆写createContextLoader方法,那么默认的上下文加载器就会是ContextLoaderListener自身。
二、Web应用上下文
至于initWebApplicationContext方法,ContextLoaderListener本身并没有实现,而是它继承自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【将context保存在一个实例变量中,以保证】
// it is available on ServletContext shutdown. 【在ServletContext关闭时可用。】
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;
}
}
这个方法的作用,主要是初始化Web应用的环境上下文context,ContextLoader持有这样一个成员变量,类型为WebApplicationContext。在这个方法中,首先判断是否已经存在了一个上下文加载器,如果已经存在而又去初始化加载器,就会抛出一个IllegalStateException异常,提示无法完成上下文的初始化,要求检查是否在web.xml中配置了多个上下文加载器。如果没有抛出这个异常,接下来就要做一些准备工作,比如打印日志,记录时间戳,但这些都不是这个方法的重点。
if (this.context == null) {
this.context = createWebApplicationContext(servletContext); // ①稍后回来
}
上面的代码是创建Web应用上下文的关键点,我们先在这里打上一个标记。方法createWebApplicationContext的源码如下:
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;
}
第一行代码用于决定使用何种方式创建环境上下文context,比如注解方式创建上下文或者XML配置方式创建上下文,这两种方式分别对应了AnnotationConfigWebApplicationContext和XmlWebApplicationContext类,它们都实现了ConfigurableWebApplicationContext接口。显然,我们需要关注determineContextClass(sc)究竟返回了一个什么样的创建方式对应的类。
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);
}
}
}
观察源码发现,上下文类的获取方式有两种,一是从ServletContext中获取初始化参数CONTEXT_CLASS_PARAM(取值为contextClass),这个可以通过在web.xml中配置context节点注入参数(存疑);另一种是从defaultStrategies获取默认的上下文类。我们先来关注下,默认的应用上下文是什么。defaultStrategies是Properties类的一个实例,Properties类则继承了HashMap。所以,Properties.getProperty相当于HashMap.get。同时defaultStrategies也是ContextLoader的一个静态成员变量。既然我们要从defaultStrategies中get一些属性,就需要确认defaultStrategies的初始化位置。
在ContextLoader声明defaultStrategies变量之后,紧接着就是对该变量的初始化代码,如下:
/**
* Name of the class path resource (relative to the ContextLoader class)
* that defines ContextLoader's default strategy names.
*/
private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";
private static final Properties defaultStrategies;
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());
}
}
从上面可以看出,defaultStrategies默认是使用了一个属性文件进行填充,至于ClassPathResource类和PropertiesLoaderUtils以及PropertiesLoaderUtils的loadProperties方法,这里就不继续跟下去了,否则又是一个没完没了的封装。我们直接来看看,是哪个属性文件保存着默认的上下文类。这里有一个DEFAULT_STRATEGIES_PATH变量,取值是ContextLoader.properties,它的位置其实已经通过注释给出了:class path资源文件的名称(相对于ContextLoader类),它定义了上下文加载器的默认策略。我们可以很轻松的在spring-web.jar中org.springframework.web.context包下找到这个ContextLoader.properties文件,打开可以看到它的内容如下:
# Default WebApplicationContext implementation class for ContextLoader.
# Used as fallback when no explicit context implementation has been specified as context-param.
# Not meant to be customized by application developers.
org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
所以,绕了这么大个圈子,spring默认就是使用XML配置的方式创建并初始化上下文环境。
再回到createWebApplicationContext方法中,由之前一系列跟踪我们知道,contextClass默认是XmlWebApplicationContext类,至于创建这个类的实例的工作,也就是创建真正的应用环境上下文的工作,就由下面的代码完成:
ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
BeanUtils.instantiateClass是一个基于反射的工具类方法,它根据传入的类型和参数,构造该类型的一个实例。
至此,环境上下文就创建完成了,我们也可以回到之前标记的位置①处,继续跟踪其后的代码。
版权声明:本文为博主原创文章,未经博主允许不得转载。