Spring MVC之DispatcherServlet初始化详解

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

1. web.xml配置

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

<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接口,这个接口声明以下:spring

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初始化以前就已经完成了。mvc

       web.xml中配置的DispatcherServlet则是Servlet规范中HttpServlet的一个具体实现,实现了该接口的以后该类就具备处理web请求的能力了。这里能够看到,DispatcherServlet配置拦截的url是'/',也就是说全部的web请求都会通过DispatcherServlet,而对于具体的url的处理,其实是在DispatcherServlet中进行分发的。这也就是Spring为何只须要配置一个Servlet的缘由。app

       关于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容器中。ide

       在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的。以下是其源码:ui

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()方法进行的,以下是该方法的源码:this

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中配置的各个组件的初始化方式进行了讲解。

相关文章
相关标签/搜索