# DispatcherServlet的初始化原理

首先咱先研究一下 `DispatcherServlet` 的初始化阶段，都干了哪些工作，下一章我们再研究程序运行期间，`DispatcherServlet` 如何处理请求并响应的。

## 1. 基于web.xml的DispatcherServlet初始化

前面我们在刚刚开始学习 WebMvc 的时候，我们只配置了两样东西：一个 `web.xml` ，一个 `spring-mvc.xml` 。`web.xml` 中注册了 `DispatcherServlet` ，`spring-mvc.xml` 负责初始化 `DispatcherServlet` 。那问题就来了，`DispatcherServlet` 在初始化的时候都干了什么，这是我们要研究的东西了。

### 1.1 init

我们都知道，一切 `Servlet` 的生命周期，都是从 `init` 方法开始，`DispatcherServlet` 也不例外。借助 IDE 往上翻，可以发现 `DispatcherServlet` 的 `init` 方法在父类 `HttpServletBean` 中重写了：

```java
@Override
public final void init() throws ServletException {
    // Set bean properties from init parameters.
    // ......
    // Let subclasses do whatever initialization they like.
    initServletBean();
}
```

上面的初始化逻辑我们不关心了，关键的点在于下面的这个模板方法 `initServletBean` ，这个才是关键！

`initServletBean` 方法要来到 `DispatcherServlet` 的直接父类 `FrameworkServlet` 中，这里有初始化的逻辑：

```java
@Override
protected final void initServletBean() throws ServletException {
    // log ......
    long startTime = System.currentTimeMillis();

    try {
        this.webApplicationContext = initWebApplicationContext();
        initFrameworkServlet();
    } // catch ......

    // logger ......
}
```

刨去超多的日志打印不看，它的核心就两句话：**一个初始化 IOC 容器，一个初始化 Servlet 本身**。由于 `initFrameworkServlet` 方法中没有任何逻辑，`DispatcherServlet` 也没有重写它，所以核心还是初始化 IOC 容器。下面咱进入 `initWebApplicationContext` 方法。

### 1.2 initWebApplicationContext

初始化 `DispatcherServlet` 之前，先把属于它的 IOC 容器先初始化出来，以此来形成父子容器，这部分的源码比较长，小册只截取最关键的部分：

```java
protected WebApplicationContext initWebApplicationContext() {
    // 先获取根容器（通过ContextLoaderListener初始化的IOC容器）
    WebApplicationContext rootContext =
            WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;

    // 该分支用于处理DispatcherServlet构造时set过ApplicationContext的情况，一般不会遇到
    if (this.webApplicationContext != null) {
        // A context instance was injected at construction time -> use it
        wac = this.webApplicationContext;
        if (wac instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
            if (!cwac.isActive()) {
                if (cwac.getParent() == null) {
                    cwac.setParent(rootContext);
                }
                configureAndRefreshWebApplicationContext(cwac);
            }
        }
    }
    // ......
    // 如果没有WebApplicationContext，则会创建一个全新的WebApplicationContext
    if (wac == null) {
        wac = createWebApplicationContext(rootContext);
    }
    // ......
    // 将该WebApplicationContext放入ServletContext中
    if (this.publishContext) {
        // Publish the context as a servlet context attribute.
        String attrName = getServletContextAttributeName();
        getServletContext().setAttribute(attrName, wac);
    }
    return wac;
}
```

纵观整段源码，在第一次初始化的时候很明显上面的乱七八糟分支都是不走的，它会走中间偏下部分的 createWebApplicationContext 方法，在此初始化 IOC 容器。我们继续跟进源码：

#### 1.2.1 createWebApplicationContext

进来 createWebApplicationContext 方法，发现它继续往下调用重载的方法：

```java
protected WebApplicationContext createWebApplicationContext(@Nullable WebApplicationContext parent) {
    return createWebApplicationContext((ApplicationContext) parent);
}
```

继续往下，发现这里面有正儿八经的逻辑了，咱先把这段源码大体浏览一下，这里面有一些关键的环节，我们需要着重去看。

```java
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
    // 加载IOC容器的承载实现类
    Class<?> contextClass = getContextClass();
    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
        throw ex ......;
    }
    // 反射实例化
    ConfigurableWebApplicationContext wac =
            (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

    wac.setEnvironment(getEnvironment());
    wac.setParent(parent);
    // 获取、设置配置文件的路径
    String configLocation = getContextConfigLocation();
    if (configLocation != null) {
        wac.setConfigLocation(configLocation);
    }
    // 刷新IOC容器 - refresh
    configureAndRefreshWebApplicationContext(wac);

    return wac;
}
```

