springmvc篇-(八)SpringMVC父子容器启动方式的源码解读

沙海 2021年4月24日09:27:40Java评论26字数 20796阅读69分19秒阅读模式
摘要

前一章节回顾: 通过前一个章节的了解,我们基本上了解了Spring父子容器的基本使用、原理和一些需要注意的点。本章节我们跟着源码看看,Spring父子容器到底是如何启动的,文章的最后也会对SpringMVC系列的文章做最后的总结。源码阅读其实并不难,难得只是我们少了一份好奇心和坚持。

文章源自JAVA秀-https://www.javaxiu.com/19961.html

前一章节回顾: 通过前一个章节的了解,我们基本上了解了Spring父子容器的基本使用、原理和一些需要注意的点。本章节我们跟着源码看看,Spring父子容器到底是如何启动的,文章的最后也会对SpringMVC系列的文章做最后的总结。源码阅读其实并不难,难得只是我们少了一份好奇心和坚持。文章源自JAVA秀-https://www.javaxiu.com/19961.html

文章源自JAVA秀-https://www.javaxiu.com/19961.html

1. Spring容器启动顶级模板方法startInternal的基本分析

我们把startInternal这个方法再次拿出来进行完整的分析,我们发现启动一个web后台服务,最少需要一个关键步骤(非基于spring开发的web后台服务),最多需要三个步骤(基于spring父子容器方式开发的web后台服务)。文章源自JAVA秀-https://www.javaxiu.com/19961.html

@Override
protected synchronized void startInternal() throws LifecycleException {

    //代码略...
    try {
        if (ok) {

            //web后台服务启动关键步骤一:解析web.xml文件,将该文件转换成为一个ServletContext对象,该对象就是一个web后台服务核心对象(根对象)。该对象创建成功后,web后台服务启动的最重要的工作就完成了。如果是非基于spring开发的后台服务,那么后台服务等于基本完成了,后面的代码都是非核心的处理代码了;如果是基于spring开发的后台服务,那么现在还只是完成一部分启动工作,后面还要启动spring。
            fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);
            // Start our child containers, if not already started
            for (Container child : findChildren()) {
                if (!child.getState().isAvailable()) {
                    child.start();
                }
            }

        }

        //代码略...

        // Configure and call application event listeners
        if (ok) {
            //web后台服务启动关键步骤二:如果当前web.xml中配置的将要使用的spring容器是父子容器结构的话,那么先在这里创建父容器,为后续创建子容器做准备(如果是父子结构,那么子容器需要持有父容器,所以一定要先创建父容器)。
            if (!listenerStart()) {
                log.error(sm.getString("standardContext.listenerFail"));
                ok = false;
            }
        }

        //代码略...

        // Load and initialize all "load on startup" servlets
        if (ok) {
            //web后台服务启动关键步骤三:1.如果当前的spring服务是单容器服务,那么这一行开始就是启动spring容器的开始;2.如果当前的spring服务是父子容器服务,那么这一行代码就是创建子容器的开始。
            if (!loadOnStartup(findChildren())){
                log.error(sm.getString("standardContext.servletFail"));
                ok = false;
            }
        }

        // Start ContainerBackgroundProcessor thread
        super.threadStart();
    } finally {
        // Unbinding thread
        unbindThread(oldCCL);
    }

    //代码略...
}

2. Spring父容器启动过程

要启动父容器,必须在web.xml中配置两个关键内容:文章源自JAVA秀-https://www.javaxiu.com/19961.html

<!-- 配置spring父容器的配置文件 -->
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath*:applicationContext.xml</param-value>
</context-param>

!-- 配置spring父容器的启动事件 -->
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

第一步:我们正式开始分析父容器的启动过程,因为web.xml中配置了上面的内容之后,tomcat容器在启动后台服务的过程中,会执行startInternal方法中的下面的代码(上一节有提到过):文章源自JAVA秀-https://www.javaxiu.com/19961.html

