Spring MVC Startup Process Analysis

This is the first post in the Spring MVC series of blogs, which will be summarized in a post.

Spring MVC is the most frequently used part of the Spring family framework.Whether it's a Spring Boot or a traditional Spring project, the Spring MVC section is used for any Web project.Therefore, programmers must be familiar with the MVC section.This blog provides a brief analysis of the Spring MVC startup process to help us better understand this framework.

Why write this blog

Spring's MVC framework has been around for a long time. There are many blogs that introduce this section online, and many of them are definitely better than I have written. Why should I write this blog?On the one hand, I think blogging is a record of my learning process. On the other hand, the process of blogging can enhance my understanding of related technologies and facilitate my own review and summary in the future.

Introduction to Spring MVC

What is Spring MVC

To answer this question, let's start with MVC.MVC is a design pattern that suggests that a request be processed by M (Module), V (View), and C (controller).The request passes through the controller, the controller calls other service layers to get the Module, and finally renders the Module data as an attempt (View) to return to the client.Spring MVC is a component of the Spring Ecosphere, a WEB MVC framework that follows the MVC design pattern.This framework can be seamlessly integrated with Spring, which is easy to start with and easy to expand.

What problems to solve

Usually we divide a J2EE project into WEB layer, business logic layer and DAO layer.Spring MVC solves the encoding problem of the WEB layer.As a framework, Spring MVC abstracts a lot of common code, simplifies the encoding of WEB layer, and supports a variety of template technologies.We don't need to write a Servlet for each controller, requesting that the JSP page be returned to the foreground.

Advantages and disadvantages

Struts2 and Spring MVC are the most popular MVC frameworks.Between the two Contrast:

  • The biggest difference is that Struts2 is completely disconnected from the Servlet container, while SpringMVC is based on the Servlet container.
  • The core controller of Spring MVC is Servlet, while Struts2 is Filter;
  • Spring MVC defaults to a single column for each Controller, while Struts2 initializes an Action for each request.
  • Spring MVC configuration is simpler, while Struts2 configuration is more XML-based.

Overall, Spring MVC is simpler, less expensive to learn, and seamlessly integrated with Spring.In the enterprise is also getting more and more applications.So personal comparison suggests using Spring MVC in your project.

Start process analysis

PS: The analysis in this article is based on the traditional Tomcat project analysis, which is the basis.The startup process in the now popular Spring Boot project is followed by an article analysis.Actually, the principle is similar...

To analyze the startup process of Spring MVC, start with its startup configuration.A ContextLoaderListener and a Dispatcher Servlet are typically configured in Tomcat's Web.xml.In fact, ContextLoaderListener can be mismatched, so Spring manages all bean s in a context container initialized by Dispatcher Servlet.Here's a general configuration to illustrate the Spring MVC startup process.(PS: Spring Boot startup process is no longer using Web.xml)

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >


<!--One web Application corresponds to one ServletContext Instance, which is after the application deployment is started. servlet Containers are created for applications.
    ServletContext Instances contain all servlet Shared resource information.By providing a set of methods to servlet Use, for
    //Communicate with the servlet container, such as the MIME type for getting files, distributing requests, logging, and so on.
    http://www.cnblogs.com/nantang/p/5919323.html -->