有了注释，看起来这段代码是不是非常简单了，核心就三步：**实例化 IOC 容器、设置配置文件路径配置、刷新 IOC 容器**。

不过这里面有很多细节，我们逐一来看。

#### 1.2.2 IOC容器的真正落地实现

首先上面的第一行，`getContextClass` 方法，它会获取到 `WebApplicationContext` 的真正落地实现，跳转进该方法，会发现它引用的是一个叫 `XmlWebApplicationContext` 的家伙：

```java
public static final Class<?> DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;
private Class<?> contextClass = DEFAULT_CONTEXT_CLASS;

public Class<?> getContextClass() {
    return this.contextClass;
}
```

是不是感觉有点似曾相识？之前，我们在 IOC 进阶，介绍 `BeanFactory` 的实现类中，提到过一个 `XmlBeanFactory` ，咱当时在那里说，`XmlBeanFactory` 这个东西不要去看了，因为已经没有在用了。但这个 `XmlWebApplicationContext` 不一样，它跟 `ClassPathXmlApplicationContext` 是差不多的，只是一个在 Web 环境下使用，一个主要在普通项目环境中使用。

接下来的配置文件路径的获取，就是利用 `XmlWebApplicationContext` 的东西，咱继续往下来看。

#### 1.2.3 获取配置文件的路径

`String configLocation = getContextConfigLocation();` 这一句代码中，获取的就是我们之前在 `web.xml` 中配置的那个 `**contextConfigLocation**` 属性，配置了它，这里就能拿到配置文件的路径；如果不配置的话，它也有一套默认规则，这个默认规则我们可以在 `XmlWebApplicationContext` 中看到一个重写的 `getDefaultConfigLocations` 方法：

```java
public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml";
public static final String DEFAULT_CONFIG_LOCATION_PREFIX = "/WEB-INF/";
public static final String DEFAULT_CONFIG_LOCATION_SUFFIX = ".xml";

protected String[] getDefaultConfigLocations() {
    if (getNamespace() != null) {
        return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX};
    } else {
        return new String[] {DEFAULT_CONFIG_LOCATION};
    }
}
```

可见这个默认的规则，就是我们在 68 章，SpringWebMvc 的入门中提到的 `/WEB-INF/{servlet-name}-servlet.xml` ，这里提到的 `getNamespace()` 获取到的，是刚才在 `FrameworkServlet` 中定义的一个方法：

```java
public static final String DEFAULT_NAMESPACE_SUFFIX = "-servlet";

public String getNamespace() {
    return (this.namespace != null ? this.namespace : getServletName() + DEFAULT_NAMESPACE_SUFFIX);
}
```

所以，看到了这个套路了吧，把 `Servlet` 的 name 拿过来，后缀拼上 `-servlet` ，这样就形成配置文件的路径了。

至于这个配置文件在哪里用，咱下面会遇到的，莫慌莫着急。

#### 1.2.4 刷新WebAppliCationContext

当上面的 `WebAppliCationContext` 都配置好之后，下面的最关键步骤，就是刷新 IOC 容器了。来到 `configureAndRefreshWebApplicationContext` 方法，这里面又是比较长，不过不太值得分段看了，咱总体扫一眼吧，看看里面的关键步骤：

```java
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
    // 设置IOC容器的id，太长略过

    // 常规设置一些web的东西
    wac.setServletContext(getServletContext());
    wac.setServletConfig(getServletConfig());
    // 此处就是上面的getNamespace方法
    wac.setNamespace(getNamespace());
    // 这里添加了一个上下文刷新的监听器
    wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

    ConfigurableEnvironment env = wac.getEnvironment();
    if (env instanceof ConfigurableWebEnvironment) {
        ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
    }

    postProcessWebApplicationContext(wac);
    applyInitializers(wac);
    // 刷新IOC容器
    wac.refresh();
}
```

看这里面的逻辑，总体还都是挺容易理解的吧！很明显，对于一个 `WebAppliCationContext` 而言，它应该持有 Servlet 相关的东西，所以上面的部分很理所当然。

