Spring MVC之DispatcherServlet初始化详解

时间:2022-12-09 03:16:22

Spring作为一个优秀的web框架,其运行是基于Tomcat的。在我们前面的讲解中,Spring的驱动都是使用的ClassPathXmlApplicationContext,并且都是直接在main方法中启动的,但是在Tomcat容器中,我们是无法使用main方法的,因而其驱动方式必然与我们测试时不一样。Tomcat是一个基于Servlet规范的web容器,而Spring则提供了对Servlet规范的支持,其DispatcherServlet则是Servlet规范的具体实现。因而在web开发过程中,当我们启动Tomcat容器时其会根据Servlet规范启动Spring实现的DispatcherServlet,这样也就驱动了Spring的运行。本文主要从源码的角度讲解Spring在web容器中是如何初始化的。

1. web.xml配置

在配置web容器时,我们都会配置一个web.xml,而在配置web.xml时,最主要的两个组件就是ContextLoaderListenerDispatcherServlet的配置。如下是一个典型的web.xml文件的配置:

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

<servlet>
    <servlet-name>myservlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>myservlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

这里ContextLoaderListener的作用是对对Servlet Context的行为进行监听,其实现了ServletContextListener接口,这个接口声明如下:

public interface ServletContextListener extends EventListener {
    // 用于在Servlet Context初始化事前执行
    default public void contextInitialized(ServletContextEvent sce) {}

    // 用于在Servlet Context被销毁之后执行
    default public void contextDestroyed(ServletContextEvent sce) {}
}

这里ServletContextListener是Servlet规范中提供的一个接口,该接口中的contextInitialized()会在servlet context初始化之前执行,而contextDestroyed()方法则会在servlet context被销毁之后执行。Spring提供的ContextLoaderListener对这两个方法都进行了实现。实际上,在web.xml中指定的contextConfigLocation参数的解析就是在ContextLoaderListener.contextInitialized()方法中解析的,也就是说Spring对于bean的创建实际上是在Servlet Context初始化之前就已经完成了。

web.xml中配置的DispatcherServlet则是Servlet规范中HttpServlet的一个具体实现,实现了该接口的之后该类就具有处理web请求的能力了。这里可以看到,DispatcherServlet配置拦截的url是'/',也就是说所有的web请求都会经过DispatcherServlet,而对于具体的url的处理,实际上是在DispatcherServlet中进行分发的。这也就是Spring为什么只需要配置一个Servlet的原因。

关于DispatcherServlet的配置这里不得不提的是,我们得为其提供一个myservlet-servlet.xml的配置文件,用于只为当前servlet提供Spring的一些基本配置。这里该文件的命名必须按照servlet名称-servlet.xml这种格式进行,由于我们的servlet的名称为myservlet,因而配置文件名必须为myservlet-servlet.xml。如果使用者需要自定义文件名,可以在当前servlet中使用init-param标签进行配置,如:

<servlet>
    <servlet-name>myservlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>WEB-INF/myservlet-servlet.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

另外,对于配置的myservlet-servlet.xml文件的初始化,是在DispatcherServlet.init()方法中进行的。这里需要注意的是,myservlet-servlet.xml完完全全是一个独立的Spring配置文件,我们可以在其中声明Spring的bean,并且注册到Spring容器中。

在web.xml中我们提到了两个Spring的配置文件,一个是我们常用的applicationContext.xml,另一个专属于某个Servlet的myservlet-servlet.xml。两个配置文件的初始化分别是由ServletContextListener.contextInitialized()方法和GenericServlet.init()方法进行的。这两个方法都是Servlet规范中提供的初始化方法,两个方法分别会初始化两个Spring容器,这两个容器中applicationContext.xml对应的容器会作为myservlet-servlet.xml初始化的容器的父容器而存在,因而在myservlet-servlet.xml的容器中,我们是可以使用任何在applicationContext.xml中声明的bean的,但是反过来则不行。在处理具体请求的时候,我们所使用的Spring容器其实一直都是myservlet-servlet.xml声明而来的。

2. ContextLoaderListener初始化

对于ContextLoaderListener,其主要是用于初始化我们常用的applicationContext.xml的。如下是其源码:

public class ContextLoaderListener extends ContextLoader
        implements ServletContextListener {
    // 初始化Spring容器
	public void contextInitialized(ServletContextEvent event) {
		initWebApplicationContext(event.getServletContext());
	}

	// 销毁Spring容器
	public void contextDestroyed(ServletContextEvent event) {
		closeWebApplicationContext(event.getServletContext());
		ContextCleanupListener.cleanupAttributes(event.getServletContext());
	}
}

可以看到,这里对于Spring容器的初始化是委托给了initWebApplicationContext()方法进行的,如下是该方法的源码:

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    // 如果当前已经初始化过一个web application context则抛出异常,这样可以保证一个web容器中
    // 只会有一个web application context
    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 {
        if (this.context == null) {
            // 通过servlet配置创建一个WebApplicationContext对象
            this.context = createWebApplicationContext(servletContext);
        }

        // 这里在createWebApplicationContext()方法中会保证创建的WebApplicationContext本质上是
        // ConfigurableWebApplicationContext类型的,因而这里进行类型判断的时候是能够进入if分支的
        if (this.context instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac =
                (ConfigurableWebApplicationContext) this.context;
            // 如果当前WebApplicationContext没有初始化过就对其进行初始化
            if (!cwac.isActive()) {
                // 如果当前WebApplicationContext没有父ApplicationContext,则通过
                // loadParentContext()方法加载一个,该方法实际上是一个空方法,这里提供
                // 出来只是为了方便用户进行容器属性的自定义,因为父容器的内容会继承到子容器中
                if (cwac.getParent() == null) {
                    ApplicationContext parent = loadParentContext(servletContext);
                    cwac.setParent(parent);
                }

                // 对WebApplicationContext进行配置,并且调用其refresh()方法初始化
                // 配置文件中配置的Spring的各个组件
                configureAndRefreshWebApplicationContext(cwac, servletContext);
            }
        }
        // 初始化完成之后将当前WebApplicationContext设置到ServletContext中
        servletContext.setAttribute(WebApplicationContext
            .ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

        // 设置当前WebApplicationContext的类加载器
        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的初始化,Spring首先会根据配置文件配置创建一个WebApplicationContext对象,然后判断该对象是否初始化过,如果没有,则对其进行配置并且初始化。这里我们首先看看Spring是如何创建WebApplicationContext对象的:

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
    // 读取配置文件中配置的实现了WebApplicationContext接口的类
    Class<?> contextClass = determineContextClass(sc);
    // 判断读取到的类是否实现了ConfigurableWebApplicationContext接口,如果没实现则抛出异常
    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
        throw new ApplicationContextException("Custom context class ["
            + contextClass.getName() + "] is not of type ["
            + ConfigurableWebApplicationContext.class.getName() + "]");
    }

    // 通过反射实例化WebApplicationContext对象
    return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}

// 这个方法的主要作用在于读取配置文件中配置的实现了WebApplicationContext接口的类,
// 从而作为WebApplicationContext容器。这里读取配置文件的方式有两种:①读取web.xml中配置的
// contextClass属性,如果存在则将其作为WebApplicationContext容器;②读取Spring提供的
// ContextLoader.properties属性文件中配置的WebApplicationContext容器。
protected Class<?> determineContextClass(ServletContext servletContext) {
    // 读取用户在web.xml中使用contextClass属性自定义的WebApplicationContext容器,
    // 如果不为空,则直接返回
    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 {
        // 如果用户没有自定义WebApplicationContext,则通过defaultStrategies读取
        // ContextLoader.properties属性文件中配置的WebApplicationContext,
        // 这里读取到的具体实现类就是XmlWebApplicationContext
        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);
        }
    }
}

这里讲到,我们可以在web.xml中配置自定义的WebApplicationContext,具体的配置方式就是在web.xml中配置如下属性:

<context-param>
    <param-name>contextClass</param-name>
    <param-value>mvc.config.MyXmlWebApplicationContext</param-value>
</context-param>

通过这种方式我们就可以实现自定义的WebApplicationContext。对于Spring提供的默认WebApplicationContext实现,其是通过defaultStrategies这个属性读取的,这个属性的初始化是在ContextLoader(ContextLoaderListener继承了该类)中使用static代码块进行初始化的,读者可自行查阅。

在创建了WebApplicationContext对象之后,Spring会对其进行配置和各个组件的初始化,如下是ContextLoader.configureAndRefreshWebApplicationContext()方法的具体实现:

