Spring MVC 初始化源码(2)—DispatcherServlet与子容器的初始化以及MVC组件的初始化【一万字】

2021年9月16日 4点热度 0条评论 来源: 刘Java

  基于最新Spring 5.x,详细介绍了Spring MVC 初始化流程的源码,主要包括DispatcherServlet与MVC子容器的初始化,以及各种MVC组件的初始化。

  上一篇文章我们讲解了ContextLoaderListener监听器与根上下文容器的初始化。
  ContextLoaderListenercontextInitialized方法回调完毕之后,Root WebApplicationContext初始化完毕,随后会初始化全部的Filter,并且执行init回调,最后会按顺序初始化全部的即时创建的Servlet,对于Spring MVC来说,最重要的就是DispatcherServlet,该过程同时会涉及到MVC子容器的创建和初始化,以及各种MVC组件的初始化。一起来看看DispatcherServlet的初始化源码吧!

  下面的源码版本基于5.2.8.RELEASE

Spring MVC源码 系列文章

Spring MVC 初始化源码(1)—ContextLoaderListener与父上下文容器的初始化

Spring MVC 初始化源码(2)—DispatcherServlet与子容器的初始化以及MVC组件的初始化【一万字】

Spring MVC 初始化源码(3)—<mvc:annotation-driven >配置标签的源码解析

Spring MVC 初始化源码(4)—@RequestMapping注解的源码解析

Spring MVC 请求执行流程的源码深度解析【两万字】

文章目录

1 DispatcherServlet的概述

  DispatcherServlet作为Spring MVC的核心类,基本上所有的请求都是通过该Servlet来进行分发。此前的Spring MVC的学习系列文章中我们已经详细学习了它的功能和流程,现在我们来学习它的源码。
  DispatcherServlet的uml类图如下:

  可以看到DispatcherServlet最终继承了HttpServlet方法,实现了Servlet接口,因此它也是一个JavaEE中的Servlet标准实现,并且主要处理HTTP请求。
  HttpServletBean是HttpServlet的简单的抽象实现,主要功能是解析web.xml文件中的<servlet/>标签下面的<init-param/>标签配置的参数,将其设置为bean的对应的属性。该Servlet将具体的请求处理留给子类实现,仅仅继承HttpServlet的默认行为(doGet,doPost等)。
  FrameworkServlet是Spring Web框架的基础servlet。该类的功能有两个:

  1. 为每个该类型的servlet关联一个子WebApplicationContext实例。Servlet的配置由Servlet名称空间中的bean确定。
  2. 发布有关请求处理的事件,无论是否成功处理了请求。子类必须实现doService方法以执行真正的请求处理。

  DispatcherServlet是HTTP请求处理程序/控制器的中央调度程序,在初始化的时候它会初始化各个功能组件的实现类,并且在后续实现具体的请求处理流程,主要是通过调度各个组件来处理请求,几乎可以处理所有请求。
  DispatcherServlet的一系列初始化操作基本都是在init方法中完成的,这个init方法就是Servelet接口提供初始化方法,因此我们从该方法入手。

/** * GenericServlet的属性 * 保存了ServletConfig参数 */
private transient ServletConfig config;

/** * GenericServlet的init方法 * * @param config Servlet配置 */
public void init(ServletConfig config) throws ServletException { 
    this.config = config;
    //调用初始化方法
    this.init();
}

/** 1. GenericServlet的init方法 2. <p> 3. 应该被子类重写的方法 */
public void init() throws ServletException { }

  该方法整体来说做了三件事:

  1. 将当前ServletConfiginit parameters参数填充到当前DispatcherServlet的实例的对应的属性中。可以在web.xml中对应的<Servlet>标签下通过设置<init-param>标签来配置各种属性。主要是HttpServletBean#init()方法。
  2. 初始化于此Servlet关联的MVC子容器,该容器的父容器就是Root Application(可能为null),随后会以属性名:org.springframework.web.servlet.FrameworkServlet.CONTEXT. + servletNamevalue为此容器的方法存入ServletContext的属性中。主要是FrameworkServlet#initServletBean方法。
  3. 初始化Servlet使用的MVC组件。主要是DispatcherServlet#onRefresh方法。

2 HttpServletBean#init()设置init属性

  HttpServletBeaninit()方法就是将当前ServletConfiginit parameters参数填充到当前DispatcherServlet的实例的对应的属性中。
  也就是将web.xml<servlet/>标签下的<init-param/>标签定义的属性设置到对应Servlet实例的对应名字的属性中,最常见属性就是contextConfigLocation、namespace等等。
  在属性填充完毕之后们将会调用initServletBean方法,HttpServletBean提供了空的实现,主要用于被子类重写并实现自定义的扩展逻辑。它的子类FrameworkServlet就重写了该方法。

/** * GenericServlet的init方法 * <p> * 应该被子类重写的方法 */
public void init() throws ServletException { 
}

/** * HttpServletBean的属性 * <p> * 必须的参数集合,默认是一个空集合 */
private final Set<String> requiredProperties = new HashSet<>(4);

/** * HttpServletBean的方法 * <p> * 将配置参数映射到该servlet的bean属性上,并调用子类初始化。 * * @throws ServletException 如果bean属性无效(或缺少必需的属性),或者子类初始化失败。 */
@Override
public final void init() throws ServletException { 
    /* * 1 新建一个ServletConfigPropertyValues实例,根据Servlet的init参数设置bean属性 * 就是将ServletConfig内部的init parameters初始化参数存储到ServletConfigPropertyValues中 * 如果有requiredProperties中的必须属性没有设置,那么抛出ServletException */
    PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
    //如果有参数
    if (!pvs.isEmpty()) { 
        try { 
            //获取当前DispatcherServlet 对象的BeanWrapper对象,以JavaBeans的样式便捷的访问属性。
            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
            //ServletContext的资源加载器
            ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
            //自定义的属性编辑器
            bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
            //初始化DispatcherServlet的BeanWrapper,该方法是一个空实现
            initBeanWrapper(bw);
            /* * 2 通过BeanWrapper便捷的将解析出来的属性集合设置给DispatcherServlet的各种属性 */
            bw.setPropertyValues(pvs, true);
        } catch (BeansException ex) { 
            if (logger.isErrorEnabled()) { 
                logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
            }
            throw ex;
        }
    }
    /* * 3 扩展接口,让子类进行他们自定义的初始化 * 子类FrameworkServlet就重写了该方法 */
    initServletBean();
}

