一.概述
ContextLoader是一个工具类,用来初始化WebApplicationContext,其主要方法就是initWebApplicationContext
二.初始化
1.实例化
ContextLoaderListener类继承了ContextLoader类,当ContextLoaderListener实例化时,也要实例化ContextLoader,会执行ContextLoader的静态代码块,属性defaultStrategies中记录了上下文的默认实现XmlWebApplicationContext,如果web.xml中没有配置实现类,就会用到此默认实现
/**
* 类路径资源的名称(相对于ContextLoader类)
* 定义了ContextLoader的默认策略名称。
* 配置文件位置:spring-web-3.2.3.jar - org.springframework.web.context.ContextLoader.properties
*/
private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";
// 记录了Spring上下文的默认实现类:XmlWebAppliactionContext
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());
}
}
默认配置文件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
2.开始实例化
org.springframework.web.context.ContextLoader # initWebApplicationContext(ServletContext servletContext)方法开始根据所给的web容器上下文servletContext实例化上下文
/**
* 根据给定的web容器上下文对象servletContext初始化Spring上下文
*
* 1.当web.xml中<context-param>中配置了"contextClass"和"contextConfigLocation"
* 则获取参数值创建一个新的ApplicationContext
*
* 2.如果web.xml中没有定义,就使用属性defaultStrategies中记录的默认上下文实现类
*
* @param servletContext current servlet context
* @return the new WebApplicationContext
*/
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) {
// 创建一个WebApplicationContext实例
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {// 上下文还没刷新
// 上下文尚未刷新 - >提供设置父上下文,设置应用程序上下文ID等服务
if (cwac.getParent() == null) {
// 上下文实例被注入了,但没有父对象 -> 确定根Web应用程序上下文(如果有)的父对象。
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
// 配置并刷新上下文对象cwac
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
// 将Spring根上下文对象存储在web容器上下文对象中,key为WebApplicationContext.class.getName() + ".ROOT"
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;
}
}
1.1 实例化上下文
现根据所给web容器上下文实例化Spring上下文
this.context = createWebApplicationContext(servletContext)
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() + "]");
}
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
1.2 配置并刷新上下文
上下文wac只是实例化了,还没有刷新,configureAndRefreshWebApplicationContext(cwac, servletContext)方法刷新上下文
/**
* 配置XmlWebApplicationContext,读取web.xml中通过contextConfigLocation标签指定的XML文件,
* 实例化XML文件中配置的bean,并在上一步中实例化的容器中进行注册。
*/
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
/**
* ObjectUtils.identityToString(wac):对象wac的String表示,
* 如org.springframework.web.context.support.XmlWebApplicationContext@1e8bf76
* wac.getId():上下文的唯一id, 如org.springframework.web.context.support.XmlWebApplicationContext@1e8bf76
*/
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// 获取web容器上下文中的Spring上下文contextId
String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
if (idParam != null) {
// 如果idParam已存在,wac id还是设置为原始值idParam
wac.setId(idParam);
} else {
// 根据可用信息分配一个更有用的ID
// 如org.springframework.web.context.WebApplicationContext:/bi
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()));
}
}
}
// 将web容器上下文对象记录在Spring上下文中
wac.setServletContext(sc);
// 返回自定义Spring配置文件路径:如/WEB-INF/spring/applicationContext.xml
String initParameter = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (initParameter != null) {
// 记录配置xml,可能有多个
wac.setConfigLocation(initParameter);
}
customizeContext(sc, wac);
// 刷新上下文
wac.refresh();
}
1.3 记录上下文
将Spring的上下文记录到web容器上下文servletContext中,key为org.springframework.web.context.WebApplicationContext.ROOT
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
1.4 下一篇
上下文刷新具体代码由XmlWebApplicationContext处理。
详情请看下一篇: Spring初始化 - XmlWebApplicationContext
三.无注释源码
package org.springframework.web.context;
public class ContextLoader {
public static final String CONTEXT_CLASS_PARAM = "contextClass";
public static final String CONTEXT_ID_PARAM = "contextId";
public static final String CONTEXT_INITIALIZER_CLASSES_PARAM = "contextInitializerClasses";
public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";
public static final String LOCATOR_FACTORY_SELECTOR_PARAM = "locatorFactorySelector";
public static final String LOCATOR_FACTORY_KEY_PARAM = "parentContextKey";
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());
}
}
private static final Map<ClassLoader, WebApplicationContext> currentContextPerThread =
new ConcurrentHashMap<ClassLoader, WebApplicationContext>(1);
private static volatile WebApplicationContext currentContext;
private WebApplicationContext context;
private BeanFactoryReference parentContextRef;
public ContextLoader() {
}
public ContextLoader(WebApplicationContext context) {
this.context = 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 {
// 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) {
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);
}
configureAndRefreshWebApplicationContext(cwac, 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() + "]");
}
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
@Deprecated
protected WebApplicationContext createWebApplicationContext(ServletContext sc, ApplicationContext parent) {
return createWebApplicationContext(sc);
}
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()));
}
}
}
wac.setServletContext(sc);
String initParameter = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (initParameter != null) {
wac.setConfigLocation(initParameter);
}
customizeContext(sc, wac);
wac.refresh();
}
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<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>
determineContextInitializerClasses(ServletContext servletContext) {
String classNames = servletContext.getInitParameter(CONTEXT_INITIALIZER_CLASSES_PARAM);
List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> classes =
new ArrayList<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>();
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<ApplicationContextInitializer<ConfigurableApplicationContext>>)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<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> initializerClasses =
determineContextInitializerClasses(servletContext);
if (initializerClasses.size() == 0) {
// no ApplicationContextInitializers have been declared -> nothing to do
return;
}
Class<?> contextClass = applicationContext.getClass();
ArrayList<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerInstances =
new ArrayList<ApplicationContextInitializer<ConfigurableApplicationContext>>();
for (Class<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerClass : initializerClasses) {
Class<?> initializerContextClass =
GenericTypeResolver.resolveTypeArgument(initializerClass, ApplicationContextInitializer.class);
if (initializerContextClass != null) {
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.getName(),
contextClass.getName()));
}
initializerInstances.add(BeanUtils.instantiateClass(initializerClass));
}
ConfigurableEnvironment env = applicationContext.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment)env).initPropertySources(servletContext, null);
}
Collections.sort(initializerInstances, new AnnotationAwareOrderComparator());
for (ApplicationContextInitializer<ConfigurableApplicationContext> 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;
}
}
四.注释源码
package org.springframework.web.context;
/**
* 执行应用程序上下文context根的初始化
*
* 1.由ContextLoaderListener调用
*
* 2.从web.xml中的context-param中查找"contextClass"以确定上下文的实现类
* 如果没有找到,即没有定义,返回默认实现类:org.springframework.web.context.support.XmlWebApplicationContext
* 如果没有找到,使用默认的ContextLoader,任何上下文context类都需要实现接口ConfigurableWebApplicationContext
*
* 3.处理web.xml中<context-param>节点中<param-name>为"contextConfigLocation"对应的值
* 值<param-value>指定了spring的配置xml,将值传递给上下文实例,值<param-value>可以为多个xml文件
* 多个文件通过逗号和空格分隔
* 如<param-value>WEB-INF/applicationContext1.xml,WEB-INF/applicationContext2.xml</param-value>
* 也支持Ant样式的路径模式,如:
* <param-value>WEB-INF/*Context.xml,WEB-INF/spring*.xml</param-value> 或
* <param-value>WEB-INF/**/*Context.xml</param-value>
*
* 如果没有明确指定<param-name>为contextConfigLocation的参数,使用一个默认位置"/WEB-INF/applicationContext.xml"
* 并且使用XmlWebApplicationContext实现上下文
*
* 4.注意:
* 在多个配置xml位置时,后面定义的bean会覆盖之前文件中定义的bean
* 这可以通过额外的XML文件故意地覆盖某些bean定义。
*
* 5.除了可以加载应用程序的根上下文,这个类还可以选择加载或获取并挂接Spring根上下文的共享父上下文ServletContext
* 详情查看ContextLoader#loadParentContext(ServletContext)方法
*
* 6.从Spring 3.1开始,ContextLoader通过#ContextLoader(WebApplicationContext)构造函数支持
* 注入应用程序的根上下文,允许在Servlet 3.0+环境中编程配置
* 详情查看org.springframework.web.WebApplicationInitializer
*/
public class ContextLoader {
/**
* 配置参数名"contextClass"对应的参数值作为根WebApplicationContext的实现类
*/
public static final String CONTEXT_CLASS_PARAM = "contextClass";
/**
* 配置参数名"contextId"对应的参数值作为根WebApplicationContext的id
* 参数对应值用作底层BeanFactory的序列化id
*/
public static final String CONTEXT_ID_PARAM = "contextId";
/**
* 配置参数名"contextInitializerClasses"对应的参数值用作初始化应用程序的上下文
*/
public static final String CONTEXT_INITIALIZER_CLASSES_PARAM = "contextInitializerClasses";
/**
* 1.可以在web.xml中配置Spring的配置xml,如
* <context-param>
* <param-name>contextConfigLocation</param-name>
* <param-value>/WEB-INF/spring/applicationContext.xml</param-value>
* </context-param>
* 2.如果在web.xml中没有配置contextConfigLocation,使用默认值"/WEB-INF/applicationContext.xml"
*/
public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";
/**
* 仅在使用#loadParentContext(ServletContext servletContext)的默认实现获取父上下文时
* 可以用可选servlet参数"locatorFactorySelector"。
*
* 调用方法ContextSingletonBeanFactoryLocator#getInstance(String selector)会返回一个selector
* 这个selector是被获取父上下文对象中的BeanFactoryLocator实例
*
* 默认值为:"classpath*:beanRefContext.xml",与ContextSingletonBeanFactoryLocator#getInstance()方法的默认值相匹配。
* 在这种情况下,提供“parentContextKey”参数就足够了。
*/
public static final String LOCATOR_FACTORY_SELECTOR_PARAM = "locatorFactorySelector";
/**
* 仅在使用#loadParentContext(ServletContext servletContext)的默认实现获取父上下文对象时
* 可以用可选servlet参数"parentContextKey"
*/
public static final String LOCATOR_FACTORY_KEY_PARAM = "parentContextKey";
/**
* 类路径资源的名称(相对于ContextLoader类)
* 定义了ContextLoader的默认策略名称。
* 配置文件位置:spring-web-3.2.3.jar - org.springframework.web.context.ContextLoader.properties
*/
private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";
// 记录了Spring上下文的默认实现类:XmlWebAppliactionContext
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());
}
}
/**
* Map from (thread context) ClassLoader to corresponding 'current' WebApplicationContext.
* key: 类加载器
* value : 当前的WebApplicationContext
*/
private static final Map<ClassLoader, WebApplicationContext> currentContextPerThread =
new ConcurrentHashMap<ClassLoader, WebApplicationContext>(1);
/**
* 当前线程的类加载器等于ContextLoader类加载器,currentContext记录当前的WebApplicationContext
* ClassLoader ccl = Thread.currentThread().getContextClassLoader();
* if (ccl == ContextLoader.class.getClassLoader()) {
* currentContext = this.context;
* }
*/
private static volatile WebApplicationContext currentContext;
/**
* 根上下文对象
*/
private WebApplicationContext context;
/**
* 通过ContextSingletonBeanFactoryLocator加载父工厂时,保留BeanFactoryReference。
* Holds BeanFactoryReference when loading parent factory via
* ContextSingletonBeanFactoryLocator.
*/
private BeanFactoryReference parentContextRef;
/**
* 1.创建一个ContextLoader,ContextLoader会基于web.xml中<context-param>配置的
* "contextClass" 和 "contextConfigLocation"属性创建一个上下文
*
* 2.当在web.xml中声明了ContextLoaderListener作为监听器,会调用这个无参构造函数
*
* 3.创建的上下文对象将被注册到web容器上下文ServletContext
* 属性名为WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
*
* 4.子类可以自由调用{@link #closeWebApplicationContext}方法关闭关闭应用程序上下文。
*/
public ContextLoader() {
}
/**
* 1.根据给定的上下文context创建一个新的ContextLoader
*
* 2.这个构造函数在Servlet 3.0+环境中很有用,通过ServletContext#addListener可以注册监听器
*
* 3.这个上下文context可能已经或者还没有刷新:org.springframework.context.ConfigurableApplicationContext#refresh()
* 如果上下文context是ConfigurableWebApplicationContext的实现,并且还没有刷新(推荐的方式)
* 会有以下情况:
* (1)如果给定上下文context还没有分配id:org.springframework.context.ConfigurableApplicationContext#setId
* 会给他分配一个id
* (2)ServletContext和ServletConfig对象将被委派给上下文对象context
* (3)org.springframework.web.context.ContextLoader#customizeContext将被调用
* (4)任何org.springframework.context.ApplicationContextInitializer将通过初始参数"contextInitializerClasses"指定
* (5)org.springframework.context.ConfigurableApplicationContext#refresh将被调用
*
* 3.如果上下文content已经刷新或不是ConfigurableWebApplicationContext的实现,
* 上述任何一种都不会在用户根据自己的具体需求执行这些操作(或不执行)的假设下进行
*
* 4.有关使用示例,请参阅{@link org.springframework.web.WebApplicationInitializer}
*
* 5.无论如何,给定的应用程序上下文content将被注册到ServletContext中,
* 属性名为WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE},
* 并且在此监听器调用{@link #contextDestroyed}方法时,Spring应用程序上下文将关闭。
*/
public ContextLoader(WebApplicationContext context) {
this.context = context;
}
/**
* 根据给定的web容器上下文对象servletContext初始化Spring上下文
*
* 1.当web.xml中<context-param>中配置了"contextClass"和"contextConfigLocation"
* 则获取参数值创建一个新的ApplicationContext
*
* 2.如果web.xml中没有定义,就使用属性defaultStrategies中记录的默认上下文实现类
*
* @param servletContext current servlet context
* @return the new WebApplicationContext
*/
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) {
// 创建一个WebApplicationContext实例
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {// 上下文还没刷新
// 上下文尚未刷新 - >提供设置父上下文,设置应用程序上下文ID等服务
if (cwac.getParent() == null) {
// 上下文实例被注入了,但没有父对象 -> 确定根Web应用程序上下文(如果有)的父对象。
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
// 配置并刷新上下文对象cwac
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
// 将Spring根上下文对象存储在web容器上下文对象中,key为WebApplicationContext.class.getName() + ".ROOT"
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;
}
}
/**
* 实例化此加载器中根WebApplicationContext
* 1.如果指定了实现类,使用自定义上下文实现类
* 2.没有指定就使用默认的上下文实现类
* @param sc current servlet context 当前web容器上下文对象
* @return the root WebApplicationContext
*/
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
Class<?> contextClass = determineContextClass(sc);
// 判断ConfigurableWebApplicationContext接口与指定的 Class 参数contextClass所表示的类或接口是否相同,
// 或是否是其超类或超接口。如果是则返回 true;否则返回 false。
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
}
// 使用WebApplicationContext的实现类的无参构造函数实例化上下文对象
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
/**
* @deprecated as of Spring 3.1 in favor of
* {@link #createWebApplicationContext(ServletContext)} and
* {@link #configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext, ServletContext)}
*/
@Deprecated 废弃
protected WebApplicationContext createWebApplicationContext(ServletContext sc, ApplicationContext parent) {
return createWebApplicationContext(sc);
}
/**
* 配置XmlWebApplicationContext,读取web.xml中通过contextConfigLocation标签指定的XML文件,
* 实例化XML文件中配置的bean,并在上一步中实例化的容器中进行注册。
*/
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
/**
* ObjectUtils.identityToString(wac):对象wac的String表示,
* 如org.springframework.web.context.support.XmlWebApplicationContext@1e8bf76
* wac.getId():上下文的唯一id, 如org.springframework.web.context.support.XmlWebApplicationContext@1e8bf76
*/
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// 获取web容器上下文中的Spring上下文contextId
String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
if (idParam != null) {
// 如果idParam已存在,wac id还是设置为原始值idParam
wac.setId(idParam);
} else {
// 根据可用信息分配一个更有用的ID
// 如org.springframework.web.context.WebApplicationContext:/bi
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()));
}
}
}
// 将web容器上下文对象记录在Spring上下文中
wac.setServletContext(sc);
// 返回自定义Spring配置文件路径:如/WEB-INF/spring/applicationContext.xml
String initParameter = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (initParameter != null) {
// 记录配置xml,可能有多个
wac.setConfigLocation(initParameter);
}
customizeContext(sc, wac);
// 刷新上下文
wac.refresh();
}
/**
* 返回实现的上下文对象
* 1.如果指定了实现类,使用自定义上下文实现类
* 2.没有指定就使用默认的上下文实现类:XmlWebApplicationContext
*
* @param servletContext current servlet context
* @return 实现的上下文对象
*/
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 {// 使用默认实现类
// 从属性defaultStrategies中获取默认实现类名:XmlWebApplicationContext
// contextClassName = "org.springframework.web.context.support.XmlWebApplicationContext"
contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
try {
// 使用指定类名来加载XmlWebApplicationContext类
return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException("Failed to load default context class [" + contextClassName + "]", ex);
}
}
}
/**
* 如果在web.xml中定义了CONTEXT_INITIALIZER_CLASSES_PARAM="contextInitializerClasses"
* 就返回返回要使用的ApplicationContextInitializer的实现类
* @param servletContext current servlet context
*/
@SuppressWarnings("unchecked")
protected List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>
determineContextInitializerClasses(ServletContext servletContext) {
String classNames = servletContext.getInitParameter(CONTEXT_INITIALIZER_CLASSES_PARAM);
List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> classes =
new ArrayList<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>();
if (classNames != null) {// 存在参数"contextInitializerClasses"
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<ApplicationContextInitializer<ConfigurableApplicationContext>>)clazz);
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException("Failed to load context initializer class [" + className + "]", ex);
}
}
}
return classes;
}
/**
* 1. 配置位置已经提供给上下文之后,在上下文applicationContext刷新之前
* ContextLoader可以创建自定义的ConfigurableWebApplicationContext类
*
* 2. #determineContextInitializerClasses(ServletContext)确定默认实现
* 如果存在默认实现,上下文初始类通过初始参数CONTEXT_INITIALIZER_CLASSES_PARAM和
* 用所给的applicationContext调用每个ApplicationContextInitializer#initialize
* @param servletContext the current servlet context
* @param applicationContext the newly created application context
*/
protected void customizeContext(ServletContext servletContext, ConfigurableWebApplicationContext applicationContext) {
// 获取ApplicationContextInitializer的实现类
List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> initializerClasses =
determineContextInitializerClasses(servletContext);
if (initializerClasses.size() == 0) {
// 没有ApplicationContextInitializers声明
return;
}
Class<?> contextClass = applicationContext.getClass();
ArrayList<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerInstances =
new ArrayList<ApplicationContextInitializer<ConfigurableApplicationContext>>();
for (Class<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerClass : initializerClasses) {
Class<?> initializerContextClass =
GenericTypeResolver.resolveTypeArgument(initializerClass, ApplicationContextInitializer.class);
if (initializerContextClass != null) {
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.getName(),
contextClass.getName()));
}
initializerInstances.add(BeanUtils.instantiateClass(initializerClass));
}
ConfigurableEnvironment env = applicationContext.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment)env).initPropertySources(servletContext, null);
}
Collections.sort(initializerInstances, new AnnotationAwareOrderComparator());
for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : initializerInstances) {
initializer.initialize(applicationContext);
}
}
/**
* 1.具有默认实现(可能被子类覆盖)的模板方法,去加载或获取将作为根WebApplicationContext的父上下文的ApplicationContext实现
* 2.如果方法的返回ApplicationContext实现为空,则不设置父上下文。
*
* 3.加载父上下文的主要原因是允许多个根Web应用程序上下文都是共享EAR上下文的子项,或者交替地共享EJB可见的相同父上下文。
* 对于纯Web应用程序,通常不需要担心应用程序根上下文有一个父上下文对象。
* @param servletContext current servlet context
* @return the parent application context, or {@code null} if none
*/
protected ApplicationContext loadParentContext(ServletContext servletContext) {
ApplicationContext parentContext = null;
String locatorFactorySelector = servletContext.getInitParameter(LOCATOR_FACTORY_SELECTOR_PARAM);
// 父上下文引用的key
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;
}
/**
* 关闭web容器上下文servletContext的Spring的Web应用程序上下文
* @param servletContext the ServletContext that the WebApplicationContext runs in
*/
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();
}
}
}
/**
* 获取当前线程的Spring根Web应用程序上下文(即当前线程的上下文ClassLoader,它需要是Web应用程序的ClassLoader)
* @return the current root web application context, or {@code null}
* if none found
*/
public static WebApplicationContext getCurrentWebApplicationContext() {
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl != null) {
WebApplicationContext ccpt = currentContextPerThread.get(ccl);
if (ccpt != null) {
return ccpt;
}
}
return currentContext;
}
}