通过web.xml配置Spring MVC

先看web.xml相关配置

<!-- 省略非关键的配置 -->

<!-- [1] Spring配置 -->
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 指定Spring Bean的配置文件所在目录。默认配置在WEB-INF目录下 -->
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:config/applicationContext.xml</param-value>
</context-param>

<!-- ====================================== -->

<!-- [2] Spring MVC配置 -->
<servlet>
    <servlet-name>spring</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!-- 可以自定义servlet.xml配置文件的位置和名称,默认为WEB-INF目录下,名称为[<servlet-name>]-servlet.xml,如spring-servlet.xml
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring-servlet.xml</param-value> // 默认
    </init-param>
    -->

    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>spring</servlet-name>
    <url-pattern>*.do</url-pattern>
</servlet-mapping>

ServletContextListener初始化流程

在servlet容器启动的时候,会先初始化ServletContextListenercontextInitialized接口,而ContextLoaderListener就实现了该接口。

/**
* 初始化Spring 根容器
*/

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

初始化容器方法如下:

// 省略部分异常或日志代码
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
        // 重复初始化,抛出错误(只能配置一个)
    }

    Log logger = LogFactory.getLog(ContextLoader.class);

    long startTime = System.currentTimeMillis();

    try {
        // 创建容器
        if (this.context == null) {
            this.context = createWebApplicationContext(servletContext);
        }
        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);
            }
        }
        // 设置servletContext属性,就是第一行检查的属性
        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);
        }
        return this.context;
    }
    catch (RuntimeException | Error ex) {
        // 抛出异常
    }
}

重点在createWebApplicationContextconfigureAndRefreshWebApplicationContext,前者负责创建容器,后者负责刷新容器。

createWebApplicationContext
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
    // 确定上下文的class
    Class<?> contextClass = determineContextClass(sc);
    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
        // 抛出异常
    }
    // 通过反射调用,实例化容器上下文
    return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}

protected Class<?> determineContextClass(ServletContext servletContext) {
    // 通过init-param指定(一般都不用配置)
    String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
    if (contextClassName != null) {
        try {
            return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
        }
        catch (ClassNotFoundException ex) {
            // 省略
        }
    }
    else {
        // 从defaultStrategies中获取,defaultStrategies是在ContextLoaderListener创建的时候初始化的
        contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
        try {
            return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
        }
        catch (ClassNotFoundException ex) {
            // 省略
        }
    }
}

// 初始化defaultStrategies
private static final Properties defaultStrategies;
private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";
static {
    // 静态代码块初始化,查找ContextLoader.properties文件并加载到defaultStrategies中
    try {
        ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
        defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
    }
    catch (IOException ex) {
        //
    }
}

// 而ContextLoader.properties中只有一个配置:
org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext

也就是说通过web.xml配置的Spring MVC默认初始化了一个XmlWebApplicationContext的上下文环境

configureAndRefreshWebApplicationContext
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
    if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
        // 设置容器的id,如果没有配置就生成一个
        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()));
        }
    }

    // 设置容器的ServletContext
    wac.setServletContext(sc);
    String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
    if (configLocationParam != null) {
        // 设置配置文件的路径,就是我们通过xml配置进来的
        wac.setConfigLocation(configLocationParam);
    }

    // 配置上下文环境
    ConfigurableEnvironment env = wac.getEnvironment();
    if (env instanceof ConfigurableWebEnvironment) {
        ((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
    }
    // 执行用户配置的容器初始化
    customizeContext(sc, wac);
    // 刷新容器,进行BeanDefinition的加载等操作
    wac.refresh();
}

重点在于refresh方法,具体可以查看Spring 容器初始化流程,这里不展开分析。

DispatcherServlet初始化流程

省略部分继承关系,只展示了Servlet相关的

通过上面的类图,我们知道DispatcherServlet实际上就是Servlet,Servlet容器在初始化Servlet时会调用其init方法,而init方法在HttpServletBean类中重写了。

@Override
public final void init() throws ServletException {

    // 从servlet的init参数中组装PropertyValues
    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) {
            // 省略
        }
    }

    // 由子类实现的初始化
    initServletBean();
}