/** * HttpServletBean的方法 * <p> * 子类可以重写此方法以执行自定义初始化。 * 在调用此方法之前,将设置此servlet的所有bean属性。 * <p> * 此默认实现为空。 */
protected void initServletBean() throws ServletException { 
}

3 FrameworkServlet#initServletBean初始化MVC容器

  FrameworkServlet重写的initServletBean方法。
  其内部首先调用initWebApplicationContext方法用于初始化并发布此Servlet关联的WebApplicationContext容器,随后调用initFrameworkServlet方法初始化FrameworkServlet本身,子类可以重写此方法以执行其所需的任何初始化,默认实现为空。

/** * FrameworkServlet的属性 * <p> * 此Servlet关联的WebApplicationContext */
@Nullable
private WebApplicationContext webApplicationContext;

/** 1. FrameworkServlet的方法 2. <p> 3. 设置所有bean属性后调用的方法,主要目的是创建此Servlet的WebApplicationContext。 */
@Override
protected final void initServletBean() throws ServletException { 
    getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
    if (logger.isInfoEnabled()) { 
        logger.info("Initializing Servlet '" + getServletName() + "'");
    }
    //当前时间戳
    long startTime = System.currentTimeMillis();

    try { 
        /* * 1 初始化并发布此Servlet关联的WebApplicationContext,核心方法 */
        this.webApplicationContext = initWebApplicationContext();
        /* * 2 初始化FrameworkServlet,在设置任何bean属性并加载WebApplicationContext后,将调用此方法。 * * 子类可以重写此方法以执行其所需的任何初始化,默认实现为空。 */
        initFrameworkServlet();
    } catch (ServletException | RuntimeException ex) { 
        logger.error("Context initialization failed", ex);
        throw ex;
    }

    if (logger.isDebugEnabled()) { 
        String value = this.enableLoggingRequestDetails ?
                "shown which may lead to unsafe logging of potentially sensitive data" :
                "masked to prevent unsafe logging of potentially sensitive data";
        logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
                "': request parameters and headers will be " + value);
    }

    if (logger.isInfoEnabled()) { 
        logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
    }
}

3.1 initWebApplicationContext初始化子MVC容器

  该方法用于初始化并发布此Servlet关联的WebApplicationContext,也就是子容器。实际上大部分情况是通过内部的createWebApplicationContext方法实际创建上下文,可以在子类中覆盖。

  1. 获取Root WebApplicationContext作为父上下文,Root WebApplicationContext是通过ContextLoaderListener初始化的,可以为null。
  2. 如果webApplicationContext属性不为null,那么直接调用configureAndRefreshWebApplicationContext配置并刷新该WebApplicationContext,一般都是null。
  3. 如果此前没有关联的WebApplicationContext,那么首先会尝试查找现成的context。首先当前Servlet中获取名为contextAttribute<init-param/>初始化参数的值,该值作为属性名用于检索该servlet应该使用的WebApplicationContext。然后从当前ServletContext中调用getAttribute方法基于该属性名获取对应属性值。如果获取的属性值不为null,那么将该值作为已初始化完成了的WebApplicationContext实例返回,如果获取的属性值为null,那么抛出:“err.servlet_config_not_initialized“异常。一般都不会找到。
  4. 如果在属性中没有指定的WebApplicationContext实例,一般来说都没有。那么调用createWebApplicationContext实例化此servlet关联的WebApplicationContext,可以是默认的XmlWebApplicationContext或自定义上下文类(如果已设置)
  5. 如果此前还没有调用onRefresh方法,那么调用一次onRefresh方法,该方法是一个可以被子类重写的模板方法,用于特定的servlet的添加自定义的刷新工作,在成功刷新WebApplicationContext后调用。
    1. 如果是新建子MVC容器,容器刷新完毕后会发送ContextRefreshedEvent事件,会触发ContextRefreshListener监听器回调。该监听器的回调就是执行FrameworkServlet的onApplicationEvent方法,内部就会执行该方法,并更改标志为true。
    2. 如果上下文是不具有刷新支持的ConfigurableApplicationContext,或者在构造时注入的上下文已被刷新,那么标志就是false。
  6. 如果应该将此WebApplicationContext发布为ServletContext的属性(默认需要设置),那么就设置为ServletContext的一个属性。属性名为org.springframework.web.servlet.FrameworkServlet.CONTEXT.+servletName
/** * FrameworkServlet的方法 * <p> * 初始化并发布此Servlet关联的WebApplicationContext。 * 通过内部的createWebApplicationContext方法实际创建上下文,可以在子类中覆盖。 * * @return WebApplicationContext实例 */
protected WebApplicationContext initWebApplicationContext() { 
    /* * 1 获取Root WebApplicationContext * 父上下文通过ContextLoaderListener初始化,可以为null */
    WebApplicationContext rootContext =
            WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;
    /* * 2 如果webApplicationContext属性不为null,那么直接配置并刷新该WebApplicationContext */
    if (this.webApplicationContext != null) { 
        //到这里表示在构造时注入了一个上下文实例->直接使用它
        wac = this.webApplicationContext;
        if (wac instanceof ConfigurableWebApplicationContext) { 
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
            // 上下文尚未刷新->提供诸如设置父上下文,设置应用程序上下文ID等服务。
            if (!cwac.isActive()) { 
                if (cwac.getParent() == null) { 
                    //上下文实例是在没有显式父级的情况下注入的->将根应用程序上下文(如果有可能为null)设置为父级
                    cwac.setParent(rootContext);
                }
                //配置并初始化此上下文容器
                configureAndRefreshWebApplicationContext(cwac);
            }
        }
    }
    /* * 3 如果wac为null,即此前没有关联的WebApplicationContext * * 首先当前Servlet中获取名为contextAttribute的<init-param/>初始化参数的值, * 该值作为属性名用于检索该servlet应该使用的WebApplicationContext。 * 然后从当前ServletContext中调用getAttribute方法基于该属性名获取对应属性值。 * 如果获取的属性值不为null,那么将该值作为已初始化完成了的WebApplicationContext实例返回, * 如果获取的属性值为null,那么抛出:“err.servlet_config_not_initialized“异常。 */
    if (wac == null) { 
        wac = findWebApplicationContext();
    }
    /* * 4 如果wac还是为null,即在属性中没有指定的WebApplicationContext实例,一般来说都没有 * * 那么实例化此servlet关联的WebApplicationContext,可以是默认的XmlWebApplicationContext或自定义上下文类(如果已设置)。 */
    if (wac == null) { 
        // No context instance is defined for this servlet -> create a local one
        wac = createWebApplicationContext(rootContext);
    }
    /* * 5 如果此前还没有调用onRefresh方法 * * 那么调用一次onRefresh方法,该方法是一个可以被子类重写的模板方法 * 用于特定的servlet的添加自定义的刷新工作,在成功刷新WebApplicationContext后调用。 * * 如果是新建子MVC容器,容器刷新完毕后会发送ContextRefreshedEvent事件,会触发ContextRefreshListener监听器回调 * 该监听器的回调就是执行FrameworkServlet的onApplicationEvent方法,内部就会执行该方法,并更改标志为true * 如果上下文是不具有刷新支持的ConfigurableApplicationContext,或者在构造时注入的上下文已被刷新,那么标志就是false */
    if (!this.refreshEventReceived) { 
        //上下文是不具有刷新支持的ConfigurableApplicationContext,或者在构造时注入的上下文已被刷新
        //那么在此处手动触发onRefresh初始化方法
        synchronized (this.onRefreshMonitor) { 
            onRefresh(wac);
        }
    }
    /* * 6 如果应该将此WebApplicationContext发布为ServletContext的属性,那么就设置为属性,默认需要设置 */
    if (this.publishContext) { 
        // Publish the context as a servlet context attribute.
        //属性名为org.springframework.web.servlet.FrameworkServlet.CONTEXT. + servletName
        String attrName = getServletContextAttributeName();
        //设置为ServletContext的一个属性
        getServletContext().setAttribute(attrName, wac);
    }
    return wac;
}