protected void configureAndRefreshWebApplicationContext(
        ConfigurableWebApplicationContext wac, ServletContext sc) {
    // 判断当前WebApplicationContext是否具有统一的id,如果没有,首先会从web.xml中读取,
    // 具体的使用contextId属性进行制定,该属性的配置方式与上面的contextClass一致,如果没有,
    // 则通过默认规则声明一个
    if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
        String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
        if (idParam != null) {
            wac.setId(idParam);
        } else {
            wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                      ObjectUtils.getDisplayString(sc.getContextPath()));
        }
    }

    // 获取web.xml中配置的contextConfigLocation属性值,这里也就是我们前面配置的
    // applicationContext.xml,在后面调用refresh()方法时会根据xml文件中的配置
    // 初始化Spring的各个组件
    wac.setServletContext(sc);
    String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
    if (configLocationParam != null) {
        wac.setConfigLocation(configLocationParam);
    }

    // 获取当前Spring的运行环境,并且初始化其propertySources
    ConfigurableEnvironment env = wac.getEnvironment();
    if (env instanceof ConfigurableWebEnvironment) {
        ((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
    }

    // 这里customizeContext()方法是一个空方法,供给用户自定义实现ContextLoaderListener时
    // 对WebApplicationContext进行自定义
    customizeContext(sc, wac);
    // 这里refresh()方法用于读取上面声明的配置文件,并且初始化Spring的各个组件
    wac.refresh();
}

关于WebApplicationContext的配置和初始化,这里主要分为了四个步骤:①为当前WebApplicationContext声明一个id,用于对其进行唯一标识;②读取web.xml中配置的Spring配置文件的位置;③初始化propertySources;④读取Spring配置文件中的内容,并且实例化Spring的各个组件。这里需要说明的是,对于Spring各个组件的初始化,调用的是ConfigurableWebApplicationContext.refresh()方法,这个方法我们前面讲解Spring bean注册解析时已经讲解了,读者可以翻阅Spring Bean注册解析(一)Spring Bean注册解析(二)

ConfigurableWebApplicationContext.refresh()方法调用完成之后,Spring配置文件中的各项配置就都已经处理完成。如此,ContextLoaderListener的初始化工作也就完成。

3. DispatcherServlet的初始化

对于DispatcherServlet的初始化,这里需要注意的是,在web.xml中我们配置了load-on-startup标签,配置了该标签就表示当前Servlet的初始化方法会在web容器启动完成后调用,也就是这里的DispatcherServlet.init()方法。我们首先看看该方法的源码:

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

    // 读取在web.xml中通过init-param标签设置的属性,如果没有配置,这里pvs就会是empty的
    PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(),
        this.requiredProperties);
    if (!pvs.isEmpty()) {
        try {
            // 注册Resource对象对应的PropertyEditor
            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
            ResourceLoader resourceLoader =
                new ServletContextResourceLoader(getServletContext());
            bw.registerCustomEditor(Resource.class,
                new ResourceEditor(resourceLoader, getEnvironment()));
            // 初始化BeanWrapper对象,这里是一个空方法,供给使用者对BeanWrapper进行自定义处理
            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;
        }
    }

    // 初始化当前DispatcherServlet的各项配置
    initServletBean();
    if (logger.isDebugEnabled()) {
        logger.debug("Servlet '" + getServletName() + "' configured successfully");
    }
}

可以看到,这里对DispatcherServlet的初始化主要分为两个步骤:①判断当前Servlet中使用使用init-param标签自定义了属性,如果定义了,则将其设置到BeanWrapper中;②初始化DispatcherServlet。这里我们继续阅读initServletBean()的源码:

@Override
protected final void initServletBean() throws ServletException {
    getServletContext().log("Initializing Spring FrameworkServlet '"
        + getServletName() + "'");
    if (this.logger.isInfoEnabled()) {
        this.logger.info("FrameworkServlet '" + getServletName()
            + "': initialization started");
    }
    long startTime = System.currentTimeMillis();

    try {
        // 初始化当前servlet配置的Spring配置
        this.webApplicationContext = initWebApplicationContext();
        // 这里initFrameworkServlet()方法是一个空方法,供给用户对当前servlet对应的Spring容器
        // 进行自定义的处理
        initFrameworkServlet();
    } catch (ServletException ex) {
        this.logger.error("Context initialization failed", ex);
        throw ex;
    } catch (RuntimeException ex) {
        this.logger.error("Context initialization failed", ex);
        throw ex;
    }

    if (this.logger.isInfoEnabled()) {
        long elapsedTime = System.currentTimeMillis() - startTime;
        this.logger.info("FrameworkServlet '" + getServletName()
            + "': initialization completed in " + elapsedTime + " ms");
    }
}