我们关注initServletBean方法就行,该方法在FrameworkServlet重写了:

@Override
protected final void initServletBean() throws ServletException {
    // 记录启动时间
    long startTime = System.currentTimeMillis();

    try {
        this.webApplicationContext = initWebApplicationContext();
        // 空实现
        initFrameworkServlet();
    }
    catch (ServletException | RuntimeException ex) {
        // 省略
    }
}


protected WebApplicationContext initWebApplicationContext() {
    // 获得根 WebApplicationContext 对象
    WebApplicationContext rootContext =
            WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;

    if (this.webApplicationContext != null) {
        // 第一种情况,如果构造方法已经传入 webApplicationContext 属性,则直接使用,我们只关注这种就行
        wac = this.webApplicationContext;
        if (wac instanceof ConfigurableWebApplicationContext) {
            // 如果是 ConfigurableWebApplicationContext 类型,并且未激活,则进行初始化
            // 如果是默认情况,应该在ServletContextListener初始化的时候就refresh了
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
            if (!cwac.isActive()) {
                if (cwac.getParent() == null) {
                    cwac.setParent(rootContext);
                }
                configureAndRefreshWebApplicationContext(cwac);
            }
        }
    }
    if (wac == null) {
        // 第二种情况,从 ServletContext 获取对应的 WebApplicationContext 对象
        wac = findWebApplicationContext();
    }
    if (wac == null) {
        // 第三种,创建一个 WebApplicationContext 对象
        wac = createWebApplicationContext(rootContext);
    }
    if (!this.refreshEventReceived) {
        // 如果未触发刷新事件,则主动触发刷新事件
        synchronized (this.onRefreshMonitor) {
            onRefresh(wac);
        }
    }
    if (this.publishContext) {
        // 将 context 设置到 ServletContext 中
        String attrName = getServletContextAttributeName();
        getServletContext().setAttribute(attrName, wac);
    }
    return wac;
}

initWebApplicationContext方法中,我们只需关注第一种情况,就是在Servlet容器中获取Spring上下文,因为在前面有提到,ServletContextListener中默认初始化了XmlWebApplicationContext,这里可以直接获取。

initFrameworkServlet方法目前Spring没有具体实现。

WebApplicationContext初始化好了以后,调用onRefresh触发刷新时间,而该事件的实现在DispatcherServlet,这里也是DispatcherServlet所有组件初始化的入口!

// DispatcherServlet.java

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

// 初始化各种组件的策略
protected void initStrategies(ApplicationContext context) {
    // 初始化 MultipartResolver
    initMultipartResolver(context);
    // 初始化 LocaleResolver
    initLocaleResolver(context);
    // 初始化 ThemeResolver
    initThemeResolver(context);
    // 初始化 HandlerMappings
    initHandlerMappings(context);
    // 初始化 HandlerAdapters
    initHandlerAdapters(context);
    // 初始化 HandlerExceptionResolvers
    initHandlerExceptionResolvers(context);
    // 初始化 RequestToViewNameTranslator
    initRequestToViewNameTranslator(context);
    // 初始化 ViewResolvers
    initViewResolvers(context);
    // 初始化 FlashMapManager
    initFlashMapManager(context);
}
initMultipartResolver

初始化 MultipartResolver

private void initMultipartResolver(ApplicationContext context) {
    try {
        // 尝试从容器中获取multipartResolver
        this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
    }
    catch (NoSuchBeanDefinitionException ex) {
        // 没有multipartResolver组件
        this.multipartResolver = null;
    }
}
initLocaleResolver

初始化 LocaleResolver

private void initLocaleResolver(ApplicationContext context) {
    try {
        // 尝试从容器中获取
        this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
    }
    catch (NoSuchBeanDefinitionException ex) {
        // 获取不到则使用默认策略
        this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
    }
}