if (ok) {
    //web后台服务启动关键步骤二:如果当前web.xml中配置的将要使用的spring容器是父子容器结构的话,那么先在这里创建父容器,为后续创建子容器做准备(如果是父子结构,那么子容器需要持有父容器,所以一定要先创建父容器)。
    if (!listenerStart()) {
        log.error(sm.getString("standardContext.listenerFail"));
        ok = false;
    }
}

第二步:开始执行listenerStart方法,本方法专门用于执行web.xml中配置的监听器的监听方法。因为我们在web.xml中配置了ContextLoaderListener监听器,所以这个方法会调用ContextLoaderListener的初始化方法。文章源自JAVA秀-https://www.javaxiu.com/19961.html

    public boolean listenerStart() {

    if (log.isDebugEnabled())
        log.debug("Configuring application event listeners");

    // Instantiate the required listeners
    String listeners[] = findApplicationListeners();
    Object results[] = new Object[listeners.length];
    boolean ok = true;
    for (int i = 0; i < results.length; i++) {
        if (getLogger().isDebugEnabled())
            getLogger().debug(" Configuring event listener class '" +
                listeners[i] + "'");
        try {
            String listener = listeners[i];
            results[i] = getInstanceManager().newInstance(listener);
        } catch (Throwable t) {
            t = ExceptionUtils.unwrapInvocationTargetException(t);
            ExceptionUtils.handleThrowable(t);
            getLogger().error(sm.getString(
                    "standardContext.applicationListener", listeners[i]), t);
            ok = false;
        }
    }
    if (!ok) {
        getLogger().error(sm.getString("standardContext.applicationSkipped"));
        return false;
    }

    // Sort listeners in two arrays
    List<Object> eventListeners = new ArrayList<>();
    List<Object> lifecycleListeners = new ArrayList<>();
    for (int i = 0; i < results.length; i++) {
        if ((results[i] instanceof ServletContextAttributeListener)
            || (results[i] instanceof ServletRequestAttributeListener)
            || (results[i] instanceof ServletRequestListener)
            || (results[i] instanceof HttpSessionIdListener)
            || (results[i] instanceof HttpSessionAttributeListener)) {
            eventListeners.add(results[i]);
        }
        if ((results[i] instanceof ServletContextListener)
            || (results[i] instanceof HttpSessionListener)) {
            lifecycleListeners.add(results[i]);
        }
    }

    // Listener instances may have been added directly to this Context by
    // ServletContextInitializers and other code via the pluggability APIs.
    // Put them these listeners after the ones defined in web.xml and/or
    // annotations then overwrite the list of instances with the new, full
    // list.
    for (Object eventListener: getApplicationEventListeners()) {
        eventListeners.add(eventListener);
    }
    setApplicationEventListeners(eventListeners.toArray());
    for (Object lifecycleListener: getApplicationLifecycleListeners()) {
        lifecycleListeners.add(lifecycleListener);
        if (lifecycleListener instanceof ServletContextListener) {
            noPluggabilityListeners.add(lifecycleListener);
        }
    }
    setApplicationLifecycleListeners(lifecycleListeners.toArray());

    // Send application start events

    if (getLogger().isDebugEnabled())
        getLogger().debug("Sending application start events");

    // Ensure context is not null
    getServletContext();
    context.setNewServletContextListenerAllowed(false);

    Object instances[] = getApplicationLifecycleListeners();
    if (instances == null || instances.length == 0) {
        return ok;
    }

    ServletContextEvent event = new ServletContextEvent(getServletContext());
    ServletContextEvent tldEvent = null;
    if (noPluggabilityListeners.size() > 0) {
        noPluggabilityServletContext = new NoPluggabilityServletContext(getServletContext());
        tldEvent = new ServletContextEvent(noPluggabilityServletContext);
    }
    for (int i = 0; i < instances.length; i++) {
        if (!(instances[i] instanceof ServletContextListener))
            continue;
        ServletContextListener listener =
            (ServletContextListener) instances[i];
        try {
            fireContainerEvent("beforeContextInitialized", listener);
            if (noPluggabilityListeners.contains(listener)) {
                //关键代码行:此时执行的是ContextLoaderListener监听器的contextInitialized方法
                listener.contextInitialized(tldEvent);
            } else {
                //关键代码行:此时执行的是ContextLoaderListener监听器的contextInitialized方法
                listener.contextInitialized(event);
            }
            fireContainerEvent("afterContextInitialized", listener);
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            fireContainerEvent("afterContextInitialized", listener);
            getLogger().error
                (sm.getString("standardContext.listenerStart",
                              instances[i].getClass().getName()), t);
            ok = false;
        }
    }
    return ok;
}