/** * FrameworkServlet的方法 * <p> * WebApplicationContext的ServletContext属性的前缀 */
public static final String SERVLET_CONTEXT_PREFIX = FrameworkServlet.class.getName() + ".CONTEXT.";

/** 1. 返回此Servlet关联的WebApplicationContext在ServletContext中的属性名称。 2. 默认实现返回SERVLET_CONTEXT_PREFIX + servletName */
public String getServletContextAttributeName() { 
    return SERVLET_CONTEXT_PREFIX + getServletName();
}

3.1.1 configureAndRefreshWebApplicationContext配置并刷新子容器

  该方法用于配置并刷新此Servlet关联的MVC子容器,步骤类似于此前文章中讲过的Root容器的同名方法。
  大概步骤如下:

  1. 设置该应用程序上下文的id(一般用不到),默认id就是"org.springframework.web.context.WebApplicationContext:"+项目路径+"/"+servletName,可以通过在web.xml中的<Servlet/>标签下配置名为contextId<init-param/>初始化参数来自定义子容器id。
  2. 将此项目的ServletContext设置给上下文的servletContext属性,将此Servlet的ServletConfig设置给上下文的servletConfig属性,将此Servlet的namespace设置给上下文的namespace属性,手动添加一个监听器SourceFilteringListener
    1. SourceFilteringListener内部包装了一个ContextRefreshListener,容器刷新完毕后会发送ContextRefreshedEvent事件,此时会触发监听器的回调,该监听器的回调就是执行FrameworkServlet的onApplicationEvent方法
  3. 获取容器的Environment环境变量对象,随后调用initPropertySources方法手动初始化Servlet属性源,该方法在refresh()刷新容器的方法之前执行,以确保servlet属性源已准备就绪,可以被refresh()方法正常使用。
  4. 调用postProcessWebApplicationContext方法,在刷新给定的WebApplicationContext并将其激活关联为该Servlet的上下文之前,对其进行后处理,即自定义容器。目前是一个空实现,子类可以重写。
  5. 调用applyInitializers方法用于继续对容器执行自定义操作。默认实现是通过web.xml中配置的<context-param>全局参数globalInitializerClasses,和来自当前<Servlet/>内部的<init-param>初始化参数contextInitializerClasses来确定指定了哪些ApplicationContextInitializer类,并执行初始化,随后使用AnnotationAwareOrderComparator排序(支持PriorityOrdered接口、Ordered接口、@Ordered注解、@Priority注解的排序),最后按照排序优先级从高到低依次调用每一个实例的initialize方法来初始化给定的servletContext。
    6 调用容器的refresh方法执行刷新操作,这是核心方法,我们在此前IoC容器初始化源码部分已经着重讲解了。该方法将会初始化容器,包括解析配置文件,创建Spring bean实例,执行各种回调方法等等……操作(源码非常多)。
/** * FrameworkServlet的属性 * <p> * 要分配的WebApplicationContext ID,可通过<init-param/>参数配置 */
@Nullable
private String contextId;

/** * FrameworkServlet的方法 * <p> * 配置并刷新新建的mvc WebApplicationContext * 该方法中会配置一系列Servlet的属性,初始化并调用ApplicationContextInitializer的扩展点(用于自定义root context),最后会执行refresh刷新容器。 * * @param wac 此servlet关联的上下文 */
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) { 
    /* * 1 如果wac的全路径identity字符串形式等于wac的id,那么设置应用程序上下文的id */
    if (ObjectUtils.identityToString(wac).equals(wac.getId())) { 
        //应用程序上下文ID仍设置为其原始默认值->可以通过<init-param/>参数配置
        if (this.contextId != null) { 
            wac.setId(this.contextId);
        } else { 
            //产生预设id,默认规则就是"org.springframework.web.context.WebApplicationContext:"+项目路径+"/"+servletName
            wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                    ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
        }
    }
    /* * 2 配置一系列属性 */
    //将此项目的ServletContext设置给上下文的servletContext属性
    wac.setServletContext(getServletContext());
    //将此Servlet的ServletConfig设置给上下文的servletConfig属性
    wac.setServletConfig(getServletConfig());
    //将此Servlet的namespace设置给上下文的namespace属性
    wac.setNamespace(getNamespace());
    //手动添加一个监听器SourceFilteringListener
    //在容器刷新完毕之后会发送ContextRefreshedEvent事件,此时就会触发监听器的回调
    //该监听器的回调就是执行FrameworkServlet的onApplicationEvent方法
    wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

    /* * 3 获取容器的Environment环境变量对象,随后调用initPropertySources方法手动初始化属性源 * 该方法在refresh()刷新容器的方法之前执行,以确保servlet属性源已准备就绪,可以被正常使用 */
    ConfigurableEnvironment env = wac.getEnvironment();
    if (env instanceof ConfigurableWebEnvironment) { 
        ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
    }
    /* * 4 在刷新给定的WebApplicationContext并将其激活关联为该Servlet的上下文之前,对其进行后处理 * 目前是一个空实现,子类可以重写 */
    postProcessWebApplicationContext(wac);
    /* * 5 对当前的WebApplicationContext实例应用给定的ApplicationContextInitializer,以实现自定义上下文的逻辑 * 这类似于在初始化Root WebApplicationContext的时候调用的customizeContext方法 */
    applyInitializers(wac);
    /* * 6 刷新(初始化)容器 * 这是核心方法,我们在此前IoC容器初始化源码部分已经着重讲解了 */
    wac.refresh();
}

