ContextLoader类的结构如图:
ContextLoader类的源码:
package org.springframework.web.context;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.ServletContext;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.access.BeanFactoryLocator;
import org.springframework.beans.factory.access.BeanFactoryReference;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextException;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.access.ContextSingletonBeanFactoryLocator;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
public class ContextLoader {
//配置root WebApplicationContext 的实现类
public static final String CONTEXT_CLASS_PARAM = "contextClass";
//在web.xml中配置的root WebApplicationContext id用于BeanFactory序列化
public static final String CONTEXT_ID_PARAM = "contextId";
public static final String CONTEXT_INITIALIZER_CLASSES_PARAM = "contextInitializerClasses";
//see org.springframework.web.context.support.XmlWebApplicationContext#DEFAULT_CONFIG_LOCATION
public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";
//可选的servlet context parameter参数,被用于当获取一个parent context时使用的是默认实现,loadParentContext(ServletContext servletContext) 指定 'selector' 用于ContextSingletonBeanFactoryLocator#getInstance(String selector)方法,该方法被用于获取parent context后从parent context获取BeanFactoryLocator实例,默认的配置是classpath*:beanRefContext.xml对应的默认应用于ContextSingletonBeanFactoryLocator#getInstance()方法,该方法对应有一个parentContextKey
public static final String LOCATOR_FACTORY_SELECTOR_PARAM = "locatorFactorySelector";
//同上可选的servlet context parameter参数parentContextKey。用于BeanFactoryLocator#useBeanFactory(String factoryKey)指定factoryKey 从BeanFactoryLocator中获取parent application context,默认依赖于 classpath*:beanRefContext.xml选取候选工厂参数
public static final String LOCATOR_FACTORY_KEY_PARAM = "parentContextKey";
//相对于ContextLoader class的资源路径,定义了ContextLoader的默认策略名称
private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";
private static final Properties defaultStrategies;
static {
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());
}
}
//从当前的ContextLoader获取applicationContext
private static final Map currentContextPerThread =
new ConcurrentHashMap(1);
//如果ContextLoader类部署在Web应用程序ClassLoader本身中
private static volatile WebApplicationContext currentContext;
private WebApplicationContext context;
//Holds BeanFactoryReference when loading parent factory via ContextSingletonBeanFactoryLocator.
private BeanFactoryReference parentContextRef;
//创建一个新的ContextLoader基于"contextClass" and "contextConfigLocation" 参数,该类被特别用于在无参的子类ContextLoaderListener中,将创建的application context注册到ServletContext中键值为ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
如果子类想释放使用closeWebApplicationContext方法在容器关闭的时候
public ContextLoader() {
}
//在给定的application context中创建一个新的ContextLoader方法应用于servlet 3.0以上注册listeners可以直接通过ServletContext#addListener
public ContextLoader(WebApplicationContext context) {
this.context = context;
}
//在给定servletContext初始化spring的root web application context
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 {
//保存上下文到本地变量中为了保证当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;
}
}
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;
}
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);
}
}
}
@SuppressWarnings("unchecked")
protected List>>
determineContextInitializerClasses(ServletContext servletContext) {
String classNames = servletContext.getInitParameter(CONTEXT_INITIALIZER_CLASSES_PARAM);
List>> classes =
new ArrayList>>();
if (classNames != null) {
for (String className : StringUtils.tokenizeToStringArray(classNames, ",")) {
try {
Class clazz = ClassUtils.forName(className, ClassUtils.getDefaultClassLoader());
Assert.isAssignable(ApplicationContextInitializer.class, clazz,
"class [" + className + "] must implement ApplicationContextInitializer");
classes.add((Class>)clazz);
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException(
"Failed to load context initializer class [" + className + "]", ex);
}
}
}
return classes;
}
protected void customizeContext(ServletContext servletContext, ConfigurableWebApplicationContext applicationContext) {
List>> initializerClasses =
determineContextInitializerClasses(servletContext);
if (initializerClasses.size() == 0) {
// no ApplicationContextInitializers have been declared -> nothing to do
return;
}
ArrayList> initializerInstances =
new ArrayList>();
for (Class> initializerClass : initializerClasses) {
Class contextClass = applicationContext.getClass();
Class initializerContextClass =
GenericTypeResolver.resolveTypeArgument(initializerClass, ApplicationContextInitializer.class);
Assert.isAssignable(initializerContextClass, contextClass, String.format(
"Could not add context initializer [%s] as its generic parameter [%s] " +
"is not assignable from the type of application context used by this " +
"context loader [%s]", initializerClass.getName(), initializerContextClass, contextClass));
initializerInstances.add(BeanUtils.instantiateClass(initializerClass));
}
applicationContext.getEnvironment().initPropertySources(servletContext, null);
Collections.sort(initializerInstances, new AnnotationAwareOrderComparator());
for (ApplicationContextInitializer initializer : initializerInstances) {
initializer.initialize(applicationContext);
}
}
protected ApplicationContext loadParentContext(ServletContext servletContext) {
ApplicationContext parentContext = null;
String locatorFactorySelector = servletContext.getInitParameter(LOCATOR_FACTORY_SELECTOR_PARAM);
String parentContextKey = servletContext.getInitParameter(LOCATOR_FACTORY_KEY_PARAM);
if (parentContextKey != null) {
// locatorFactorySelector may be null, indicating the default "classpath*:beanRefContext.xml"
BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector);
Log logger = LogFactory.getLog(ContextLoader.class);
if (logger.isDebugEnabled()) {
logger.debug("Getting parent context definition: using parent context key of '" +
parentContextKey + "' with BeanFactoryLocator");
}
this.parentContextRef = locator.useBeanFactory(parentContextKey);
parentContext = (ApplicationContext) this.parentContextRef.getFactory();
}
return parentContext;
}
public void closeWebApplicationContext(ServletContext servletContext) {
servletContext.log("Closing Spring root WebApplicationContext");
try {
if (this.context instanceof ConfigurableWebApplicationContext) {
((ConfigurableWebApplicationContext) this.context).close();
}
}
finally {
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = null;
}
else if (ccl != null) {
currentContextPerThread.remove(ccl);
}
servletContext.removeAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
if (this.parentContextRef != null) {
this.parentContextRef.release();
}
}
}
public static WebApplicationContext getCurrentWebApplicationContext() {
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl != null) {
WebApplicationContext ccpt = currentContextPerThread.get(ccl);
if (ccpt != null) {
return ccpt;
}
}
return currentContext;
}
}
ContextLoader的描述文档中已经说的很明白了其作用:实际上由ContextLoaderListener调用执行根应用上下文的初始化工作。
其中的参数解析:
contextClass:是在web.xml中context-param级别的参数。默认就是XmlWebApplicationContext
contextClass
org.springframework.web.context.support.XmlWebApplicationContext
WEB-INF/applicationContext1.xml
WEB-INF/*Context.xml,WEB-INF/spring*.xml
/*Context.xml
如果没有显示的配置就使用:/WEB-INF/applicationContext.xml
contextConfigLocation
classpath*:applicationContext.xml,classpath:spring/quartz.xml
org.springframework.web.context.ContextLoaderListener
在使用默认的ApplicationContext的实现时,在配置中需要注意的就是在配置了多文件xml的情况下,后面的bean定义将会覆盖前面的。
这可以通过额外的XML文件故意地(This can be leveraged to deliberately)覆盖某些bean定义。除了加载根应用程序上下文外,此类可以可选地加载或获取并将共享父上下文连接到根应用程序上下文。
从Spring 3.1开始 ContextLoader支持通过ContextLoader(WebApplicationContext)构造函数注入根Web应用程序上下文,允许在Servlet 3.0+环境中进行编程配置。
有关使用示例,请参阅{@link org.springframework.web.WebApplicationInitializer}。