第三步:从这里开始我们可以感觉到离创建Spring父容器越来越近了,实际上就是在initWebApplicationContext中完成了父容器对象的创建和初始化的工作。文章源自JAVA秀-https://www.javaxiu.com/19961.html

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

第四步:正式开始父容器对象的创建和初始化的工作文章源自JAVA秀-https://www.javaxiu.com/19961.html

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
        throw new IllegalStateException(
                "Cannot initialize context because there is already a root application context present - " +
                "check whether you have multiple ContextLoader* definitions in your web.xml!");
    }

    Log logger = LogFactory.getLog(ContextLoader.class);
    servletContext.log("Initializing Spring root WebApplicationContext");
    if (logger.isInfoEnabled()) {
        logger.info("Root WebApplicationContext: initialization started");
    }
    long startTime = System.currentTimeMillis();

    try {
        // Store context in local instance variable, to guarantee that
        // it is available on ServletContext shutdown.
        if (this.context == null) {
            //重要执行一:创建父容器对象,将servletContext当作参数传入进去,意味着我们的父容器对象会持有ServletContext对象。
            this.context = createWebApplicationContext(servletContext);
        }
        if (this.context instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
            if (!cwac.isActive()) {
                // The context has not yet been refreshed -> provide services such as
                // setting the parent context, setting the application context id, etc
                if (cwac.getParent() == null) {
                    // The context instance was injected without an explicit parent ->
                    // determine parent for root web application context, if any.
                    ApplicationContext parent = loadParentContext(servletContext);
                    cwac.setParent(parent);
                }
                //重要调用执行二:配置父容器对象信息,并刷新容器bean信息
                configureAndRefreshWebApplicationContext(cwac, 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);
        }

        if (logger.isDebugEnabled()) {
            logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
                    WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
        }
        if (logger.isInfoEnabled()) {
            long elapsedTime = System.currentTimeMillis() - startTime;
            logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
        }

        return this.context;
    }
    catch (RuntimeException ex) {
        logger.error("Context initialization failed", ex);
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
        throw ex;
    }
    catch (Error err) {
        logger.error("Context initialization failed", err);
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
        throw err;
    }
}

//创建Spring父容器对象
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
    Class<?> contextClass = determineContextClass(sc);
    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
        throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
                "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
    }
    //简单明了的使用BeanUtils工具类的instantiateClass方法创建一个applicationContext对象(即父容器对象)。
    return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}