3.1.1.1 getNamespace获取名称空间

  Servlet对应的容器的nameSpace就被设置为Servlet的namespace,可以通过设置该Servlet的名为nameSpace<init-param>初始化参数手动指定名称空间,如未指定,那么默认名称空间为servletName+"-servlet",即如果此servlet的servlet-name为"test",则该servlet使用的默认名称空间将解析为"test-servlet"

/** * FrameworkServlet中的常量属性 * <p> * WebApplicationContext名称空间的后缀。 * 如果此类的servlet在上下文中被命名为"test",则servlet使用的默认名称空间将解析为"test-servlet"。 */
public static final String DEFAULT_NAMESPACE_SUFFIX = "-servlet";

/** * FrameworkServlet的方法 * <p> * 获取此nameSpace * <p> * 返回此servlet的名称空间,如果未设置自定义名称空间,则返回默认方案: * 即默认nameSpace为servletName+"-servlet",也可以通过设置Servlet的nameSpace属性手动指定名称空间 */
public String getNamespace() { 
    return (this.namespace != null ? this.namespace : getServletName() + DEFAULT_NAMESPACE_SUFFIX);
}

3.1.1.2 applyInitializers应用ApplicationContextInitializer扩展

  基于ApplicationContextInitializer自定义MVC子容器,这个方法类似于在初始化Root WebApplicationContext的时候调用的customizeContext方法。
  默认实现是通过web.xml中配置的<context-param>全局参数globalInitializerClasses,和来自当前<Servlet/>内部的<init-param>初始化参数contextInitializerClasses来确定指定了哪些ApplicationContextInitializer类,并执行初始化,随后使用AnnotationAwareOrderComparator排序(支持PriorityOrdered接口、Ordered接口、@Ordered注解、@Priority注解的排序),最后按照排序优先级从高到低依次调用每一个实例的initialize方法来初始化给定的servletContext。

//FrameworkServlet的属性

/** * 实际要应用于上下文的ApplicationContextInitializer实例。 */
private final List<ApplicationContextInitializer<ConfigurableApplicationContext>> contextInitializers =
        new ArrayList<>();

/** * 在设置的ApplicationContextInitializer的全路径类名 */
@Nullable
private String contextInitializerClasses;

/** * FrameworkServlet的方法 * <p> * 在对给定容器应用刷新之前调用所有的ApplicationContextInitializer,一起带实现自定义容器的逻辑 * 这个方法类似于在初始化Root WebApplicationContext的时候调用的customizeContext方法 * * @param wac 配置的WebApplicationContext(尚未刷新) */
protected void applyInitializers(ConfigurableApplicationContext wac) { 
    /* * 1 获取并初始化全局ApplicationContextInitializer */
    //从servletContext中尝试获取globalInitializerClasses全局参数的值
    String globalClassNames = getServletContext().getInitParameter(ContextLoader.GLOBAL_INITIALIZER_CLASSES_PARAM);
    //如果设置了该全局参数
    if (globalClassNames != null) { 
        //根据",; \t\n"拆分值字符串
        for (String className : StringUtils.tokenizeToStringArray(globalClassNames, INIT_PARAM_DELIMITERS)) { 
            //根据给定的全路径名字符串初始化指定的ApplicationContextInitializer实例并加入contextInitializers集合中
            this.contextInitializers.add(loadInitializer(className, wac));
        }
    }
    /* * 1 获取并初始化当前Servlet的ApplicationContextInitializer */
    //当前Servlet如果设置了名为contextInitializerClasses的<init-param>初始化参数
    if (this.contextInitializerClasses != null) { 
        //根据",; \t\n"拆分值字符串
        for (String className : StringUtils.tokenizeToStringArray(this.contextInitializerClasses, INIT_PARAM_DELIMITERS)) { 
            //根据给定的全路径名字符串初始化指定的ApplicationContextInitializer实例并加入contextInitializers集合中
            this.contextInitializers.add(loadInitializer(className, wac));
        }
    }
    /* * 3 对该集合进行Order排序,可以支持PriorityOrdered接口、Ordered接口、@Ordered注解、@Priority注解的排序 * 比较优先级为PriorityOrdered>Ordered>@Ordered>@Priority,排序规则是order值越小排序越靠前,优先级越高 * 没有order值则默认排在尾部,优先级最低。 */
    AnnotationAwareOrderComparator.sort(this.contextInitializers);
    /* * 4 按照优先级从高到低依次调用ApplicationContextInitializer的initialize方法,传递的参数就是当前的应用程序上下文容器 */
    for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) { 
        initializer.initialize(wac);
    }
}

3.1.2 findWebApplicationContext查找上下文容器

  如果此前没有已存在的关联的容器,那么首先会尝试查找指定的容器。
  首先当前Servlet中获取名为contextAttribute<init-param/>初始化参数的值,该值作为属性名用于检索该servlet应该使用的WebApplicationContext。然后从当前ServletContext中调用getAttribute方法基于该属性名获取对应属性值。如果获取的属性值不为null,那么将该值作为已初始化完成了的WebApplicationContext实例返回,如果获取的属性值为null,那么抛出:“err.servlet_config_not_initialized“异常。
  这个逻辑一般是不会走的,因为Servlet的名为contextAttribute<init-param/>初始化参数一般没有设置。