其中getDefaultStrategy方法是从DispatcherServlet.properties文件中配置的,在DispatcherServlet 初始化的时候通过静态代码块初始到defaultStrategies变量中。

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

关于getDefaultStrategy是如何获取对应策略的这里不展开分析,大概就是通过传进来的class获取到全限定名,然后到defaultStrategies变量中查找。

initThemeResolver

初始化 ThemeResolver

private void initThemeResolver(ApplicationContext context) {
    try {
        this.themeResolver = context.getBean(THEME_RESOLVER_BEAN_NAME, ThemeResolver.class);
    }
    catch (NoSuchBeanDefinitionException ex) {
        this.themeResolver = getDefaultStrategy(context, ThemeResolver.class);
    }
}

套路跟initMultipartResolver一样

initHandlerMappings

初始化 HandlerMappings

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

    if (this.detectAllHandlerMappings) {
        // 在ApplicationContext中找到所有HandlerMappings,包括父容器的上下文。
        Map<String, HandlerMapping> matchingBeans =
                BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, truefalse);
        if (!matchingBeans.isEmpty()) {
            this.handlerMappings = new ArrayList<>(matchingBeans.values());
            // 排序
            AnnotationAwareOrderComparator.sort(this.handlerMappings);
        }
    }
    else {
        try {
            // 从容器中获取
            HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
            this.handlerMappings = Collections.singletonList(hm);
        }
        catch (NoSuchBeanDefinitionException ex) {
        }
    }

    // 获取默认策略
    if (this.handlerMappings == null) {
        this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
    }
}

一般我们也没有指定HandlerMapping,所以也是从默认策略中初始化的。

initHandlerAdapters

初始化 HandlerAdapters

private void initHandlerAdapters(ApplicationContext context) {
    this.handlerAdapters = null;

    if (this.detectAllHandlerAdapters) {
        // 在ApplicationContext中找到所有HandlerMappings,包括父容器的上下文。
        Map<String, HandlerAdapter> matchingBeans =
                BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, truefalse);
        if (!matchingBeans.isEmpty()) {
            this.handlerAdapters = new ArrayList<>(matchingBeans.values());
            // 排序
            AnnotationAwareOrderComparator.sort(this.handlerAdapters);
        }
    }
    else {
        try {
            // 从容器中获取
            HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
            this.handlerAdapters = Collections.singletonList(ha);
        }
        catch (NoSuchBeanDefinitionException ex) {
        }
    }

    if (this.handlerAdapters == null) {
        // 获取默认策略
        this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
    }
}

initHandlerMappings一样的套路。

initHandlerExceptionResolvers

初始化 HandlerExceptionResolvers

private void initHandlerExceptionResolvers(ApplicationContext context) {
    this.handlerExceptionResolvers = null;

    if (this.detectAllHandlerExceptionResolvers) {
        Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
                .beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, truefalse);
        if (!matchingBeans.isEmpty()) {
            this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values());
            AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
        }
    }
    else {
        try {
            HandlerExceptionResolver her =
                    context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);
            this.handlerExceptionResolvers = Collections.singletonList(her);
        }
        catch (NoSuchBeanDefinitionException ex) {
        }
    }

    if (this.handlerExceptionResolvers == null) {
        this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
    }
}

一样的~

initRequestToViewNameTranslator

初始化 RequestToViewNameTranslator

private void initRequestToViewNameTranslator(ApplicationContext context) {
    try {
        this.viewNameTranslator =
                context.getBean(REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME, RequestToViewNameTranslator.class);
    }
    catch (NoSuchBeanDefinitionException ex) {
        this.viewNameTranslator = getDefaultStrategy(context, RequestToViewNameTranslator.class);
    }
}

还是同样的套路~

initViewResolvers

初始化 ViewResolvers