//配置和刷新Spring父容器对象
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
    if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
        // The application context id is still set to its original default value
        // -> assign a more useful id based on available information
        String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
        if (idParam != null) {
            wac.setId(idParam);
        }
        else {
            // Generate default id...
            wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                    ObjectUtils.getDisplayString(sc.getContextPath()));
        }
    }

    //让父容器对象持有ServletContext对象,也就意味着父容器对象可以和ServletContext对象一样,就可以代表是我们的一个后台服务了。
    wac.setServletContext(sc);
    //将web.xml中配置的contextConfigLocation信息,设置到父容器对象中
    String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
    if (configLocationParam != null) {
        wac.setConfigLocation(configLocationParam);
    }

    // The wac environment's #initPropertySources will be called in any case when the context
    // is refreshed; do it eagerly here to ensure servlet property sources are in place for
    // use in any post-processing or initialization that occurs below prior to #refresh
    ConfigurableEnvironment env = wac.getEnvironment();
    if (env instanceof ConfigurableWebEnvironment) {
        ((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
    }

    //自定义父容器对象
    customizeContext(sc, wac);

    //刷新父容器对象
    wac.refresh();
}

3. Spring子容器启动过程

要启动子容器,必须在web.xml中配置一个关键内容:文章源自JAVA秀-https://www.javaxiu.com/19961.html

<servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!-- contextConfigLocation不是必须的, 如果不配置contextConfigLocation, springmvc的配置文件默认在:WEB-INF/servlet的name+"-servlet.xml" -->
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext-mvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

上面一小节,我们看到了Spring父容器的创建的工作,本小节我们开始看看子容器的启动过程。其实子容器的启动过程我们之前已经讲解过了,这里我们在简单的回顾一下:文章源自JAVA秀-https://www.javaxiu.com/19961.html

第一步:我们正式开始分析子容器的启动过程,因为web.xml中配置了上面的内容之后,tomcat容器在启动后台服务的过程中,会执行startInternal方法中的下面的代码(第一节有提到过):文章源自JAVA秀-https://www.javaxiu.com/19961.html

if (ok) {
        //web后台服务启动关键步骤三:1.如果当前的spring服务是单容器服务,那么这一行开始就是启动spring容器的开始;2.如果当前的spring服务是父子容器服务,那么这一行代码就是创建子容器的开始。
        if (!loadOnStartup(findChildren())){
            log.error(sm.getString("standardContext.servletFail"));
            ok = false;
        }
}

第二步:从loadOnStartup方法,到initServletBean的调用过程就不重复了,在springmvc篇-(六)SpringMVC的Spring容器是如何启动的文章中有提到,这里就不做多的讲解。我们从initServletBean方法开始,往下看看spring的子容器是如何被创建出来的。注意,后面的文字解释里面没有说Spring子容器,而是说Spring容器,实际上是一个意思。不管是单容器启动,还是父子容器方式启动,都会执行下面的代码。当我们配置Spring单容器启动的时候,下面就是创建一个spring容器;当我们配置Spring父子容器方式启动的时候,下面就是创建一个Spring子容器。文章源自JAVA秀-https://www.javaxiu.com/19961.html

//本方法也不直接启动spring容器,而是进一步将启动spring容器的工作委托给initWebApplicationContext方法。从这个方法命名,我们就知道离真正的创建一个applicationcontext对象越来越近了。
@Override
protected final void initServletBean() throws ServletException {
    getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
    if (this.logger.isInfoEnabled()) {
        this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
    }
    long startTime = System.currentTimeMillis();

    try {
        this.webApplicationContext = initWebApplicationContext();
        initFrameworkServlet();
    }
    catch (ServletException ex) {
        this.logger.error("Context initialization failed", ex);
        throw ex;
    }
    catch (RuntimeException ex) {
        this.logger.error("Context initialization failed", ex);
        throw ex;
    }

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

//本方法就是真正的开始我们的spring容器的启动的核心方法。
protected WebApplicationContext initWebApplicationContext() {

    //在此处就会完成applicationcontext对象的创建和初始化的工作
    WebApplicationContext rootContext =
            WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;

    if (this.webApplicationContext != null) {
        // A context instance was injected at construction time -> use it
        wac = this.webApplicationContext;
        if (wac instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
            if (!cwac.isActive()) {
                // The context has not yet been refreshed -> provide services such as
                // setting the parent context, setting the application context id, etc
                if (cwac.getParent() == null) {
                    // The context instance was injected without an explicit parent -> set
                    // the root application context (if any; may be null) as the parent
                    cwac.setParent(rootContext);
                }
                configureAndRefreshWebApplicationContext(cwac);
            }
        }
    }
    if (wac == null) {
        // No context instance was injected at construction time -> see if one
        // has been registered in the servlet context. If one exists, it is assumed
        // that the parent context (if any) has already been set and that the
        // user has performed any initialization such as setting the context id
        wac = findWebApplicationContext();
    }
    if (wac == null) {
        // No context instance is defined for this servlet -> create a local one
        // 从这里创建一个applicationcontext对象
        wac = createWebApplicationContext(rootContext);
    }

    if (!this.refreshEventReceived) {
        // Either the context is not a ConfigurableApplicationContext with refresh
        // support or the context injected at construction time had already been
        // refreshed -> trigger initial onRefresh manually here.
        onRefresh(wac);
    }

    if (this.publishContext) {
        // Publish the context as a servlet context attribute.
        String attrName = getServletContextAttributeName();
        getServletContext().setAttribute(attrName, wac);
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
                    "' as ServletContext attribute with name [" + attrName + "]");
        }
    }

    return wac;
}