/** * FrameworkServlet的方法 * <p> * 使用配置的名称从ServletContext属性检索已配置好的WebApplicationContext。 * 子类可以重写此方法以提供不同的WebApplicationContext检索策略。 * * @return 此Servlet的WebApplicationContext;如果找不到,则为null */
@Nullable
protected WebApplicationContext findWebApplicationContext() { 
    //尝试获取该Servlet的名为contextAttribute的<init-param/>初始化参数的值
    String attrName = getContextAttribute();
    //如果参数值为null,那么返回null
    if (attrName == null) { 
        return null;
    }
    //如果参数值不为null,那么从当前ServletContext中调用getAttribute方法基于该属性名获取对应属性值
    WebApplicationContext wac =
            WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
    //如果参数值为null,那么抛出"No WebApplicationContext found: initializer not registered?"异常
    if (wac == null) { 
        throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
    }
    //返回结果
    return wac;
}


/** * ServletContext的属性名字符串,可在其中找到WebApplicationContext。 */
@Nullable
private String contextAttribute;

/** 1. 返回ServletContext属性的名称,该属性应用于检索该servlet应该使用的WebApplicationContext。 */
@Nullable
public String getContextAttribute() { 
    return this.contextAttribute;
}

3.1.3 createWebApplicationContext创建子上下文容器

  如果没有找到上下文容器,那么创建与此Servlet绑定的MVC上下文容器。其核心步骤和此前创建Root容器的步骤差不多,包含了创建和刷新的工作。

  1. 获取给定的上下文类Class,默认是XmlWebApplicationContext.class,可以通过此Servlet的名为contextClass<init-param>初始化参数配置。
  2. 用无参构造器,根据给定的Class创建一个上下文实例。
  3. 设置WebApplicationContext的配置路径,通过此Servlet的名为contextConfigLocation<init-param>初始化参数配置。
  4. 调用configureAndRefreshWebApplicationContext方法配置并刷新新建的mvc WebApplicationContext,这个方法我们在此前介绍过了。
/** * FrameworkServlet的方法 * <p> * 实例化此servlet的WebApplicationContext,可以是默认的XmlWebApplicationContext或自定义上下文类(如果已设置)。 * 内部委托给createWebApplicationContext(ApplicationContext)方法 * * @param parent 要使用的父WebApplicationContext;如果没有,则为 null * @return 该servlet关联的WebApplicationContext */
protected WebApplicationContext createWebApplicationContext(@Nullable WebApplicationContext parent) { 
    return createWebApplicationContext((ApplicationContext) parent);
}


/** * FrameworkServlet的方法 * <p> * 实例化此servlet的WebApplicationContext,可以是默认的XmlWebApplicationContext或自定义上下文类(如果已设置)。 * * @param parent 要使用的父ApplicationContext;如果没有,则为null * @return 该servlet关联的WebApplicationContext */
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) { 
    /* * 1 获取给定的上下文类,默认是XmlWebApplicationContext.class * 可以通过此Servlet的名为contextClass的<init-param>初始化参数配置 */
    Class<?> contextClass = getContextClass();
    //如果不是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");
    }
    /* * 2 调用无参构造器,根据给定的Class创建一个上下文实例 */
    ConfigurableWebApplicationContext wac =
            (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
    //设置环境变量
    wac.setEnvironment(getEnvironment());
    //设置父容器
    wac.setParent(parent);
    /* * 3 设置WebApplicationContext的配置路径 */
    //获取上下文的配置路径,通过此Servlet的名为contextConfigLocation的<init-param>初始化参数配置
    String configLocation = getContextConfigLocation();
    if (configLocation != null) { 
        //如果配置了该初始参数,则设置为容器的configLocation参数,支持使用",; \t\n"拆分
        wac.setConfigLocation(configLocation);
    }
    /* * 4 配置并刷新新建的mvc WebApplicationContext */
    configureAndRefreshWebApplicationContext(wac);

    return wac;
}

/** * FrameworkServlet的属性 * <p> * 默认创建的WebApplicationContext实现类,就是XmlWebApplicationContext.class */
private Class<?> contextClass = DEFAULT_CONTEXT_CLASS;

/** * FrameworkServlet的属性 * <p> * 指定上下文配置位置。 */
@Nullable
private String contextConfigLocation;

/** * FrameworkServlet的属性 * <p> * 返回使用的上下文类 */
public Class<?> getContextClass() { 
    return this.contextClass;
}

/** * 返回显式指定的上下文配置位置(如果有)。 */
@Nullable
public String getContextConfigLocation() { 
    return this.contextConfigLocation;
}

3.1.4 ContextRefreshListener监听器

  configureAndRefreshWebApplicationContext方法中,会手动添加一个手动添加一个监听器SourceFilteringListenerSourceFilteringListener内部包装了一个ContextRefreshListener,容器刷新完毕后会发送ContextRefreshedEvent事件,此时会触发监听器的回调,该监听器的回调就是执行FrameworkServlet的onApplicationEvent方法。