private void initViewResolvers(ApplicationContext context) {
    this.viewResolvers = null;

    if (this.detectAllViewResolvers) {
        Map<String, ViewResolver> matchingBeans =
                BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, truefalse);
        if (!matchingBeans.isEmpty()) {
            this.viewResolvers = new ArrayList<>(matchingBeans.values());
            AnnotationAwareOrderComparator.sort(this.viewResolvers);
        }
    }
    else {
        try {
            ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class);
            this.viewResolvers = Collections.singletonList(vr);
        }
        catch (NoSuchBeanDefinitionException ex) {
        }
    }

    if (this.viewResolvers == null) {
        this.viewResolvers = getDefaultStrategies(context, ViewResolver.class);
    }
}

过~

initFlashMapManager

初始化 FlashMapManager

private void initFlashMapManager(ApplicationContext context) {
    try {
        this.flashMapManager = context.getBean(FLASH_MAP_MANAGER_BEAN_NAME, FlashMapManager.class);
    }
    catch (NoSuchBeanDefinitionException ex) {
        this.flashMapManager = getDefaultStrategy(context, FlashMapManager.class);
    }
}

还是一样~

至此,DispatcherServlet就算初始化完了

通过javaConfig配置Spring MVC

通过xml配置Spring MVC已经过时了,我们来看看如何通过javaConfig来初始化。

Servlet3.0开始,在Servlet容器在启动的时候会扫描所有jar包下的ServletContainerInitializer实现类调用onStartup方法来初始化容器,而Spring基于javaConfig的配置正是通过该机制来实现的。

spring-web的jar中,配置了如下文件:META-INF/services/javax.servlet.ServletContainerInitializer,通过java的SPI机制,会为ServletContainerInitializer生成一个实现类,而这个类的全限定名就是配置在这个文件中

// javax.servlet.ServletContainerInitializer
org.springframework.web.SpringServletContainerInitializer

可见SpringServletContainerInitializer就是Spring容器注册的关键

SpringServletContainerInitializer

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
    @Override
    public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
            throws ServletException 
{

        List<WebApplicationInitializer> initializers = new LinkedList<>();

        if (webAppInitializerClasses != null) {
            for (Class<?> waiClass : webAppInitializerClasses) {
                // 检查给定的webAppInitializerClasses是否合规
                // 虽然指定了HandlesTypes,但是某些Servlet容器可能没有实现HandlesTypes的逻辑,把一些不相干的Class也传进来
                if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
                        WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
                    try {
                        initializers.add((WebApplicationInitializer)
                                ReflectionUtils.accessibleConstructor(waiClass).newInstance());
                    }
                    catch (Throwable ex) {
                        // 抛出异常
                    }
                }
            }
        }

        if (initializers.isEmpty()) {
            servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
            return;
        }

        AnnotationAwareOrderComparator.sort(initializers);
        for (WebApplicationInitializer initializer : initializers) {
            // 调用onStartup方法
            initializer.onStartup(servletContext);
        }
    }

}

Spring提供的WebApplicationInitializer体系如下:

体系中实现了onStartup方法的类如下:

我们这里只关注前两个,逐个分析:

AbstractContextLoaderInitializer

我们先来分析AbstractContextLoaderInitializer

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

protected void registerContextLoaderListener(ServletContext servletContext) {
    // 创建Spring容器
    WebApplicationContext rootAppContext = createRootApplicationContext();
    if (rootAppContext != null) {
        // 设置监听器
        ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
        listener.setContextInitializers(getRootApplicationContextInitializers());
        servletContext.addListener(listener);
    }
    else {
        // 省略日志
    }
}
// 抽象方法
protected abstract WebApplicationContext createRootApplicationContext();

createRootApplicationContextAbstractAnnotationConfigDispatcherServletInitializer提供了默认实现:

// AbstractAnnotationConfigDispatcherServletInitializer.java