中间它添加了一个 `ContextRefreshListener` ，注意它来自 `spring-webmvc` 包，它就是监听我们之前在事件的章节中学习的 `ContextRefreshedEvent` 事件！至于这个监听器都干了什么，接下来咱马上就会说到。

最底下，它执行 IOC 容器的 `refresh` 方法，那它就会去加载、解析配置文件，以及初始化的逻辑等等等等。

注意一点！**此时** `**WebAppliCationContext**` **中是没有** `**DispatcherServlet**` **的**！所以 IOC 容器的初始化，是无法控制和操作 `DispatcherServlet` 的，加上咱上面说到了，`initFrameworkServlet` 方法里面没有任何初始化逻辑，所以如果 `DispatcherServlet` 还需要执行额外的初始化逻辑，那就不得不使用其他的方案来触发。

那咋触发呢？刚才不是刚刚说到一个监听器嘛，这个 `ContextRefreshListener` ，就是让 IOC 容器重新跟 `DispatcherServlet` 打交道的 “桥梁” 。

### 1.3 ContextRefreshListener

在进入监听器的源码之前，先想一个问题。

#### 1.3.0 前置思考：此时 IOC 容器初始化到哪一步了？

此时正在执行的，是 `ContextRefreshedEvent` 事件的发布动作吧！**此时第 11 步** `**finishBeanFactoryInitialization**` **方法已经执行完毕了吧，IOC 容器中的 bean 也都初始化完成了吧！（** `**ContextRefreshedEvent**` **事件的发布，是在第 12 步** `**finishRefresh**` **方法中广播的）**

所以，当 `ContextRefreshedEvent` 事件广播的时候，我们就应该知道，此时所有的 bean 都准备就绪。但是！因为我们在之前的初体验中，根本什么东西都没有注册，甚至连一个 `<mvc:annotation-driven />` 标签，或者 `@EnableWebMvc` 注解都没有写，所以**此时 IOC 容器中应该只有一个** `**InternalResourceViewResolver**` **，其余的什么** `**HandlerMapping**` **、**`**HandlerAdapter**` **统统都没有！**

那问题就来了，这些东西都没有注册，咋就好使了呢？

好，带着这个问题，我们可以来看 `ContextRefreshListener` 的源码了。

#### 1.3.1 ContextRefreshListener的源码

```java
private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        FrameworkServlet.this.onApplicationEvent(event);
    }
}
```

这个 `ContextRefreshListener` 定义在 `FrameworkServlet` 的内部，而正是因为定义在内部，所以刚好能拿到 `DispatcherServlet` 的实例。这个监听器的触发调用的逻辑，就是 `FrameworkServlet` 的 `onApplicationEvent` 方法：

```java
public void onApplicationEvent(ContextRefreshedEvent event) {
    this.refreshEventReceived = true;
    synchronized (this.onRefreshMonitor) {
        onRefresh(event.getApplicationContext());
    }
}

protected void onRefresh(ApplicationContext context) {
    // For subclasses: do nothing by default.
}
```

很明显，接下来又是模板方法，跳转到 `DispatcherServlet` 了。

#### 1.3.2 DispatcherServlet的onRefresh

**注意这个地方到了非常非常重要的点了！！！高能预警！！！**

```java
@Override
protected void onRefresh(ApplicationContext context) {
    initStrategies(context);
}

protected void initStrategies(ApplicationContext context) {
    initMultipartResolver(context); // 文件上传
    initLocaleResolver(context); // 国际化
    initThemeResolver(context); // 前端主题
    initHandlerMappings(context); // 处理器映射器
    initHandlerAdapters(context); // 处理器适配器
    initHandlerExceptionResolvers(context); // 异常处理器
    initRequestToViewNameTranslator(context);
    initViewResolvers(context); // 视图解析器
    initFlashMapManager(context);
}
```

可以发现，这个刷新逻辑中，**把** `**DispatcherServlet**` **需要的所有组件，全部都初始化了一遍**！所以各位，能猜到里面干了什么了吧！肯定是**我们没有初始化哪些组件，这个** `**initStrategies**` **方法就会帮我们初始化**！

下面我们来挑一个比较重要的组件来看看吧，小册就挑 `HandlerMapping` 吧。

#### 1.3.3 initHandlerMappings

首先我们来看 `HandlerMapping` 的初始化：