/** * ApplicationListener endpoint that receives events from this servlet's WebApplicationContext * only, delegating to {@code onApplicationEvent} on the FrameworkServlet instance. */
private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> { 

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

  onApplicationEvent方法的默认实现调用onRefresh方法,触发此Servlet的上下文相关状态的刷新。

//FrameworkServlet的方法

/** * 用于检测是否已调用onRefresh的标志。 */
private volatile boolean refreshEventReceived = false;

/** * 监视同步的onRefresh方法执行的同步监视器 */
private final Object onRefreshMonitor = new Object();

/** * FrameworkServlet的方法 * <p> * 从此Servlet的WebApplicationContext接收刷新事件的回调。 * 默认实现调用onRefresh,触发此Servlet的上下文相关状态的刷新。 * * @param event 传入的ApplicationContext事件 */
public void onApplicationEvent(ContextRefreshedEvent event) { 
    //标志位改为true
    this.refreshEventReceived = true;
    //添加同步
    synchronized (this.onRefreshMonitor) { 
        //调用onRefresh方法
        onRefresh(event.getApplicationContext());
    }
}

/** * FrameworkServlet的方法 * <p> * 可以重写的模板方法以添加特定于servlet的刷新工作,成功刷新上下文后调用。 * 此实现为空,子类可重写该方法 * * @param context 当前的WebApplicationContext */
protected void onRefresh(ApplicationContext context) { 
    // For subclasses: do nothing by default.
}

4 DispatcherServlet#onRefresh初始化MVC组件

  FrameworkServletonRefresh方法的默认实现为,子类DispatcherServlet重写了该方法,用于初始化此servlet使用的各种MVC组件对象(通过initStrategies方法),这同样是一个核心方法。该方法执行之前,Root WebApplicationContext和DispatcherServlet关联的Child WebApplicationContext都已经创建并初始化完毕。
  需要注意的是,通过DispatcherServlet.properties默认加载的组件仅会创建对象实例,仅提供最基本的mvc功能而并没有激活其他默认配置,而如果通过@EnableWebMvc注解或者<mvc:annotation-driven/>标签开启mvc配置之后,除了会注册这些组件之外,还会加载一些默认配置,并且支持自定义配置。
  比如,如果不开启MVC配置,那么mvc就不支持application/json请求和响应(即使有jackson的依赖也不会自动注册MappingJackson2HttpMessageConverter),也就无法提供REST风格的请求、响应和异常处理,没有默认的conversionService

/** * DispatcherServlet的方法 * <p> * 此实现内部调用initStrategies方法 */
@Override
protected void onRefresh(ApplicationContext context) { 
    initStrategies(context);
}

/** * DispatcherServlet的方法 * <p> * 默认实现是初始化此DispatcherServlet使用的组件对象,可以在子类中重写以实现其他的自定义逻辑 */
protected void initStrategies(ApplicationContext context) { 
    /* * 1 初始化MultipartResolver,用于处理上传请求 * * 文件上传时就可以使用MultipartResolver来解析上传请求中的文件数据,方便快捷的实现文件上传! */
    initMultipartResolver(context);
    /* * 2 初始化LocaleResolver,区域解析器 * * 用户的区域也称为Locale,Locale信息是可以由前端直接获取的,可以根据不同的用户区域展示不同的视图, * 比如为不同区域的用户可以设置不同的语言和时区,也就是提供国际化视图支持。 */
    initLocaleResolver(context);
    /* * 3 初始化ThemeResolver,主题解析器 * * 主题就是系统的整体样式或风格,可通过Spring MVC框架提供的主题(theme) * 设置应用不同的整体样式风格,提高用户体验。Spring MVC的主题就是一些静态资源的集合, * 即包括样式及图片,用来控制应用的视觉风格。主题也支持国际化,同一个主题不同区域也可以显示不同的风格。 */
    initThemeResolver(context);
    /* * 4 初始化HandlerMapping,处理器映射器 * * 用于查找能够处理请求的Handler,将请求映射为HandlerExecutionChain 对象 * (包含一个Handler处理器对象、多个 HandlerInterceptor 拦截器)对象。 */
    initHandlerMappings(context);
    /* * 5 初始化HandlerAdapter,处理器适配器 * 帮助 DispatcherServlet调用请求映射到的Handler,但是不管Handler实际如何调用。 * 将会返回一个ModelAndView对象,其中model是一个Map结构,存放了我们返回的所有数据,view是逻辑视图名,即ViewName。 */
    initHandlerAdapters(context);
    /* * 6 初始化HandlerExceptionResolver,异常解析器 * 如果在前面执行handler的过程中抛出了某个异常,将会走异常解析器的方法! * 在异常解析器中可以将此错误映射到其他handler、HTML 错误视图(错误页面)或统一抛出自己的异常。 */
    initHandlerExceptionResolvers(context);
    /* * 7 初始化RequestToViewNameTranslator,请求到视图名的转换器 * * 当未明确提供视图名称时,用于将传入的HttpServletRequest转换为逻辑视图名称的转换器。 */
    initRequestToViewNameTranslator(context);
    /* * 8 初始化ViewResolver,视图解析器 * * ViewResolver根据handler执行之后返回的ModelAndView中的String类型的逻辑视图名解析成物理视图名,即具体的资源地址 * 再生成对应的View视图对象。但是具体的事务解析以及数据填充工作由View视图自己完成(View. render方法)。 */
    initViewResolvers(context);
    /* * 9 初始化FlashMapManager,FlashMap管理器 * * 用于存储并检索FlashMap,这些FlashMap可用于将属性从一个请求传递到另一个请求,通常是用在重定向中。 * 也就是说FlashMap主要用在redirect中传递参数,而FlashMapManager则用于管理这些FlashMap。 */
    initFlashMapManager(context);
}

  从源码中我们可以看到,该方法会尝试初始化各种常见或者不常见的组件,包括MultipartResolver、LocaleResolver、ThemeResolver、HandlerMapping、HandlerAdapter、HandlerExceptionResolver、RequestToViewNameTranslator、ViewResolver、FlashMapManager
  下面我们看看常见组件的初始化源码!

4.1 initMultipartResolver

  初始化MultipartResolver,会尝试从此Servlet关联的ApplicationContext容器中获取beanName为multipartResolver,类型为MultipartResolver的bean。
  找到的multipartResolver对象将绑定给此DispatcherServlet对象的multipartResolver属性,如果没找到则不会抛出异常,而是该属性赋值为null,即不提供multipart处理功能

//DispatcherServlet的的属性

/** * Bean工厂中此servlet使用的MultipartResolver对象的beanName */
public static final String MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver";

/** * 此servlet使用的MultipartResolver。 */
@Nullable
private MultipartResolver multipartResolver;

/** * DispatcherServlet的方法 * <p> * 初始化此类使用的MultipartResolver。 * 如果在BeanFactory中没有给定名称为"multipartResolver"以及类型为MultipartResolver的bean,则不会提供multipart处理功能。 */
private void initMultipartResolver(ApplicationContext context) { 
    try { 
        //从容器中获取名称为"multipartResolver"以及类型为MultipartResolver的多部件处理器,并赋给multipartResolver
        this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
        if (logger.isTraceEnabled()) { 
            logger.trace("Detected " + this.multipartResolver);
        } else if (logger.isDebugEnabled()) { 
            logger.debug("Detected " + this.multipartResolver.getClass().getSimpleName());
        }
    } catch (NoSuchBeanDefinitionException ex) { 
        // Default is no multipart resolver.
        //如果没找到就赋值为null,即不提供multipart处理功能,而不是抛出异常
        this.multipartResolver = null;
        if (logger.isTraceEnabled()) { 
            logger.trace("No MultipartResolver '" + MULTIPART_RESOLVER_BEAN_NAME + "' declared");
        }
    }
}

4.2 initHandlerMappings

  初始化HandlerMapping,HandlerMapping可以通过配置名为detectAllHandlerMappings<init-param>初始化参数来控制是否尝试从此Servlet关联的ApplicationContext容器中查找所有的HandlerMapping的bean或者仅仅在容器中查找名为“handlerMapping”的单个HandlerMapping的bean,该参数默认为true
  如果以上两种方式都没有在容器中找到至少一个HandlerMapping的bean实例,那么将尝试注册默认的HandlerMapping来确保至少有一个HandlerMapping,spring-webmvc-5.2.8.RELEASE的依赖中默认的HandlerMapping有三个,类型为org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping、org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping、org.springframework.web.servlet.function.support.RouterFunctionMapping
  所有的默认组件都被定义在与DispatcherServlet同级包路径的DispatcherServlet.properties文件中。

//DispatcherServlet的属性

/** * 此servlet使用的HandlerMapping列表 */
@Nullable
private List<HandlerMapping> handlerMappings;

/** * 是否需要检测所有HandlerMappings还是只期望检测单个名为"handlerMapping"的bean? */
private boolean detectAllHandlerMappings = true;

/** * Bean工厂中的HandlerMapping对象的beanName,仅在关闭detectAllHandlerMappings=false时使用。 */
public static final String HANDLER_MAPPING_BEAN_NAME = "handlerMapping";

/** * DispatcherServlet的方法 * <p> * 初始化此类使用的HandlerMappings。 * 如果在BeanFactory中没有为此Servlet定义HandlerMapping的Bean,则默认为BeanNameUrlHandlerMapping。 */
private void initHandlerMappings(ApplicationContext context) { 
    //handlerMappings置为null
    this.handlerMappings = null;
    //是否需要检测所有的HandlerMapping,默认需要,可以通过init-param初始化参数指定
    //如果需要
    if (this.detectAllHandlerMappings) { 
        // 在ApplicationContext中找到所有HandlerMapping实例,包括Root ApplicationContext,不包括非单例的
        //返回一个map key为beanName,value为对应的实例
        Map<String, HandlerMapping> matchingBeans =
                BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
        //如果ApplicationContext中具有至少一个HandlerMapping的bean
        if (!matchingBeans.isEmpty()) { 
            //获取map的所有value,即所有的HandlerMapping实例集合
            this.handlerMappings = new ArrayList<>(matchingBeans.values());
            //最后使用AnnotationAwareOrderComparator比较器对handlerMappings进行排序,这说明handlerMapping支持order排序
            //该比较器支持Ordered、PriorityOrdered接口,以及@Order、@Priority注解的排序,比较优先级为PriorityOrdered>Ordered>@Ordered>@Priority,
            //排序规则是order值越小排序越靠前,优先级越高,没有order值则默认排在尾部,优先级最低。
            AnnotationAwareOrderComparator.sort(this.handlerMappings);
        }
    } else { 
        //如果不需要
        try { 
            //那么仅仅获取名为"handlerMapping"类型为HandlerMapping的单个bean实例
            HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
            //仅仅使用单个handlerMapping
            this.handlerMappings = Collections.singletonList(hm);
        } catch (NoSuchBeanDefinitionException ex) { 
            // Ignore, we'll add a default HandlerMapping later.
            //如果没有找到符合条件的bean,那么该异常被忽略,稍后我们将添加默认的HandlerMapping。
        }
    }

    /* * 1 如果handlerMappings还是为null,那说明 * 在ApplicationContext中找不到任何HandlerMapping的bean或者找不到符合条件的HandlerMapping * 那么通过注册默认的HandlerMapping来确保至少有一个HandlerMapping * * 在Spring 5.2.8.RELEASE中,默认注册的HandlerMapping有三个,分别为 * org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping * org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping * org.springframework.web.servlet.function.support.RouterFunctionMapping * 最重要的就是RequestMappingHandlerMapping了 */
    if (this.handlerMappings == null) { 
        this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
        if (logger.isTraceEnabled()) { 
            logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
                    "': using default strategies from DispatcherServlet.properties");
        }
    }
}