protected WebApplicationContext createRootApplicationContext() {
    Class<?>[] configClasses = getRootConfigClasses();
    if (!ObjectUtils.isEmpty(configClasses)) {
        // 创建一个注解配置的Web应用容器
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        context.register(configClasses);
        return context;
    }
    else {
        return null;
    }
}
// 没有默认实现,需要我们手动注册
protected abstract Class<?>[] getRootConfigClasses();

总的来说,AbstractContextLoaderInitializeronStartup主要是实现了容器的创建。

AbstractDispatcherServletInitializer

注意!这里就是注册DispatcherServlet的关键地方

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

先调用了父类的onStartup方法,就是我们上面分析的AbstractContextLoaderInitializer#onStartup。然后调用registerDispatcherServlet,一看名字就知道是注册DispatcherServlet了~

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

    // 创建 WebApplicationContext 对象,这里重新创建容器,所以可支持配置多个DispatcherServlet,而且每个DispatcherServlet都有自己的容器环境
    WebApplicationContext servletAppContext = createServletApplicationContext();
    Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");

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

    // 注册到servlet容器中
    ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
    if (registration == null) {
        // 抛出异常
    }

    registration.setLoadOnStartup(1);
    registration.addMapping(getServletMappings());
    registration.setAsyncSupported(isAsyncSupported());

    // 注册过滤器
    Filter[] filters = getServletFilters();
    if (!ObjectUtils.isEmpty(filters)) {
        for (Filter filter : filters) {
            registerServletFilter(servletContext, filter);
        }
    }
    // 空实现,提供子类配置
    customizeRegistration(registration);
}

protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) {
    // 直接new一个DispatcherServlet
    return new DispatcherServlet(servletAppContext);
}

createServletApplicationContext在前面已经解析过,这里重新创建容器,所以可支持配置多个DispatcherServlet,而且每个DispatcherServlet都有自己的容器环境。

DispatcherServlet注册到Servlet容器后,后续的流程跟前面通过xml配置的流程一致,忘记了的话返回到上面看看就行。


49 条评论

bonanza178 · 2023年6月19日 下午4:16

… [Trackback]

[…] Find More to that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]

Ventilatii generale · 2023年6月19日 下午4:54

… [Trackback]

[…] Information to that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]

cvv sites · 2023年6月26日 下午2:08

… [Trackback]

[…] Info to that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]

colt 1911 for sale · 2023年7月3日 下午11:59

… [Trackback]

[…] Info on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]

magic mushrooms for sale · 2023年7月13日 上午8:08

… [Trackback]

[…] Read More to that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]

magic mushrooms for sale online australia · 2023年7月19日 下午9:32

… [Trackback]

[…] Find More on on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]

นำเข้าสินค้าจากจีน · 2023年7月20日 上午8:13

… [Trackback]

[…] Info on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]

ปั้มฟอล · 2023年7月21日 上午8:43

… [Trackback]

[…] Read More on on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]

bonanza178 · 2023年7月21日 下午6:07

… [Trackback]

[…] Here you can find 58660 additional Information to that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]

เย้เย้ · 2023年7月24日 上午8:06

… [Trackback]

[…] Read More on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]

เรือไปเกาะหลีเป๊ะ · 2023年7月25日 上午6:26

… [Trackback]

[…] Here you can find 89090 additional Info to that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]

pappy van winkle 23 · 2023年7月25日 下午3:58

… [Trackback]

[…] Find More Information here to that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]

ติดเน็ตบ้าน ais พร้อม กล่องทีวี · 2023年8月3日 上午6:51

… [Trackback]

[…] Find More on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]

miami boat rentals with captain · 2023年8月3日 上午10:22

… [Trackback]

[…] Here you can find 66784 more Info on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]

best shipping car company · 2023年8月3日 上午10:53

… [Trackback]

[…] Find More on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]

88888 คาสิโน · 2023年8月6日 上午9:35

… [Trackback]

[…] There you will find 21840 more Info to that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]

disposable carts · 2023年8月7日 下午9:35

… [Trackback]