//本方法就是真正的开始我们的spring容器的启动的核心方法。
protected WebApplicationContext initWebApplicationContext() {

    //在此处就会完成applicationcontext对象的创建和初始化的工作
    WebApplicationContext rootContext =
            WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;

    if (this.webApplicationContext != null) {
        // A context instance was injected at construction time -> use it
        wac = this.webApplicationContext;
        if (wac instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
            if (!cwac.isActive()) {
                // The context has not yet been refreshed -> provide services such as
                // setting the parent context, setting the application context id, etc
                if (cwac.getParent() == null) {
                    // The context instance was injected without an explicit parent -> set
                    // the root application context (if any; may be null) as the parent
                    cwac.setParent(rootContext);
                }
                configureAndRefreshWebApplicationContext(cwac);
            }
        }
    }
    if (wac == null) {
        // No context instance was injected at construction time -> see if one
        // has been registered in the servlet context. If one exists, it is assumed
        // that the parent context (if any) has already been set and that the
        // user has performed any initialization such as setting the context id
        wac = findWebApplicationContext();
    }
    if (wac == null) {
        // No context instance is defined for this servlet -> create a local one
        // 从这里创建一个applicationcontext对象
        wac = createWebApplicationContext(rootContext);
    }

    if (!this.refreshEventReceived) {
        // Either the context is not a ConfigurableApplicationContext with refresh
        // support or the context injected at construction time had already been
        // refreshed -> trigger initial onRefresh manually here.
        onRefresh(wac);
    }

    if (this.publishContext) {
        // Publish the context as a servlet context attribute.
        String attrName = getServletContextAttributeName();
        getServletContext().setAttribute(attrName, wac);
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
                    "' as ServletContext attribute with name [" + attrName + "]");
        }
    }

    return wac;
}

第三步:创建applicationcontext对象以及初始化applicationcontext对象文章源自JAVA秀-https://www.javaxiu.com/19961.html

protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
    return createWebApplicationContext((ApplicationContext) parent);
}