这里initServletBean()除了进行一些日志记录以外,主要工作还是委托给了initWebApplicationContext()方法进行,我们这里直接阅读该方法的源码:

protected WebApplicationContext initWebApplicationContext() {
    // 获取在ContextLoaderListener中初始化的Spring容器,并且将其作为当前servlet对应
    // 的容器的父容器,这样当前servlet容器就可以使用其父容器中的所有内容了
    WebApplicationContext rootContext =
        WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;

    // 如果当前Servlet对应的WebApplicationContext不为空,并且其未被初始化,则对其进行初始化
    if (this.webApplicationContext != null) {
        wac = this.webApplicationContext;
        if (wac instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac =
                (ConfigurableWebApplicationContext) wac;
            // 判断当前WebApplicationContext是否已初始化过,没有则进行初始化
            if (!cwac.isActive()) {
                if (cwac.getParent() == null) {
                    cwac.setParent(rootContext);
                }
                // 初始化当前WebApplicationContext
                configureAndRefreshWebApplicationContext(cwac);
            }
        }
    }
    if (wac == null) {
        // 如果wac为空,则说明当前servlet对应的WebApplicationContext是空的,
        // 这里会通过当前servlet配置的contextAttribute属性查找一个自定义的
        // WebApplicationContext,将其作为当前servlet的容器
        wac = findWebApplicationContext();
    }
    if (wac == null) {
        // 如果用户没有自定义WebApplicationContext,则创建一个,并且对其进行初始化
        wac = createWebApplicationContext(rootContext);
    }

    if (!this.refreshEventReceived) {
        // 这里的onRefresh()方法并不是初始化Spring配置文件中的bean的,
        // 而是用于初始化Spring处理web请求相关的组件的,如RequestMappingHandlerMapping等
        onRefresh(wac);
    }

    if (this.publishContext) {
        // 将当前WebApplicationContext对象设置到ServletContext中
        String attrName = getServletContextAttributeName();
        getServletContext().setAttribute(attrName, wac);
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Published WebApplicationContext of servlet '"
                + getServletName() + "' as ServletContext attribute with name ["
                + attrName + "]");
        }
    }

    return wac;
}

这里默认情况下,DispatcherServlet中是不存在已经初始化过的WebApplicationContext的,因而最终还是会调用createWebApplicationContext()方法进行初始化,在初始化完成之后就会初始化Spring处理web请求的相关组件。我们首先看createWebApplicationContext()方法的实现:

protected WebApplicationContext createWebApplicationContext(@Nullable
        ApplicationContext parent) {
    // 读取web.xml中配置的contextClass属性,将其作为当前servlet的WebApplicationContext
    Class<?> contextClass = getContextClass();
    if (this.logger.isDebugEnabled()) {
        this.logger.debug("Servlet with name '" + getServletName()
            + "' will try to create custom WebApplicationContext context of class '"
            + contextClass.getName() + "'" + ", using parent context [" + parent + "]");
    }

    // 保证用户定义的WebApplicationContext对象是ConfigurableWebApplicationContext类型的
    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
        throw new ApplicationContextException(
            "Fatal initialization error in servlet with name '" + getServletName() +
            "': custom WebApplicationContext class [" + contextClass.getName() +
            "] is not of type ConfigurableWebApplicationContext");
    }

    // 实例化WebApplicationContext对象
    ConfigurableWebApplicationContext wac =
        (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

    // 设置当前的运行环境
    wac.setEnvironment(getEnvironment());
    // 将ContextLoaderListener中初始化的WebApplicationContext作为当前
    // WebApplicationContext的父容器
    wac.setParent(parent);
    // 获取当前servlet配置的contextConfigLocation
    String configLocation = getContextConfigLocation();
    if (configLocation != null) {
        wac.setConfigLocation(configLocation);
    }
    // 读取当前WebApplicationContext配置的Spring相关的bean,并进行初始化
    configureAndRefreshWebApplicationContext(wac);
    return wac;
}

这里对当前servlet的WebApplicationContext的初始化过程其实比较简单,其中最主要需要注意的有两点:①会将ContextLoaderListener初始化的WebApplicationContext作为当前WebApplicationContext的父容器;②在获取当前configLocation的时候,如果没有设置,则使用"servlet名称-servlet.xml"的方式读取。