[…] Here you can find 2708 more Info to that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]

ตาสองชั้นหมอไก่ · 2023年8月8日 上午7:45

… [Trackback]

[…] Read More on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]

buy runtz weed · 2023年8月9日 下午3:10

… [Trackback]

[…] Find More on to that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]

Mango · 2023年8月10日 上午5:33

… [Trackback]

[…] Read More on on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]

qiuqiu99 agen · 2023年8月11日 下午9:05

… [Trackback]

[…] Here you can find 92467 additional Info to that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]

เว็บ บอล ที่ ดี · 2023年8月12日 上午7:26

… [Trackback]

[…] Find More on on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]

เรือไปหลีเป๊ะ · 2023年8月12日 上午8:07

… [Trackback]

[…] Find More on on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]

alpha88 สล็อต · 2023年8月12日 上午10:15

… [Trackback]

[…] There you will find 5357 more Info on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]

togel idn · 2023年8月12日 下午5:49

… [Trackback]

[…] Information on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]

bonanza178 · 2023年8月18日 下午4:05

… [Trackback]

[…] Read More on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]

Christensen arms · 2023年8月20日 上午6:47

… [Trackback]

[…] Information to that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]

เว็บพนันออนไลน์ · 2023年8月22日 上午7:23

… [Trackback]

[…] Find More Info here on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]

ทินเนอร์คุณภาพสูง · 2023年8月26日 上午7:05

… [Trackback]

[…] Find More on to that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]

judi slot terpercaya · 2023年8月29日 下午4:58

… [Trackback]

[…] Find More on to that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]

togel online · 2023年8月29日 下午7:20

… [Trackback]

[…] Read More Information here to that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]

20176 zip code · 2023年8月30日 上午2:34

… [Trackback]

[…] There you can find 54510 additional Info on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]

เครื่องสำอางค์เกาหลี · 2023年8月30日 上午6:56

… [Trackback]

[…] There you will find 41849 additional Info to that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]

ป้ายโฆษณา · 2023年8月30日 上午8:17

… [Trackback]

[…] Read More to that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]

เว็บพนันบอล ดีที่สุด pantip · 2023年8月31日 上午7:01

… [Trackback]

[…] Info to that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]

แทงบอลออนไลน์ · 2023年9月2日 下午2:47

… [Trackback]

[…] Find More Information here on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]

house for sale pattaya · 2023年9月3日 上午7:44

… [Trackback]

[…] Read More Info here on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]

พรมปูพื้นรถยนต์ 6d · 2023年9月5日 上午6:52

… [Trackback]

[…] Find More on on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]

เว็บคาสิโนออนไลน์ · 2023年9月5日 上午7:48

… [Trackback]

[…] Read More on on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]

more · 2023年9月6日 下午6:52

… [Trackback]

[…] Read More Info here on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]

sidegra · 2023年9月7日 上午6:59

… [Trackback]

[…] Find More Information here on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]

luxury pool villas in phuket · 2023年9月7日 上午8:01

… [Trackback]

[…] There you will find 69904 additional Information on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]

เทคนิค พนันออนไลน์ · 2023年9月8日 上午6:33

… [Trackback]

[…] Find More on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]

สถาปนิกรับออกแบบบ้าน · 2023年9月8日 上午7:05

… [Trackback]

[…] Info on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]

ออกแบบโลโก้ · 2023年9月8日 上午7:06

… [Trackback]

[…] Here you will find 79575 more Info to that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]

ยูเรเนียน · 2023年9月8日 上午7:25

… [Trackback]

[…] Here you can find 22868 more Information on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]

result sgp · 2023年9月10日 上午2:11

… [Trackback]

[…] Read More on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]

Skrot Lilla Edet · 2023年9月10日 上午2:53

… [Trackback]

[…] Read More Information here on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]

เพิ่มยอดไลค์ · 2023年9月10日 上午7:11

… [Trackback]

[…] Find More to that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]

评论已关闭。