```java
private void initHandlerMappings(ApplicationContext context) {
    this.handlerMappings = null;

    // 默认分支：需要从容器中取出所有HandlerMapping
    if (this.detectAllHandlerMappings) {
        Map<String, HandlerMapping> matchingBeans =
                BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerMappings = new ArrayList<>(matchingBeans.values());
            AnnotationAwareOrderComparator.sort(this.handlerMappings);
        }
    }
    else {
        // 该分支，仅会取出名为 “handlerMapping” 的bean
        try {
            HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
            this.handlerMappings = Collections.singletonList(hm);
        } // catch ......
    }

    if (this.handlerMappings == null) {
        this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
        // logger ......
    }
}
```

首先上面的逻辑中，它会先从 IOC 容器中，寻找那些类型为 `HandlerMapping` 的 bean ，借助 `BeanFactoryUtils.beansOfTypeIncludingAncestors` 方法，可以从子容器一路爬到最顶层父容器，来搜寻所有指定类型的 bean 。如果全部搜寻完毕后，发现没有任何 `HandlerMapping` ，则会调用最底下的 `getDefaultStrategies` 方法，使用默认的策略初始化 `HandlerMapping` 。

#### 1.3.4 【默认策略】getDefaultStrategies

这个默认策略**相当重要**，一起来看源码是如何设计的：

```java
private static final Properties defaultStrategies;

protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
    String key = strategyInterface.getName();
    String value = defaultStrategies.getProperty(key);
    if (value != null) {
        String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
        List<T> strategies = new ArrayList<>(classNames.length);
        for (String className : classNames) {
            try {
                Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
                Object strategy = createDefaultStrategy(context, clazz);
                strategies.add((T) strategy);
            } // catch ......
        }
        return strategies;
    } else {
        return new LinkedList<>();
    }
}
```

可以发现！中间 for 循环中初始化了一组 `Class<?>` ，而对应的 `classNames` 是由 `value` 分割而来，而 `value` 又是根据上面的一个 `**Properties**` **类型的** `**defaultStrategies**` **变量**而来！而这个 defaultStrategies 的初始化，是走的静态代码块：

```java
private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";

static {
    try {
        ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
        defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
    } // catch ......
}
```

注意看！它加载了 classpath 下的一个名为 `“DispatcherServlet.properties”` 的文件！而这个文件，来自于 `spring-webmvc` 的 jar 包：