<web-app>
    <display-name>Archetype Created Web Application</display-name>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:beans.spring.xml</param-value>
    </context-param>
    <context-param>
        <param-name>webAppRootKey</param-name>
        <param-value>project.root.path</param-value>
    </context-param>

    <!-- Put the absolute path of the project in the system variable -->
    <listener>
        <listener-class>org.springframework.web.util.WebAppRootListener</listener-class>
    </listener>
    <!--Initialization Spring IOC Container and place it in servletContext Among the attributes of-->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener> 
     
    <!-- To configure SPRINGMVC-->
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc.spring.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <!-- Writing is not recommended here/* -->
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>

Tomcat starts loading the istener, Filter, and Servlet configured in web.xml in turn.So, depending on the configuration above, the ContextLoader Listener is loaded first, which inherits the ContextLoader to initialize the Spring root context and place it in the ServletContext.This is the entry to analyze the code below.

The Tomcat container first calls the contextInitialized() method of the ContextLoadListener, which in turn calls the initWebApplicationContext() method of the parent ContextLoader.Here is the source code for this method.

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
//Error if Spring container already exists in 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) {
            //The webApplicationContext is created here, and the XmlWebApplicationContext is created by default
            //If you want to customize the implementation class, you can configure the contextClass parameter in the <context-param>of the web.xml
            //The ontext is not configured at this time, which is equivalent to an "empty shell"
            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);
                }
                //Read Spring's configuration file to initialize the parent context
                configureAndRefreshWebApplicationContext(cwac, servletContext);
            }
        }
        //Save the root context in the 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;
    }
}

At this point, Spring's parent (root) context has been initialized and already exists in the ServletContext.

The following starts the initialization process of the analysis context.This process is done through the core Servlets of Spring MVC, so it is also necessary to talk about the life cycle of Servlets.Ask to see if the Servlet was created, instantiate it, call the init method, and then call the service method.When we configure the Dispatcher Servlet, we set it to create an instance at startup, so Tomcat creates a Spring subcontext at startup.

The following is the inheritance structure of Dispatcher Servlet.

GenericServlet (javax.servlet)
    HttpServlet (javax.servlet.http)
        HttpServletBean (org.springframework.web.servlet)
            FrameworkServlet (org.springframework.web.servlet)
                DispatcherServlet (org.springframework.web.servlet)

Dispatcher Servlet inherits the FrameworkServlet,FrameworkServlet inherits the HttpServletBean, HttpServletBean inherits the HttpServlet and overrides the init method, so the entry to create the context is in this init method.

//The init of HttpServletBean is the entry method
@Override
public final void init() throws ServletException {
    if (logger.isDebugEnabled()) {
        logger.debug("Initializing servlet '" + getServletName() + "'");
    }
    // Set bean properties from init parameters.
    try {
        //Read init-param of Servlet configuration to create Dispatcher Servlet instance
        PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
        BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
        ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
        bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
        initBeanWrapper(bw);
        bw.setPropertyValues(pvs, true);
    }
    catch (BeansException ex) {
        if (logger.isErrorEnabled()) {
            logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
        }
        throw ex;
    }
    // Nothing is done in this method of HttpServletBean, and the initServletBean () method of the subclass FrameWorkServlet is overridden
    // This class, so subsequent work will be done in this method.
    initServletBean();
    if (logger.isDebugEnabled()) {
        logger.debug("Servlet '" + getServletName() + "' configured successfully");
    }
}

The following is the initServletBean() method for the FrameWorkServlet class

//FrameWorkServlet.initServletBean()
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 {
        //Here's the focus, initializing the child Spring context
        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");
    }
}

The following is the specific code for the initWebApplicationContext() method

protected WebApplicationContext initWebApplicationContext() {
    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
        // This side is the focus, creating the Spring subcontext, and setting its parent context
        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) {
        // Save Spring context into ServletContext
        String attrName = getServletContextAttributeName();
        getServletContext().setAttribute(attrName, wac);
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
                    "' as ServletContext attribute with name [" + attrName + "]");
        }
    }

    return wac;
}

Finally, look at the onRefresh() method in Dispatcher Servlet, which initializes a number of strategies:

protected void initStrategies(ApplicationContext context) {
    initMultipartResolver(context);
    initLocaleResolver(context);
    initThemeResolver(context);
    initHandlerMappings(context);
    initHandlerAdapters(context);
    initHandlerExceptionResolvers(context);
    initRequestToViewNameTranslator(context);
    initViewResolvers(context);
    initFlashMapManager(context);
}

So far, the SpringMVC startup process has ended.Here's a summary of SpringMVC initialization (not very detailed)

  • HttpServletBean mainly does some initialization work, setting the parameter book we configure in web.xml into the Servlet;
  • The main purpose of the FrameworkServlet is to initialize the Spring context, set its parent context, and associate it with the ServletContext;
  • Dispatcher Servlet: An implementation class that initializes various functions.Examples include exception handling, view handling, request mapping processing, and so on.

A brief summary

The traditional Spring MVC project startup process is as follows:

  • If the org.springframework.web.context.ContextLoaderListener is configured in web.xml, Tomcat will load the parent container and place it in the ServletContext at startup.
  • Dispatcher Servlet is then loaded, because Dispatcher Servlet is essentially a Servlet, its init method is executed first.This init method is implemented in the class HttpServletBean. Its main work is to do some initialization work, set the parameter book we configure in web.xml into the Servlet, and then trigger the initServletBean() method of the FrameworkServlet.
  • The main purpose of the Framework Servlet is to initialize the Spring context, set its parent context, and put it into the ServletContext;
  • The FrameworkServlet triggers the onRefresh() method of Dispatcher Servlet during the call to initServletBean(), which initializes the various functional components of Spring MVC.Examples include exception handlers, view handlers, request mapping processing, and so on.

Blog Reference

Keywords: Java Spring xml Tomcat encoding

Added by snakebit on Thu, 19 Mar 2020 06:09:58 +0200