前言
我們打開springMVC工程的web.xml,我們可以發(fā)現(xiàn)這樣一行配置
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
這個(gè)監(jiān)聽器就是用來初始化spring容器的。
springMVC啟動(dòng)過程
摘自https://segmentfault.com/q/1010000000210417
- 首先,對于一個(gè)web應(yīng)用,其部署在web容器中,web容器提供其一個(gè)全局的上下文環(huán)境,這個(gè)上下文就是ServletContext,其為后面的spring IoC容器提供宿主環(huán)境;
- 其次,在web.xml中會(huì)提供有contextLoaderListener。在web容器啟動(dòng)時(shí),會(huì)觸發(fā)容器初始化事件,此時(shí)contextLoaderListener會(huì)監(jiān)聽到這個(gè)事件,其contextInitialized方法會(huì)被調(diào)用,在這個(gè)方法中,spring會(huì)初始化一個(gè)啟動(dòng)上下文,這個(gè)上下文被稱為根上下文,即WebApplicationContext,這是一個(gè)接口類,確切的說,其實(shí)際的實(shí)現(xiàn)類是XmlWebApplicationContext。這個(gè)就是spring的IoC容器,其對應(yīng)的Bean定義的配置由web.xml中的context-param標(biāo)簽指定。在這個(gè)IoC容器初始化完畢后,spring以WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE為屬性Key,將其存儲到ServletContext中,便于獲取;
- 再次,contextLoaderListener監(jiān)聽器初始化完畢后,開始初始化web.xml中配置的Servlet,這個(gè)servlet可以配置多個(gè),以最常見的DispatcherServlet為例,這個(gè)servlet實(shí)際上是一個(gè)標(biāo)準(zhǔn)的前端控制器,用以轉(zhuǎn)發(fā)、匹配、處理每個(gè)servlet請求。DispatcherServlet上下文在初始化的時(shí)候會(huì)建立自己的IoC上下文,用以持有spring mvc相關(guān)的bean。在建立DispatcherServlet自己的IoC上下文時(shí),會(huì)利用WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE先從ServletContext中獲取之前的根上下文(即WebApplicationContext)作為自己上下文的parent上下文。有了這個(gè)parent上下文之后,再初始化自己持有的上下文。這個(gè)DispatcherServlet初始化自己上下文的工作在其initStrategies方法中可以看到,大概的工作就是初始化處理器映射、視圖解析等。這個(gè)servlet自己持有的上下文默認(rèn)實(shí)現(xiàn)類也是mlWebApplicationContext。初始化完畢后,spring以與servlet的名字相關(guān)(此處不是簡單的以servlet名為Key,而是通過一些轉(zhuǎn)換,具體可自行查看源碼)的屬性為屬性Key,也將其存到ServletContext中,以便后續(xù)使用。這樣每個(gè)servlet就持有自己的上下文,即擁有自己獨(dú)立的bean空間,同時(shí)各個(gè)servlet共享相同的bean,即根上下文(第2步中初始化的上下文)定義的那些bean。
從這段話中我們可以知道ContextLoaderListener的作用。ContextLoaderListener主要實(shí)現(xiàn)了ServletContextListener接口中的兩個(gè)函數(shù)contextInitialized和contextDestroyed,分別在web容器啟動(dòng)時(shí)執(zhí)行和容器銷毀時(shí)執(zhí)行。
類圖
我們可以很容易地畫出ContextLoaderListener類的結(jié)構(gòu)。

簡單地先說一下這個(gè)類圖。其中ContextLoaderListener實(shí)現(xiàn)了ServletContextListener,而真正的具體實(shí)現(xiàn)方式確實(shí)交給ContextLoader來完成的。
EventListener接口
public interface EventListener {
}
我們可以看到EventListener接口中沒有任何東西。那么很顯然EventListener只是一個(gè)標(biāo)記接口,就類似于Serializable一樣。所有的時(shí)間監(jiān)聽器接口都必須擴(kuò)展該接口。
ServletContextListener接口
public interface ServletContextListener extends EventListener {
// 在web容器初始化時(shí)執(zhí)行。會(huì)在filter和servlet初始化之前執(zhí)行
public void contextInitialized ( ServletContextEvent sce );
// 在web容器關(guān)閉時(shí)執(zhí)行。會(huì)在所有的servlets和filters執(zhí)行destroy之后執(zhí)行
public void contextDestroyed ( ServletContextEvent sce );
}
ContextLoaderListener類
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
public ContextLoaderListener() {
}
public ContextLoaderListener(WebApplicationContext context) {
super(context);
}
// 初始化根WebApplicationContext
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
// 關(guān)閉根WebApplicationContext
@Override
public void contextDestroyed(ServletContextEvent event) {
closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
此處印證了前面類圖那處所說的,最終真正的實(shí)現(xiàn)是由ContextLoader來做的。
ContextLoader類
首先從ContextLoaderListener類中contextInitialized調(diào)用的initWebApplicationContext方法開始看起。
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
// 判斷web.xml中是否存在不止一個(gè)ContextLoader
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 {
// 創(chuàng)建WebApplicationContext
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) { // cwac還沒有刷新過
// 設(shè)置父上下文
if (cwac.getParent() == null) {
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
// 初始化WebApplicationContext
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
// 將this.context設(shè)置為servletContext的ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE屬性,這樣就可以直接通過servletContext中獲取訪問了
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
// 本地實(shí)例變量中存儲Context,保證哪怕ServletContext關(guān)閉了也可以訪問。暫不清楚為何要以下面的形式來實(shí)現(xiàn)
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;
}
}
我們來看看其中的createWebApplicationContext這一步,
// createWebApplicationContext方法
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
Class<?> contextClass = determineContextClass(sc);
// 判斷contextClass是否ConfigurableWebApplicationContext的子類
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);
}
// determineContextClass方法
protected Class<?> determineContextClass(ServletContext servletContext) {
String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
// 使用用戶指定的contextClass
if (contextClassName != null) {
try {
return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException(
"Failed to load custom context class [" + contextClassName + "]", ex);
}
}
// 使用默認(rèn)的WebApplicationContext,即XmlWebApplicationContext
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);
}
}
}
// defaultStrategies的定義和初始化部分
// defaultStrategies中最終只有這樣一條屬性
// org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
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.properties文件
org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
這樣子的話,我們梳理一下整個(gè)流程:
- 判斷ContextLoader是否存在沖突
- 創(chuàng)建WebApplicationContext
- 設(shè)置WebApplicationContext的父上下文
- 初始化WebApplicationContext
- 向servletContext的ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE屬性設(shè)置WebApplicationContext,以便于可以直接從servletContext中進(jìn)行訪問
- 本地實(shí)例變量存儲context