Spring原始碼系列:Spring的啟動過程

glmapper發表於2017-08-27

Spring對於程式設計師說來說都不陌生;作為一個強大的開源技術,幫助我們能夠更好的進行專案的開發與維護。 直接進入主題吧。Spring的啟動過程實際上就是Ioc容器初始化以及載入Bean的過程;本文主要是學習記錄下前半部分(Ioc容器的初始化),新手上路,如有錯誤,請指正! 1.從配置檔案說起

<listener>  
     <listener-class>org.springframework.web.context.ContextLoaderListener
     </listener-class>  
</listener> 
<context-param>  
    <param-name>contextConfigLocation</param-name>  
    <param-value>classpath:applicationContext.xml</param-value>  
</context-param> 
複製程式碼

在一般的WEB專案中,專案的啟動一般是從web.xml檔案的載入開始的。如果我們的專案中使用了Spring,那麼你肯定會在你的web.xml檔案中看到上面的配置。Spring正是通過ContextLoaderListener監聽器來進行容器初始化的。下面通過程式碼進行分析。

2.Spring容器載入的三步走

  • step1:建立一個WebApplicationContext
  • step2:配置並且重新整理Bean
  • step3:將容器初始化到servlet上下文中

3.WebApplicationContext的建立過程

public class ContextLoaderListener extends ContextLoader implements ServletContextListener 
複製程式碼

從ContextLoaderListener的定義可以看出,該監聽器繼承了ContextLoader,並且重寫了ServletContextListener中的contextInitialized和contextDestroyed方法。

在contextInitialized中,通過呼叫父類(ContextLoader)的initWebApplicationContext方法進行容器建立:

@Override
public void contextInitialized(ServletContextEvent event) {
    initWebApplicationContext(event.getServletContext());
}
複製程式碼

下面來看initWebApplicationContext的程式碼:

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    //1:判斷當前容器是否存在,如果存在則報容器已經存在的異常資訊
    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);
    //下面這個日誌就是我們經常在啟動Spring專案時看到的日誌資訊: 
    //Initializing Spring root WebApplicationContext
    //Root WebApplicationContext: initialization started
    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.
      //如果當前容器為null,則建立一個容器,並將servletContext上下文作為引數傳遞進去,
      if (this.context == null) {
        this.context = createWebApplicationContext(servletContext);
      }
       //判斷當前容器是否為可配置的,只有是Configurable的容器,才能進行後續的配置
      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);
        }
      }
       //將配置並且重新整理過的容器存入servlet上下文中,並以WebApplicationContext的類名作為key值

      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的

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
    //首先來確定context是由什麼類定義的,並且判斷當前容器是否為可配置的
    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);
  }
複製程式碼

最後來看下determineContextClass這個方法

protected Class<?> determineContextClass(ServletContext servletContext) {
    //首先從web.xml中檢視使用者是否自己定義了context
    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);
      }
    }
    /*如果沒有,則去defaultStrategies裡面取【defaultStrategies是Propertites類的/物件,在ContextLoader中的靜態程式碼塊中初始化的;具體可看下下面的影像】;預設容器是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);
      }
    }
  }
複製程式碼

總的來說就是:Spring的web工程首先回去檢查使用者是否自己定義了context,如果有就採用;如果沒有就使用Spring預設的。 defaultStrategies初始化:

Spring原始碼系列:Spring的啟動過程

至此,容器建立完成。下面是整個過程的一個流程圖(有疏漏,回頭補一個時序圖):

Spring原始碼系列:Spring的啟動過程

相關文章