//本方法主要是创建spring容器对象也就是ConfigurableWebApplicationContext对象
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
    //获取当前需要创建的spring容器的实际类,也就是XmlWebApplicationContext类,这个就是真正实例化的spring容器的类。
    Class<?> contextClass = getContextClass();
    if (this.logger.isDebugEnabled()) {
        this.logger.debug("Servlet with name '" + getServletName() +
                "' will try to create custom WebApplicationContext context of class '" +
                contextClass.getName() + "'" + ", using parent context [" + parent + "]");
    }
    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");
    }

    //使用BeanUtils工具类的instantiateClass方法创建一个XmlWebApplicationContext实例,也就是spring容器。
    ConfigurableWebApplicationContext wac =
            (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

    //设置当前服务环境信息
    wac.setEnvironment(getEnvironment());
    //spring容器启动有两种方式:一种比较简单,就是单容器启动方式;一种稍微复杂,就是父子容器启动方式。父子容器的意思是会创建两个applicationcontext对象,但是这两个对象一起构成了一个整体的spring容器(形态就是子容器对象持有父容器对象,两个对象作为一个整体代表一个spring容器)。因为本章节讲解的时单容器启动,这里不多做讲解,这里的的parent父容器也就是为空。
    wac.setParent(parent);
    //此处就是在web.xml中servlet配置的contextConfigLocation属性配置的内容,也就是spring配置文件所在位置
    wac.setConfigLocation(getContextConfigLocation());

    //前面的几步只是初步的设置了ConfigurableWebApplicationContext实例的基本信息,本行代码调用将进一步的初始化ConfigurableWebApplicationContext实例
    configureAndRefreshWebApplicationContext(wac);

    return wac;
}

//本方法主要用于使得Spring容器对象持有有ServletContext对象,这样我们得spring容器也就具备了代表一个web后台服务得能力。
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
    if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
        // The application context id is still set to its original default value
        // -> assign a more useful id based on available information
        if (this.contextId != null) {
            wac.setId(this.contextId);
        }
        else {
            // Generate default id...
            wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                    ObjectUtils.getDisplayString(getServletContext().getContextPath()) + "/" + getServletName());
        }
    }

    //我们前面讲过一个web.xml文件最终被解析成为一个ServletContext对象,这个对象也就可以理解为一个web后台服务的实例对象。而我们的spring容器启动过程中,会将这个web后台服务实例对象添加到自己的属性中。也就是说基于spring开发的后台服务,我们可以认为spring容器是我们的后台服务实例对象,因为他更能代表这个后台服务。
    wac.setServletContext(getServletContext());
    wac.setServletConfig(getServletConfig());
    wac.setNamespace(getNamespace());
    wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

    // The wac environment's #initPropertySources will be called in any case when the context
    // is refreshed; do it eagerly here to ensure servlet property sources are in place for
    // use in any post-processing or initialization that occurs below prior to #refresh
    ConfigurableEnvironment env = wac.getEnvironment();
    if (env instanceof ConfigurableWebEnvironment) {
        ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
    }

    postProcessWebApplicationContext(wac);
    applyInitializers(wac);

    //到这里就回到我们熟悉的刷新spring容器环境的方法了。
    wac.refresh();
}

通过上面的代码的跟踪,我们基本上也就了解了Spring的父子容器的整体过程了。文章源自JAVA秀-https://www.javaxiu.com/19961.html

