springMVC(12) ContextLoaderListener

前言

我們打開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

  1. 首先,對于一個(gè)web應(yīng)用,其部署在web容器中,web容器提供其一個(gè)全局的上下文環(huán)境,這個(gè)上下文就是ServletContext,其為后面的spring IoC容器提供宿主環(huán)境;
  2. 其次,在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中,便于獲取;
  3. 再次,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)。

ContextLoaderListener類圖

簡單地先說一下這個(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è)流程:

  1. 判斷ContextLoader是否存在沖突
  2. 創(chuàng)建WebApplicationContext
  3. 設(shè)置WebApplicationContext的父上下文
  4. 初始化WebApplicationContext
  5. 向servletContext的ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE屬性設(shè)置WebApplicationContext,以便于可以直接從servletContext中進(jìn)行訪問
  6. 本地實(shí)例變量存儲context
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容