![img](https://cdn.nlark.com/yuque/0/2022/png/229542/1671715489387-1c660e2b-60c6-4de7-983f-928454e5ae3f.png)

打开这个 properties 文件，发现里面定义了刚才在 `initStrategies` 方法中初始化的所有 WebMvc 的核心组件的落地实现！

```properties
# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\
	org.springframework.web.servlet.function.support.RouterFunctionMapping

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
	org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
	org.springframework.web.servlet.function.support.HandlerFunctionAdapter


org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
	org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
	org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
```

可见，即便我们什么也不注册，也能使用默认的策略，加载这些 SpringWebMvc 认为必需的组件。

由 properties 文件可知，默认的 HandlerMapping 会初始化以下三个实现：

* `org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping`
* `**org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping**`
* `org.springframework.web.servlet.function.support.RouterFunctionMapping`

刚好有我们之前说的最重要的 `RequestMappingHandlerMapping` ，所以我们不需要任何配置，就可以实现 `@Controller` + `@RequestMapping` 的注解式 WebMvc 开发。

所以，读取完配置文件后，在上面的 `getDefaultStrategies` 方法中，就可以拿到上面的 3 个实现类，并实例化并注册进 IOC 容器，这样就完成了默认策略的加载。

### 1.4 小结

**使用** `**web.xml**` **配置** `**DispatcherServlet**` **的时候，即便我们不配置任何内置组件，SpringWebMvc 可以通过预先定义好的内置组件，在 IOC 容器已经初始化接近完毕的时候，借助事件监听，回调** `**DispatcherServlet**` **使其初始化默认组件的落地实现，从而达到兜底配置。**

## 2. 基于Servlet3.0规范的DispatcherServlet初始化

基于 Servlet 3.0 规范的 SpringWebMvc 工程，咱之前说过，它的入口是通过 `WebApplicationInitializer` 来的。我们在初体验中编写了一个 `AbstractAnnotationConfigDispatcherServletInitializer` 的子类，来声明根容器和 `DispatcherServlet` 子容器的配置类，并且指定了 `DispatcherServlet` 的 `servlet-mapping` ，就完成了 WebMvc 的配置。

这其中，我们也是只配置了一个 `InternalResourceViewResolver` ，其余的肯定都跟上面一样，执行了 `DispatcherServlet` 的 `initStrategies` 方法做了兜底初始化。不过我们应该好奇的是，`AbstractAnnotationConfigDispatcherServletInitializer` 是怎么由 `WebApplicationInitializer` 来一步一步把根容器和 WebMvc 子容器都初始化好的呢？

### 2.1 AbstractContextLoaderInitializer

要说到这个，那就必须来看 `WebAppliCationInitializer` 的第一个抽象实现类了，它首先实现了 `WebAppliCationInitializer` 接口的 `onStartUp` 方法：

```java
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
    registerContextLoaderListener(servletContext);
}
```

只不过这看上去，是为了注册之前在 `web.xml` 中初始化的那个 `ContextLoaderListener` 监听器，那里面的逻辑一定是**初始化根容器**咯，但是这似乎跟 `DispatcherServlet` 没啥关系。。。

不过要注意，IDEA 在此处给了我们提示，这个 `onStartUp` 方法，还有重写过的子类：

![img](https://cdn.nlark.com/yuque/0/2022/png/229542/1671715746400-d3476920-4152-4a05-8759-1c9f1820b0cf.png)

好，那咱就点击这个向下的 override 图标，跳转到一个叫 `AbstractDispatcherServletInitializer` 的子类。

### 2.2 AbstractDispatcherServletInitializer

这个 `AbstractDispatcherServletInitializer` 重写的 `onStartUp` 中，除了调用上面 `AbstractContextLoaderInitializer` 父类的 `onStartUp` 初始化根容器之外，还注册了 `DispatcherServlet` ：

```java
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
    super.onStartup(servletContext);
    registerDispatcherServlet(servletContext);
}
```

进入到 `registerDispatcherServlet` 方法：

```java
protected void registerDispatcherServlet(ServletContext servletContext) {
    String servletName = getServletName();
    Assert.hasLength(servletName, "getServletName() must not return null or empty");

    // 创建WebMvc子容器
    WebApplicationContext servletAppContext = createServletApplicationContext();
    Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");

    // 初始化DispatcherServlet
    FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
    Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
    dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());

    // 利用Servlet3.0的规范，动态注册Servlet
    ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
    // check ......

    registration.setLoadOnStartup(1);
    // 设置servlet-mapping
    registration.addMapping(getServletMappings());
    // servlet是否支持异步请求，默认true
    registration.setAsyncSupported(isAsyncSupported());

    Filter[] filters = getServletFilters();
    if (!ObjectUtils.isEmpty(filters)) {
        for (Filter filter : filters) {
            registerServletFilter(servletContext, filter);
        }
    }

    customizeRegistration(registration);
}
```

可以发现，这整个流程还是很容易理解的吧！首先初始化 WebMvc 的子容器，然后把 `DispatcherServlet` 创建出来，最后注册进 `ServletContext` ，完事。而初始化 WebMvc 子容器的逻辑，就定义在我们刚才说的 `AbstractAnnotationConfigDispatcherServletInitializer` 中了：

```java
@Override
protected WebApplicationContext createServletApplicationContext() {
    AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
    Class<?>[] configClasses = getServletConfigClasses();
    if (!ObjectUtils.isEmpty(configClasses)) {
        context.register(configClasses);
    }
    return context;
}
```

这里面我们要重写的方法，就是这个获取配置类的 `getServletConfigClasses` 方法。拿到配置类，就可以初始化 WebMvc 的子容器了，后面的逻辑也就跟上面分析的一样了。

## 小结

好，最后我们总结一下。

**使用** `**web.xml**` **配置** `**DispatcherServlet**` **的时候，即便我们不配置任何内置组件，SpringWebMvc 可以通过预先定义好的内置组件，在 IOC 容器已经初始化接近完毕的时候，借助事件监听，回调** `**DispatcherServlet**` **使其初始化默认组件的落地实现，从而达到兜底配置。**

**基于 Servlet 3.0 规范的方式，通过编写继承** `**AbstractAnnotationConfigDispatcherServletInitializer**` **的子类，分别提供根容器与 WebMvc 子容器的配置类，底层会帮我们初始化对应的容器，并且借助** `**ServletContext**` **注册** `**DispatcherServlet**` **。后续的处理逻辑与基于** `**web.xml**` **的配置方式一致。**
