# SpringMVC的启动流程

SpringMVC的启动是建立在Servlet容器之上的，所有web工程的初始位置就是web.xml,它配置了servlet的上下文（context）、过滤器（Filter）和监听器（Listener）。Spring的启动流程其实就是Ioc容器的启动过程。一个WEB项目在Servlet容器中启动的时候会根据web.xml的配置执行对应的方法，因此Spring的启动流程可以选择配置在web.xml的Servlet或者Filter或者Listener上。不管是以何种方式启动容器，过程一般就是三步：

* 创建一个WebApplicationContext
* 配置并刷新Bean
* 将容器初始化到Servlet上下文中

## 使用Servlet控制容器的初始化

SpringMVC提供了DispatcherServlet来在容器启动的时候对Spring进行初始化，

```xml
<servlet>
  <servlet-name>springmvc</servlet-name>
  <servlet-class>
    org.springframework.web.servlet.DispatcherServlet
  </servlet-class>
  <!-- 表示启动容器时初始化此Servelt,调用init方法 -->
  <load-on-startup>1</load-on-startup>
  <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:springmvc.xml</param-value>
  </init-param>
</servlet>
<servlet-mapping>
  <servlet-name>springmvc</servlet-name>  <!-- 需要和上面的servlet-name保持一致 -->
  <url-pattern>/</url-pattern> <!-- url的匹配规则，/ 就是匹配所有 -->
</servlet-mapping>
```

因为配置了 `<load-on-startup>1</load-on-startup>`，所以在容器启动的时候会调用Servlet的init方法，追踪代码，可以看到是在DispatcherServlet的父类的父类HttpServletBean中实现了init方法。在init方法里对Spring容器进行了初始化操作。

```java
@Override
	public final void init() throws ServletException {
		if (logger.isDebugEnabled()) {
			logger.debug("Initializing servlet '" + getServletName() + "'");
		}

		// Set bean properties from init parameters.
		PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
		if (!pvs.isEmpty()) {
			try {
				BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
				ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
				bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
				initBeanWrapper(bw);
				bw.setPropertyValues(pvs, true);
			}
			catch (BeansException ex) {
				if (logger.isErrorEnabled()) {
					logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
				}
				throw ex;
			}
		}

		// Let subclasses do whatever initialization they like.
		initServletBean();

		if (logger.isDebugEnabled()) {
			logger.debug("Servlet '" + getServletName() + "' configured successfully");
		}
	}
```

## 使用Listener进行初始化

在web.xml中配置Listener来进行容器的初始化。

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

ContextLoaderListener实现了ServletContextListener的contextInitialized方法，所以在容器启动的时候会调用此方法，在此方法里面实现Spring容器的初始化。在contextInitialized中，通过调用父类（ContextLoader）的initWebApplicationContext方法进行容器创建：

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

下面来看initWebApplicationContext的代码：

```java
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项目时看到的日志信息: 
    servletContext.log("Initializing Spring root WebApplicationContext");
    if (logger.isInfoEnabled()) {
      logger.info("Root WebApplicationContext: initialization started");
    }
    long startTime = System.currentTimeMillis();

    try {
      //如果当前容器为null,则创建一个容器，并将servletContext上下文作为参数传递进去，
      if (this.context == null) {
        this.context = createWebApplicationContext(servletContext);
      }
       //判断当前容器是否为可配置的，只有是Configurable的容器，才能进行后续的配置
      if (this.context instanceof ConfigurableWebApplicationContext) {
        ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
        if (!cwac.isActive()) {
          if (cwac.getParent() == null) {
            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;
    }
  }
```

下面我们在看下**createWebApplicationContext**方法是如何创建WebApplicationContext的

```java
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这个方法:

```java
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);
      }
    }
  }
```

> 参考：
>
> <https://juejin.im/post/59a286866fb9a0249d616fbb>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://jun-wang.gitbook.io/learnjava/ji-shu-xue-xi/spring/springmvc-de-qi-dong-liu-cheng.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
