天天看點

springMVC源碼分析--容器初始化(一)ContextLoaderListener

在spring Web中,需要初始化IOC容器,用于存放我們注入的各種對象。當tomcat啟動時首先會初始化一個web對應的IOC容器,用于初始化和注入各種我們在web運作過程中需要的對象。當tomcat啟動的時候是如何初始化IOC容器的,我們先看一下在web.xml中經常看到的配置:

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

ContextLoaderListener是一個監聽器,其實作了ServletContextListener接口,其用來監聽Servlet,當tomcat啟動時會初始化一個Servlet容器,這樣

ContextLoaderListener會監聽到Servlet的初始化,這樣在Servlet初始化之後我們就可以在

ContextLoaderListener中也進行一些初始化操作。看下面的

ServletContextListener的源碼也是比較簡單的,

ContextLoaderListener實作了ServletContextListener接口,是以會有兩個方法contextInitialized和contextDestroyed。web容器初始化時會調用方法

contextInitialized,web容器銷毀時會調用方法

contextDestroyed。

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {

  public ContextLoaderListener() {
  }

  public ContextLoaderListener(WebApplicationContext context) {
    super(context);
  }
  /**
   * Initialize the root web application context.
   */
  @Override
  public void contextInitialized(ServletContextEvent event) {
    //在父類ContextLoader中實作
    initWebApplicationContext(event.getServletContext());
  }
  /**
   * Close the root web application context.
   */
  @Override
  public void contextDestroyed(ServletContextEvent event) {
    closeWebApplicationContext(event.getServletContext());
    ContextCleanupListener.cleanupAttributes(event.getServletContext());
  }

}      

ContextLoaderListener的方法contextInitialized()的預設實作是在他的父類ContextLoader的initWebApplicationContext方法中實作的,意思就是初始化web應用上下文。他的主要流程就是建立一個IOC容器,并将建立的IOC容器存到servletContext中,ContextLoader的核心實作如下:

initWebApplicationContext函數:

//初始化WebApplicationContext,IOC容器
  public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    //判斷web容器中是否已經有WebApplicationContext
    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.
      //建立WebApplicationContext
      if (this.context == null) {
        //最終得到的是XmlWebApplicationContext
        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);
          }
          //設定并重新整理WebApplicationContext容器
          configureAndRefreshWebApplicationContext(cwac, servletContext);
        }
      }
      //将初始化的WebApplicationContext設定到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");
      }
      //初始化 WebApplicationContext完成并傳回
      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;
    }
  }      

springMVC父容器初始化流程圖(盜圖)如下:

springMVC源碼分析--容器初始化(一)ContextLoaderListener

完整的ContextLoader源碼如下:

/**
 * 用于初始化ApplicationContext
 *
 */
public class ContextLoader {

  //WebApplicationContext id
  public static final String CONTEXT_ID_PARAM = "contextId";
  
  //配置檔案
  public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";

  public static final String CONTEXT_CLASS_PARAM = "contextClass";

  public static final String CONTEXT_INITIALIZER_CLASSES_PARAM = "contextInitializerClasses";

  
  public static final String GLOBAL_INITIALIZER_CLASSES_PARAM = "globalInitializerClasses";

  
  public static final String LOCATOR_FACTORY_SELECTOR_PARAM = "locatorFactorySelector";

  public static final String LOCATOR_FACTORY_KEY_PARAM = "parentContextKey";

  private static final String INIT_PARAM_DELIMITERS = ",; \t\n";

  private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";


  private static final Properties defaultStrategies;

