Spring source series: Spring's startup process

Spring is no stranger to programmers; as a powerful open source technology, it helps us to better develop and maintain projects. Go straight to the topic. The startup process of Spring is actually the process of Ioc container initialization and bean loading; this article is mainly to learn and record the first half (initialization of Ioc container), newbies are on the road, please correct me if there are any mistakes! 1. Start with the configuration file

<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> 

In a general WEB project, the startup of the project generally starts from the loading of the web.xml file. If we are using Spring in our project, then you will definitely see the above configuration in your web.xml file. Spring does container initialization through the ContextLoaderListener listener. The code is analyzed below.

2. Three steps for Spring container loading

  • step1: Create a WebApplicationContext
  • step2: configure and refresh the bean
  • step3: Initialize the container into the servlet context

3. The creation process of WebApplicationContext

public class ContextLoaderListener extends ContextLoader implements ServletContextListener 

As can be seen from the definition of ContextLoaderListener, the listener inherits ContextLoader and overrides the contextInitialized and contextDestroyed methods in ServletContextListener.

In contextInitialized, the container is created by calling the initWebApplicationContext method of the parent class (ContextLoader):

@Override
public void contextInitialized(ServletContextEvent event) {
    initWebApplicationContext(event.getServletContext());
}

Let's look at the code of 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;
    }
  }

Let's take a look at how to create a 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);
  }

Finally, let's take a look at the method 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);
      }
    }
  }

In general, Spring's web project first goes back to check whether the user has defined the context, if so, use it; if not, use Spring's default. defaultStrategies initialization:

At this point, the container creation is complete. The following is a flow chart of the whole process (there are omissions, go back and make up a sequence diagram):



Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324772932&siteId=291194637