4.2.1 getDefaultStrategies获取默认组件

  在初始化上面的那些组件的时候,如果在容器中没有指定组件bean,那么都会调用该方法获取默认的组件。该方法用于获取默认组件对象的列表,默认实现使用"DispatcherServlet.properties"文件(与DispatcherServlet类位于同一包中)来确定使用的默认组件。
  最开始获取的都是默认组件的全路径名字符串,随后会转换为Class并通过ApplicationContext内部的BeanFactory调用createDefaultStrategy方法来实例化指定具体类型的组件对象,该过程中将会执行Bean的完整初始化应用所有适用的BeanPostProcessor,还会填充带注入注解的字段和方法,以及应用所有标准的bean初始化回调,但是通过此方法创建的默认实例不会被加入Spring容器中管理。

/** * DispatcherServlet的方法 * <p> * 获取默认组件对象的列表,默认实现使用"DispatcherServlet.properties"文件(与DispatcherServlet类位于同一包中)来确定使用的默认组件。 * 通过ApplicationContext内部的BeanFactory来实例化组件对象,将会执行Bean的完整初始化 * 应用所有适用的BeanPostProcessor,还会填充带注入注解的字段和方法,以及应用所有标准的bean初始化回调。 * * @param context 当前的WebApplicationContext * @param strategyInterface 组件接口类型 * @return 相应组件对象的列表 */
@SuppressWarnings("unchecked")
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) { 
    //组件接口的全路径名字符串
    String key = strategyInterface.getName();
    //获取对应的默认的组件类型字符串
    String value = defaultStrategies.getProperty(key);
    //如果有默认组件
    if (value != null) { 
        //依据","拆分字符串,因为value中可能包含多个组件的全路径名,且使用","分隔。
        String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
        //默认的组件实例集合
        List<T> strategies = new ArrayList<>(classNames.length);
        //遍历拆分后的组件全路径名
        for (String className : classNames) { 
            try { 
                //转化为Class
                Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
                //通过ApplicationContext内部的BeanFactory来实例化组件对象,将会执行Bean的完整初始化
                //应用所有适用的BeanPostProcessor,还会填充带注入注解的字段和方法,以及应用所有标准的bean初始化回调。
                Object strategy = createDefaultStrategy(context, clazz);
                //加入到集合中
                strategies.add((T) strategy);
            } catch (ClassNotFoundException ex) { 
                throw new BeanInitializationException(
                        "Could not find DispatcherServlet's default strategy class [" + className +
                                "] for interface [" + key + "]", ex);
            } catch (LinkageError err) { 
                throw new BeanInitializationException(
                        "Unresolvable class definition for DispatcherServlet's default strategy class [" +
                                className + "] for interface [" + key + "]", err);
            }
        }
        //返回
        return strategies;
    } else { 
        //返回空集合
        return new LinkedList<>();
    }
}

  源码中一个非常重要的属性就是defaultStrategies,该属性在DispatcherServlet这个类被加载的时候就在静态块中被初始化了,其内部的数据实际上就是从相对于DispatcherServlet类的DispatcherServlet.properties配置文件中加载的数据,就是一些默认的组件。key为组件接口全路径名,value为默认组件实例的全路径名字符串,多个默认组件名使用“,”分隔。