  static {
    // Load default strategy implementations from properties file.
    // This is currently strictly internal and not meant to be customized
    // by application developers.
    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;

  private final List<ApplicationContextInitializer<ConfigurableApplicationContext>> contextInitializers =
      new ArrayList<ApplicationContextInitializer<ConfigurableApplicationContext>>();

  public ContextLoader() {
  }

  public ContextLoader(WebApplicationContext context) {
    this.context = context;
  }

  @SuppressWarnings("unchecked")
  public void setContextInitializers(ApplicationContextInitializer<?>... initializers) {
    if (initializers != null) {
      for (ApplicationContextInitializer<?> initializer : initializers) {
        this.contextInitializers.add((ApplicationContextInitializer<ConfigurableApplicationContext>) initializer);
      }
    }
  }

  //初始化WebApplicationContext,IOC容器
  public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    //判斷web容器中是否已經有WebApplicationContext
    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.
      //建立WebApplicationContext
      if (this.context == null) {
        //最終得到的是XmlWebApplicationContext
        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);
          }
          //設定并重新整理WebApplicationContext容器
          configureAndRefreshWebApplicationContext(cwac, servletContext);
        }
      }
      //将初始化的WebApplicationContext設定到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");
      }
      //初始化 WebApplicationContext完成并傳回
      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;
    }
  }

  
  //在web容器加載的時候初始化根WebApplicationContext
  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() + "]");
    }
    //初始化WebApplicationContext,是其實作類XmlWebApplicationContext
    return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
  }

  
  protected Class<?> determineContextClass(ServletContext servletContext) {
    //檢視servlet是否已經存在應用上下文類名
    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 {
      //如果應用上下文類名為空,則獲得預設的context上下文名
      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);
      }
    }
  }

  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);
      //設定應用上下文id,在web.xml中可以進行配置
      if (idParam != null) {
        wac.setId(idParam);
      }
      else {
        // Generate default id...
        //如果沒有配置則設定預設名稱
        wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
            ObjectUtils.getDisplayString(sc.getContextPath()));
      }
    }
    //設定ServletContext到容器中
    wac.setServletContext(sc);
    //獲得web.xml中配置的contextConfigLocation的值,一般是classpath:applicationContext.xml
    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);
    //這一步是特别關鍵的,這樣application.xml中的所有配置都會被初始化到XMLWebApplication中
    wac.refresh();
  }

  protected void customizeContext(ServletContext sc, ConfigurableWebApplicationContext wac) {
    List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> initializerClasses =
        determineContextInitializerClasses(sc);

    for (Class<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerClass : initializerClasses) {
      Class<?> initializerContextClass =
          GenericTypeResolver.resolveTypeArgument(initializerClass, ApplicationContextInitializer.class);
      if (initializerContextClass != null) {
        Assert.isAssignable(initializerContextClass, wac.getClass(), String.format(
            "Could not add context initializer [%s] since its generic parameter [%s] " +
            "is not assignable from the type of application context used by this " +
            "context loader [%s]: ", initializerClass.getName(), initializerContextClass.getName(),
            wac.getClass().getName()));
      }
      this.contextInitializers.add(BeanUtils.instantiateClass(initializerClass));
    }

    AnnotationAwareOrderComparator.sort(this.contextInitializers);
    for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) {
      initializer.initialize(wac);
    }
  }

  protected List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>
      determineContextInitializerClasses(ServletContext servletContext) {

    List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> classes =
        new ArrayList<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>();

    String globalClassNames = servletContext.getInitParameter(GLOBAL_INITIALIZER_CLASSES_PARAM);
    if (globalClassNames != null) {
      for (String className : StringUtils.tokenizeToStringArray(globalClassNames, INIT_PARAM_DELIMITERS)) {
        classes.add(loadInitializerClass(className));
      }
    }

    String localClassNames = servletContext.getInitParameter(CONTEXT_INITIALIZER_CLASSES_PARAM);
    if (localClassNames != null) {
      for (String className : StringUtils.tokenizeToStringArray(localClassNames, INIT_PARAM_DELIMITERS)) {
        classes.add(loadInitializerClass(className));
      }
    }

    return classes;
  }

  @SuppressWarnings("unchecked")
  private Class<ApplicationContextInitializer<ConfigurableApplicationContext>> loadInitializerClass(String className) {
    try {
      Class<?> clazz = ClassUtils.forName(className, ClassUtils.getDefaultClassLoader());
      Assert.isAssignable(ApplicationContextInitializer.class, clazz);
      return (Class<ApplicationContextInitializer<ConfigurableApplicationContext>>) clazz;
    }
    catch (ClassNotFoundException ex) {
      throw new ApplicationContextException("Failed to load context initializer class [" + className + "]", ex);
    }
  }

  
  //從ServletContext中擷取父容器
  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;
  }

  
  //通過給定的ServletContext來關閉IOC容器
  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();
      }
    }
  }

  //在目前線程中擷取WebApplicationContext
  public static WebApplicationContext getCurrentWebApplicationContext() {
    ClassLoader ccl = Thread.currentThread().getContextClassLoader();
    if (ccl != null) {
      WebApplicationContext ccpt = currentContextPerThread.get(ccl);
      if (ccpt != null) {
        return ccpt;
      }
    }
    return currentContext;
  }

}