4. Spring web九大组件初始化

在第三点最后,我们讲到,初始化servlet对应的容器之后,其会调用onRefresh()方法初始化Spring web相关的组件,该方法的具体实现在DispatcherServlet.onRefresh()方法中,这里我们直接阅读该方法的源码:

@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);
}

这里对Spring的九大组件的实例化方式都比较统一,关于这九大组件的具体细节我们后面会依次进行讲解。这里我们主要讲解其初始化方式。关于这九大组件,其实例化方式可分为两类:

  • 通过制定的bean名称在Spring容器中读取对应的bean,如果不存在则使用默认的类来初始化;
  • 通过参数配置控制是在Spring容器中读取指定实现指定接口的所有bean,还是读取Spring容器中指定名称的bean,如果这两种方式都无法读取到对应的bean,则读取Spring配置文件中配置的默认的bean。

对于第一种实例化方式,我们这里以LocaleResolver的初始化为例进行讲解,如下是initLocaleResolver()方法的源码:

private void initLocaleResolver(ApplicationContext context) {
    try {
        // 这里LOCALE_RESOLVER_BEAN_NAME的值为localeResolver,也就是说用户如果
        // 需要自定义的LocaleResolver,那么在声明该bean是,其名称必须为localeResolver
        this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME,
            LocaleResolver.class);
        if (logger.isDebugEnabled()) {
            logger.debug("Using LocaleResolver [" + this.localeResolver + "]");
        }
    } catch (NoSuchBeanDefinitionException ex) {
        // 如果Spring容器中没有配置自定义的localeResolver,则通过默认策略实例化对应的bean
        this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
        if (logger.isDebugEnabled()) {
            logger.debug("Unable to locate LocaleResolver with name '"
                + LOCALE_RESOLVER_BEAN_NAME
                + "': using default [" + this.localeResolver + "]");
        }
    }
}

对于第二种方式,我们这里以HandlerMapping的实例化为例进行讲解,如下是initHandlerMappings()方法的实现原理:

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

    // 检查是否配置了获取Spring中配置的所有HandlerMapping类型对象,是则进行读取,并且按照
    // 指定的排序规则对其进行排序,否则就从Spring中读取名称为handlerMapping的bean,
    // 并将其作为指定的bean
    if (this.detectAllHandlerMappings) {
        // 从Spring容器中读取所有的实现了HandlerMapping接口的bean
        Map<String, HandlerMapping> matchingBeans =
            BeanFactoryUtils.beansOfTypeIncludingAncestors(context,
                HandlerMapping.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerMappings = new ArrayList<>(matchingBeans.values());
            // 对获取到的HandlerMapping进行排序
            AnnotationAwareOrderComparator.sort(this.handlerMappings);
        }
    } else {
        try {
            // 获取Spring容器中名称为handlerMapping的bean
            HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME,
                HandlerMapping.class);
            this.handlerMappings = Collections.singletonList(hm);
        } catch (NoSuchBeanDefinitionException ex) {
			// 忽略当前异常
        }
    }

    if (this.handlerMappings == null) {
        // 如果上述方式没法获取到对应的HandlerMapping,则使用默认策略获取对应的HandlerMapping
        this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
        if (logger.isDebugEnabled()) {
            logger.debug("No HandlerMappings found in servlet '"
                 + getServletName() + "': using default");
        }
    }
}

上述两种初始化Spring web组件的方式中都涉及到一个获取默认的bean的方法,该方法实际上是从Spring提供的配置文件指定对应的bean的Class,在读取该文件之后会对其进行实例化,然后返回。对于getDefaultStrategies()方法的实现原理,其实比较简单,我们这里主要给大家展示Spring提供的组件的配置文件的内容,该配置文件的名称为DispatcherServlet.properties,如下是该文件的内容:

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

可以看到,这里的配置文件的key就是对应的接口的全路径名,这也就是getDefaultStrategies()方法第二个参数传入的是Class对象的原因,而value就是该接口对应的实现类,可以有多个。

5. 小结

本文首先讲解了web.xml文件的配置方式,并且着重讲解了该文件中各个配置的意义,接着依次在源码的层面对web.xml中配置的各个组件的初始化方式进行了讲解。如果大家想学习以上路线内容,在此我向大家推荐一个架构学习交流群。交流学习群号874811168 里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多