Spring初始化 - ContextLoader

一.概述

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处理。

三.无注释源码

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;
	}

}

五.流程图






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值