前言:
1,如果使用自定义的监听器,需要经过下面的步骤 1到步骤10
2,如果使用Spring自己的监听器ContextLoaderListener,需要经过下面的步骤6到步骤10
3,web.xml中的加载顺序:context-param -> listener -> filter -> servlet
上下文加载步骤分析:
1,在WebService的 web.xml可以定义一个监听器:WebContextLoaderListener:
<listener>
<listener-class>com.dangdang.ddframe.web.WebContextLoaderListener</listener-class>
</listener>
WebContextLoaderListener类:
package com.dangdang.ddframe.web;
public final class WebContextLoaderListener extends ContextLoaderListener {
private static ContainerInitializer containerInitializer = ContainerInitializer.getInstance();
@Override
public void contextInitialized(final ServletContextEvent event) {
containerInitializer.startContainer(new AbstractInitializeCallbackAdapter() {
@Override
public ApplicationContext doInitialize() {
ServletContext servletContext = event.getServletContext();
servletContext.setInitParameter(ContextLoader.CONFIG_LOCATION_PARAM, SpringContainer.CONFIG_FILE);
WebContextLoaderListener.super.contextInitialized(event);
return WebApplicationContextUtils.getWebApplicationContext(servletContext);
}
});
}
}
ContainerInitializer在初始化时,初始化了参数加载器,读取了日志级别。
2,因为WebContextLoaderListener继承了Spring的ContextLoaderListener类,ContextLoaderListener类是在容器启动时加载spring配置信息用的,web容器启动时,会加载contextInitialized()方法,在这里加载的就是WebContextLoaderListener类的contextInitialized()方法。
3,contextInitialized()方法调用了ContainerInitializer的startContainer()方法,参数是一个AbstractInitializeCallbackAdapter类,先看一下这个AbstractInitializeCallbackAdapter类:
package com.dangdang.ddframe.container.spring.initialize;
public abstract class AbstractInitializeCallbackAdapter implements InitializeCallback {
@Override
public void logSuccess(final Logger log) {
log.info("--- dd-frame container started success. ---");
}
@Override
public void logFailure(final Logger log, final SystemException cause) {
log.error("--- dd-frame init failure. ---", cause);
}
}
其实就是定义了启动成功时候的日志和启动失败时候的日志,他实现的接口InitializeCallback定义了这两个方法,还有启动容器的doInitialize()方法。
4,现在开始看ContainerInitializer的startContainer()方法:
package com.dangdang.ddframe.container.spring.initialize;
public void startContainer(final InitializeCallback initializeCallback) {
prepareSystemProperties();
ApplicationContext context = null;
try {
context = initializeCallback.doInitialize();
// CHECKSTYLE:OFF
} catch (final Exception ex) {
// CHECKSTYLE:ON
logAndThrow(initializeCallback, ex);
}
for (Entry<String, InitializeValidator> entry : context.getBeansOfType(InitializeValidator.class).entrySet()) {
try {
entry.getValue().validate();
} catch (final InitializeValidatorException ex) {
logAndThrow(initializeCallback, ex);
}
}
ContextHolder.initInstance(context);
initializeCallback.logSuccess(LOG);
LOG_CONFIG.setLevel(CLASSPATH_PROP_LOADER.getClasspathProperties().getProperty(LogConfig.RUNNING_LOG_LEVEL_KEY));
}
5,prepareSystemProperties();方法是把配置文件中的配置项放到java.lang.System中。
然后调用了InitializeCallback的doInitialize()方法,就是在WebContextLoaderListener之中写的那一部分:
@Override
public ApplicationContext doInitialize() {
ServletContext servletContext = event.getServletContext();
servletContext.setInitParameter(ContextLoader.CONFIG_LOCATION_PARAM,SpringContainer.CONFIG_FILE);
WebContextLoaderListener.super.contextInitialized(event);
return WebApplicationContextUtils.getWebApplicationContext(servletContext);
}
首先设置了配置文件路径:把ContextLoader.CONFIG_LOCATION_PARAM设置为SpringContainer.CONFIG_FILE,这个路径是可配置的,这里配置为:”classpath:META-INF/dd-frame/spring/root/applicationContext.xml”。
然后,在这个方法中调用了父类的contextInitialized()方法,也就是org.springframework.web.context .ContextLoaderListener的contextInitialized()方法:
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
6,其中调用的initWebApplicationContext()方法在他的父类org.springframework.web.context.ContextLoader中:
package org.springframework.web.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;
}
}
7,这个方法就是把spring配置文件中的内容加载到上下文的方法,初始化时this.context为null,调用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() + "]");
}
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
8,方法的第一行调用determineContextClass()方法,返回了ConfigurableWebApplicationContext的Class类,这个方法有很多层,看到最后是调用了ContextLoader.properties配置文件,这个配置文件在Spring的包中是和ContextLoader.class文件放在一起的,没有开放给用户使用,不可配置,这个配置文件内容如下:
# 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的上下文实际上默认是用XmlWebApplicationContext来初始化的。注释里也写了,没打算开放给开发人员去配置。
9,回头看步骤7,在得到Class类后,最后一行调用BeanUtils.instantiateClass()方法进行初始化,并返回。
10,继续看步骤6中的代码,在刚才调用createWebApplicationContext()方法得到对象后,调用configureAndRefreshWebApplicationContext()方法来加载上下文配置:
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...
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getContextPath()));
}
}
wac.setServletContext(sc);
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
wac.setConfigLocation(configLocationParam);
}
// The wac environment's #initPropertySources will be called in any case when the context
// is refreshed; do it eagerly here to ensure servlet property sources are in place for
// use in any post-processing or initialization that occurs below prior to #refresh
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
}
customizeContext(sc, wac);
wac.refresh();
}
11,代码最后的refresh()方法,实际调用的是之前初始化的XmlWebApplicationContext的refresh()方法,这个方法的实现在他的祖辈中,这个类的继承关系是:
org.springframework.web.context.support.XmlWebApplicationContext
继承
org.springframework.web.context.support.AbstractRefreshableWebApplicationContext
继承
org.springframework.context.support.AbstractRefreshableConfigApplicationContext
继承
org.springframework.context.support.AbstractRefreshableApplicationContext
继承
org.springframework.context.support.AbstractApplicationContext
refresh()方法在AbstractApplicationContext类中:
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
logger.warn("Exception encountered during context initialization - cancelling refresh attempt", ex);
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
}
}
可以看到refresh()方法初始化了一个BeanFactory,这个方法进行了一系列对BeanFactory的初始化和装配工作,每个子方法里都进行了大量的操作,直到finishBeanFactoryInitialization()方法,初始化了所有非懒加载的单例bean的实例。
至此spring的上下文加载完成。