/** * 定义DispatcherServlet的默认组件名称的类路径资源的名称(相对于DispatcherServlet类)。 */
private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";

/** * DispatcherServlet.properties加载之后的map */
private static final Properties defaultStrategies;

/*DispatcherServlet的静态块,加载默认组件资源*/
static { 
    // 从属性文件加载默认组件实现。
    // 当前这严格是内部的,并不意味着应由应用程序开发人员自定义。
    try { 
        ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
        //加载到defaultStrategies集合中
        defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
    } catch (IOException ex) { 
        throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());
    }
}

  DispatcherServlet.properties配置文件在spring-webmvc的依赖中,和DispatcherServlet同一路径。

  通过查看该配置文件的内容,即可知道,某个版本的Spring MVC将会默认使用哪些组件, spring-webmvc-5.2.8.RELEASE版本中的配置文件内容如下:

org.springframework.web.servlet.LocaleResolver=org.springframework.web.ser
vlet.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

4.3 initLocaleResolver

  初始化LocaleResolver,首先会尝试从此Servlet关联的ApplicationContext容器中查找名为“localeResolver”的单个LocaleResolver的bean。
  如果没有在容器中找到指定的LocaleResolver的bean实例,那么将尝试注册默认的LocaleResolver,spring-webmvc-5.2.8.RELEASE的依赖中默认的LocaleResolver类型为org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
  所有的默认组件都被定义在与DispatcherServlet同级包路径的DispatcherServlet.properties文件中。

//DispatcherServlet的属性

/** * 此servlet使用的LocaleResolver。 */
@Nullable
private LocaleResolver localeResolver;

/** * Bean工厂中的LocaleResolver对象的beanName。 */
public static final String LOCALE_RESOLVER_BEAN_NAME = "localeResolver";

/** 1. DispatcherServlet的方法 2. <p> 3. 初始化此Servlet使用的LocaleResolver。 4. 如果在BeanFactory中没有为此给定名称的bean,则我们默认为AcceptHeaderLocaleResolver。 */
private void initLocaleResolver(ApplicationContext context) { 
    try { 
        //从此Servlet关联的ApplicationContext容器中查找名为“handlerMapping”的单个LocaleResolver的bean。
        this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
        if (logger.isTraceEnabled()) { 
            logger.trace("Detected " + this.localeResolver);
        } else if (logger.isDebugEnabled()) { 
            logger.debug("Detected " + this.localeResolver.getClass().getSimpleName());
        }
    } catch (NoSuchBeanDefinitionException ex) { 
        // We need to use the default.
        //如果没有获取到,那么使用默认的localeResolver,即org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
        this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
        if (logger.isTraceEnabled()) { 
            logger.trace("No LocaleResolver '" + LOCALE_RESOLVER_BEAN_NAME +
                    "': using default [" + this.localeResolver.getClass().getSimpleName() + "]");
        }
    }
}

4.4 其他组件的初始化

  initHandlerAdapters、initHandlerExceptionResolvers、initViewResolvers这三个方法的源码原理和上面的initHandlerMappings方法非常相似,initThemeResolver、initRequestToViewNameTranslator、initFlashMapManager这三个方法的源码原理和上面的initLocaleResolver方法非常相似,在此不再赘述。所有的默认组件都被定义在与DispatcherServlet同级包路径的DispatcherServlet.properties文件中。
  spring-webmvc-5.2.8.RELEASE的依赖中:

  1. 默认的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。
  2. 默认的HandlerExceptionResolver有三个,类型为org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver、org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver、org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver。
  3. 默认的HandlerExceptionResolver有一个,类型为org.springframework.web.servlet.view.InternalResourceViewResolver。
  4. 默认的ThemeResolver有一个,类型为org.springframework.web.servlet.theme.FixedThemeResolver。
  5. 默认的RequestToViewNameTranslator有一个,类型为org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator。
  6. 默认的FlashMapManager有一个,类型为org.springframework.web.servlet.support.SessionFlashMapManager。

5 总结

  本次我们学习了DispatcherServlet与子容器的初始化,以及各种MVC组件的初始化,核心方法就是Servlet的init()方法,该方法大概又可以分为三步,分别涉及到三个核心子方法:

  1. 将当前ServletConfiginit parameters参数填充到当前DispatcherServlet的实例的对应的属性中,方便后面的步骤中使用。可以在web.xml中对应的<Servlet>标签下通过设置<init-param>标签来配置各种属性。主要是HttpServletBean#init()方法。
  2. 初始化于此Servlet关联的MVC子容器,该容器的父容器就是Root Application(可能为null),随后会以属性名:org.springframework.web.servlet.FrameworkServlet.CONTEXT. + servletNamevalue为此容器的方法存入ServletContext的属性中。主要是FrameworkServlet#initServletBean()方法。
  3. 初始化Servlet使用的MVC组件。主要是DispatcherServlet#onRefresh()方法。

相关文章:
  https://spring.io/
  Spring Framework 5.x 学习
  Spring MVC 5.x 学习
  Spring Framework 5.x 源码

如有需要交流,或者文章有误,请直接留言。另外希望点赞、收藏、关注,我将不间断更新各种Java学习博客!

    原文作者:刘Java
    原文地址: https://blog.csdn.net/weixin_43767015/article/details/117934672
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系管理员进行删除。