4. SpringMVC系列文章的总结

  1. 在没有Spring的时候,我们使用原生的Servlet做业务开发,我们在doGet,doPost方法里面去做业务开发,这两个方法和我们在Spring的Controller里面的方法很像。在这一步,我们的理解是一个请求来服务器后,服务器会被Tomcat容器接收到,tomcat接收到请求之后,会层层嵌套调用,直到调用我们的业务Servlet的doGet和doPost方法进行业务处理。
  2. 当我们使用SpringMVC做后台服务开发的时候,我们发现Tomcat会将层层调用,直到调用我们的DispatcherServlet类的doPost方法进行业务处理。后面我们不再使用层层调用这种说法,我们用转发或者转派更加好理解。而DispatcherServlet类不会直接处理这个请求,而是转交给一个叫做doDispatch的核心模板方法进行处理。这个方法主要做了一个工作,就是根据用户的请求,找到能处理这个请求的方法集合(过滤器链和业务处理方法),并依次进行处理。文章源自JAVA秀-https://www.javaxiu.com/19961.html

  3. 我们对doDispatch的核心模板展开了进行了仔细的分析,过滤器链是如果构建的,执行顺序是怎么样的,业务方法是怎么拿到的等等。文章源自JAVA秀-https://www.javaxiu.com/19961.html

  4. 经过前面的三个步骤,我们基本上知道了SpringMVC的工作原理,但是又引入了一个更深的问题,就是我们的war包是如何被tomcat启动起来的呢?而且基于Spring开发的war包是怎么创建一个spring容器的呢?为了解决这个问题,我们又深入的介绍了一下web.xml和ServletContext这两个概念。web.xml就是一个还未运行的后台服务实例核心对象的前身,也就是ServletContext对象的前身,它记录和承载了一个web后台服务的核心信息。当我们的web容器启动我们的web后台应用的时候,会把web.xml文件读取解析成为一个ServletContext对象,这个对象也就可以看成是一个运行中的后台服务实例。文章源自JAVA秀-https://www.javaxiu.com/19961.html

  5. 我们对web.xml文件和ServletContext概念深入了解了之后,我们扒开了tomcat的源码,我们知道了StandardContext类的startInternal方法,就是启动我们的web后台服务的核心方法,这个方法里面进行web.xml的解析工作,一步一步的将web.xml文件解析成为一个ServletContext对象,也就是一个web后台服务核心对象。文章源自JAVA秀-https://www.javaxiu.com/19961.html

  6. 我们知道了web后台服务是如何启动的过程了,我们再startInternal方法里面进一步查找,我们发现了原来在loadOnStartup方法里面层层委托,直到委托给FrameworkServlet类的initWebApplicationContext方法,真正开始了我们spring容器的启动的工作。但是在分析代码的过程中,又引入一个新的问题,就是原来springmvc的使用还有两种方式,一种是单容器的方式使用,一种是父子容器的方式使用。文章源自JAVA秀-https://www.javaxiu.com/19961.html

  7. 前面说了父子容器的使用方式,我们简单的介绍了如何进行父子容器的配置和使用,也简单的介绍了父子容器的原理和一些注意的事项:在使用切面和事务的时候,需要特别注意,否则会出现不生效的情况。文章源自JAVA秀-https://www.javaxiu.com/19961.html

  8. 最后一章节我们跟着源码走了一遍,我们发现启动一个基于Spring开发的web后台服务,最复杂的情况下,我们需要做三次关键的初始化的工作,这三个初始化的调用的开始的地方在startInternal方法里面。第一个是将web.xml解析成为一个ServletContext对象,而这个过程我们可以成为启动web后台服务;第二个是启动Spring父容器的工作,这个是通过我们在web.xml中配置的ContextLoaderListener来最终完成的;第三个是启动Spring子容器的工作,在这个过程中会使Spring子容器持有Spring父容器对象,这二者一起构成一个Spring容器进行工作。文章源自JAVA秀-https://www.javaxiu.com/19961.html

到这里本系列文章的介绍基本上也就结束了,这里读者可能会有很多的疑问,读者可以自行进行进一步的挖掘,在挖掘源码的过程也是在一步一步的抽象自己思维的过程,一定会有很多的收获。文章源自JAVA秀-https://www.javaxiu.com/19961.html

springmvc篇-(八)SpringMVC父子容器启动方式的源码解读 文章源自JAVA秀-https://www.javaxiu.com/19961.html

更多知识请关注公众号文章源自JAVA秀-https://www.javaxiu.com/19961.html

继续阅读
速蛙云 - 极致体验,强烈推荐!!!购买套餐就免费送各大视频网站会员!快速稳定、独家福利社、流媒体稳定解锁!速度快,全球上网、视频、游戏加速、独立IP均支持!基础套餐性价比很高!这里不多说,我一直正在使用,推荐购买:https://www.javaxiu.com/59919.html
weinxin
资源分享QQ群
本站是JAVA秀团队的技术分享社区, 会经常分享资源和教程; 分享的时代, 请别再沉默!
沙海
匿名

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定