精华内容
下载资源
问答
  • 基于最新Spring 5.x,详细介绍了Spring MVC 初始化流程的源码,主要包括DispatcherServlet与MVC子容器的初始化,以及各种MVC组件初始化

      基于最新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学习博客!

    展开全文
  • COM组件初始化

    千次阅读 2016-02-15 10:36:39
    任何一个使用COM组件的windows程序在初始化COM库的时候都要调用CoInitializeEx函数,每一个使用COM接口的线程必须单独调用这个函数。
        任何一个使用COM组件的windows程序在初始化COM库的时候都要调用CoInitializeEx函数,每一个使用COM接口的线程必须单独调用这个函数。下面是这个函数的声明:
    
    HRESULT CoInitializeEx(LPVOID pvReserved, DWORD dwCoInit);
    第一个参数是保留参数,为空。第二个参数规定了将使用的线程模型。COM接口支持两种不同的线程模型:单线程和多线程。一旦指定了单线程模型,就以意味着:
    1.你将通过单线程来访问每一个COM对象;在不同的线程之间不可以共享COM接口指针;
    2.线程将会有一个消息队列。
    如果上述任何一条不满足的话,就需要指定multithreaded模型。下表为两种模型的标识符。 
    

    Flag Description

    COINIT_APARTMENTTHREADED Apartment threaded.
    COINIT_MULTITHREADED Multithreaded.

    你必须设置清楚上述标记位。通常来讲,创建窗口的线程应该使用COINIT_APARTMENTTHREADED ,其它线程应该使用COINIT_MULTITHREADED。然而,一些COM组件需要特定的线程模型,在MSDN文档中会对这种情况有所介绍。
    实际上,即使你指定了apartment线程,通过使用marshaling技术,线程之间共享接口也是有可能的;但是,你不能够将一个接口指针复制给另外一个线程。下面给出了
    
    HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
    这个函数返回类型HRESULT包含了正确或者错误的代码。下文中我将介绍错误处理。每一次成功调用CoInitializeEx之后,你必须调用CoUninitialize函数,该函数定义如下
    
    CoUninitialize();

    Note Actually, even if you specify apartment threading, it is still possible to share interfaces between threads, by using a technique called marshaling. Marshaling is beyond the scope of this module. The important point is that with apartment threading, you must never simply copy an interface pointer to another thread. For more information about the COM threading models, see Processes, Threads, and Apartments and Understanding and Using COM Threading Models.

    In addition to the flags already mentioned, it is a good idea to set the COINIT_DISABLE_OLE1DDE flag in the dwCoInit parameter. Setting this flag avoids some overhead associated with Object Linking and Embedding (OLE) 1.0, an obsolete technology.

    展开全文
  • 使用Dialog弹窗时,在打开或者关闭的...可以考虑使用v-if对Dialog组件进行销毁和创建,这样每次都会自动初始化组件内的数据。个人觉得损失一些性能提高开发速度和代码可维护性,是可以接受的。 比如组件AddFinan...

    使用Dialog弹窗时,在打开或者关闭的时候需要对数据进行初始化,在初始过程中,一旦出现遗漏就有可能导致bug。

    就好像下面这种方式,不但代码冗余,且不易维护,一旦新增字段又需要一个个修改。

    如何结局这个问题呢?可以考虑使用v-if对Dialog组件进行销毁和创建,这样每次都会自动初始化组件内的数据。个人觉得损失一些性能提高开发速度和代码可维护性,是可以接受的。

    比如组件AddFinancialProduct是个利用dialog构建的弹窗组件,在父组件中通过修改visible的值,进行组件的创建和销毁:

    <AddFinancialProduct @save='searchList' ref='addFinancialProduct' :isWritable="getFundFinancialProductStatus" v-if="visible"></AddFinancialProduct>

     

    展开全文
  • vue的组件中里的组件单独初始化

    千次阅读 2019-04-08 09:40:16
    实现思路:在子组件data中定义一个showChild=false,每次进入子组件watch中把showChild=true 。关闭事件showChild=false。 在孙子组件上加一个v-if (例如:<孙子组件 v-if="showChild">),每次点击showChild...

    想实现的效果: 每次关闭子组件,孙子组件数据重新清空, 点击子组件重新加载孙子组件。

    实现思路:在子组件data中定义一个showChild=false,每次进入子组件watch中把showChild=true 。关闭事件showChild=false。

    在孙子组件上加一个v-if (例如:<孙子组件  v-if="showChild">),每次点击showChild就等于true

    展开全文
  • Flex容器和组件初始化创建事件

    千次阅读 2013-06-30 18:29:32
    在FLEX中,我们经常会用到,当容器或者这个组件初始化的时候,或者创建完成的时候,就去做一些事情。 那么此时Flex就会派发preinitialize、initialize和creationComplete事件。当子项初始化完成后,其父容器开始...
  • eCos组件初始化

    千次阅读 2013-10-15 20:30:41
    eCos组件初始化机制 eCos组件初始化利用了C++静态对象实例初始化的机制。C++对象在初始化时不像C语言中的静态变量那样只是在特定的内存单元写入特定的数值,C++对象在初始化时将会调用该对象类的...
  • 我需要拿到服务端返回来的数据对DatePicker组件进行初始化。 服务端返回来的数据格式如下:YYYYMMDD 一开始的解决方案: 在构造器阶段拿到数据后转换成moment形式的数据 constructor(props) { .... this.s...
  • 问题: 在前端vue中,经常遇到页面的data,需要进行数据初始化 原来的方法是: 1,设计一个初始化方法,把需要初始化的数据...现在可以在组件初始化的时候,对数据进行初始化 方法就是在 组件调用的地方 加入 v-if ...
  • react组件初始化时会触发的钩子函数 1、getDefaultProps() 设置默认的props,也可以用dufaultProps设置组件的默认属性。 2、getInitialState() 在使用es6的class语法时是没有这个钩子函数的,可以直接在...
  • Jacob组件无法初始化问题

    万次阅读 2016-03-24 12:12:23
    我用的是Jacob-1.9.jar,操作系统是32位,运行打开本地Excel文件时报错, java.lang.NoClassDefFoundError: Could ... 经排查,是因为将jacob.dll拷贝至%JAVA_HOME%\jre\bin目录所致。 另外,C:\Windows\System32目
  • Flex 组件初始化过程

    千次阅读 2012-04-16 20:17:19
    首先介绍一下SystemManager. SystemManager是Flex应用的主控者, 它... SystemManager是FlashPlayer实例的第一个类, 它存储了主应用窗口的大小和位置信息, 保存其子组件比如:浮动弹出窗口和模态窗口的痕迹. 通过System

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 532,739
精华内容 213,095
关键字:

下载组件未初始化