Refined Spring MVC source code analysis - initialization of WebApplicationContext container

This series of documents is summarized in the process of learning the source code of Spring MVC. It may not be very friendly to readers. Please combine my source code comments Spring MVC source code analysis GitHub address Read

Spring version: 5.2.4 RELEASE

With the popularity of Spring Boot and Spring Cloud in many large and medium-sized enterprises, you may have forgotten the classic Servlet + Spring MVC combination. Do you still remember the web XML configuration file. Before starting this article, please put aside Spring Boot and go back to the past. Let's see how Servlet integrates with Spring MVC and how to initialize Spring container. Before reading this article, it's better to have some knowledge of Servlet and Spring IOC container, which is easier to understand

summary

Before starting to look at the specific source code implementation, let's take a look at the "strange" web XML file, you can view another of my articles MyBatis user manual The integration of the web services involved in the Spring section in the document XML file, part of which is as follows:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">
    <display-name>Archetype Created Web Application</display-name>

    <!-- [1] Spring to configure -->
    <!-- In container( Tomcat,Jetty)When starting, it will be ContextLoaderListener Listening,
         To call its contextInitialized() Method, initialization Root WebApplicationContext container -->
    <!-- statement Spring Web Container listener -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    
    <!-- Spring and MyBatis Configuration file for -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-mybatis.xml</param-value>
    </context-param>

    <!-- [2] Spring MVC to configure -->
    <!-- 1.SpringMVC Configure front controller( SpringMVC (entrance to)
         DispatcherServlet It's a Servlet,Therefore, multiple can be configured DispatcherServlet -->
    <servlet>
        <!-- stay DispatcherServlet During the initialization of, the framework will web Applied WEB-INF Under the folder,
             Looking for a file named [servlet-name]-servlet.xml The configuration file defined in the generation file Bean. -->
        <servlet-name>SpringMVC</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- Configure the configuration file that needs to be loaded -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-mvc.xml</param-value>
        </init-param>
        <!-- Program runtime from web.xml At the beginning, the loading sequence is: context-param -> Listener -> Filter -> Structs -> Servlet
             set up web.xml The order in which files are loaded at startup(1 Represents that the container is initialized first when it starts Servlet,Let this Servlet along with Servlet Start with container)
             load-on-startup I mean this Servlet Is currently web The application is created when it is loaded, not when it is first requested  -->
        <load-on-startup>1</load-on-startup>
        <async-supported>true</async-supported>
    </servlet>
    <servlet-mapping>
        <!-- this Servlet My name is SpringMVC,There can be more than one DispatcherServlet,It is distinguished by name
             every last DispatcherServlet Have their own WebApplicationContext The object is saved in context at the same time ServletContext neutralization Request Object
             ApplicationContext(Spring Container) yes Spring Core of
             Context We usually interpret it as context, Spring hold Bean Put it in this container and you can use it when you need it getBean Method take out-->
        <servlet-name>SpringMVC</servlet-name>
        <!-- Servlet Interception matching rule, optional configuration:*.do,*.action,*.html,/,/xxx/* ,not allow:/* -->
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

[1] Office, configured with org springframework. Web. context. ContextLoaderListener object, which implements the javax. Net of Servlet servlet. The servletcontextlistener interface can monitor the life cycle of the ServletContext object, that is, the life cycle of the Web application. When the Servlet container is started or destroyed, the corresponding ServletContextEvent event event will be triggered. If the ContextLoaderListener monitors the start event, it will initialize a Root Spring WebApplicationContext container. If it listens to the destruction event, it will destroy the container

[2] Office, configured with org springframework. web. Servlet. Dispatcherservlet object, which inherits javax Servlet. http. Httpservlet abstract class, that is, a Servlet. The core class of Spring MVC, which handles requests, initializes a Spring WebApplicationContext container belonging to it, and the container takes the Root container at [1] as the parent container

  • When [container 1] is created, why does [Root 2] need to be created? Because multiple [2] can be configured. Of course, in the actual scenario, multiple [2] will not be configured 😈
  • To sum up again, [1] and [2] will create their corresponding Spring WebApplicationContext containers respectively, and they are the relationship between parent and child containers

Root WebApplicationContext container

In the overview of the web XML, we have seen that the initialization of Root WebApplicationContext container is realized through ContextLoaderListener. When the Servlet container is started, for example, after Tomcat and Jetty are started, it will be listened by the ContextLoaderListener, which will call the contextInitialized(ServletContextEvent event) method to initialize the Root WebApplicationContext container

The class diagram of ContextLoaderListener is as follows:

ContextLoaderListener

org. springframework. web. context. The contextloaderlistener class implements javax Servlet. The servletcontextlistener interface inherits the ContextLoader class to initialize and destroy the WebApplicationContext container respectively when the Servlet container is started and closed. The code is as follows:

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {

	public ContextLoaderListener() {
	}

    /**
     * As of Spring 3.1, supports injecting the root web application context
     */
	public ContextLoaderListener(WebApplicationContext context) {
		super(context);
	}


	/**
	 * Initialize the root web application context.
	 */
	@Override
	public void contextInitialized(ServletContextEvent event) {
        // <1> Initialize Root WebApplicationContext
		initWebApplicationContext(event.getServletContext());
	}


	/**
	 * Close the root web application context.
	 */
	@Override
	public void contextDestroyed(ServletContextEvent event) {
        // <2> Destroy Root WebApplicationContext
		closeWebApplicationContext(event.getServletContext());
		ContextCleanupListener.cleanupAttributes(event.getServletContext());
	}

}
  1. After listening to the Servlet container startup event, call the initWebApplicationContext(ServletContext servletContext) method of the parent ContextLoader to initialize the WebApplicationContext container
  2. After listening to the Servlet destruction start event, call the closeWebApplicationContext(ServletContext servletContext) method of the parent ContextLoader to destroy the WebApplicationContext container

ContextLoader

org. springframework. web. context. The contextloader class is a class that truly implements the logic of initializing and destroying the WebApplicationContext container

Static code block
public class ContextLoader {

	/**
	 * Name of the class path resource (relative to the ContextLoader class)
	 * that defines ContextLoader's default strategy names.
	 */
	private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";

    /**
     * Default configuration Properties object
     */
	private static final Properties defaultStrategies;

	static {
		// Load default strategy implementations from properties file.
		// This is currently strictly internal and not meant to be customized by application developers.
		try {
			ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
			defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
		}
		catch (IOException ex) {
			throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
		}
	}
}

From contextloader Properties, read the default configuration properties object. In fact, just like load default strategy implementations from properties file This is currently strictly internal and not meant to be customized by application developers. Note that this is a configuration that application developers do not need to care about, but defined by the Spring framework itself

Open the file and have a look. The code is as follows:

# Default WebApplicationContext implementation class for ContextLoader.
# Used as fallback when no explicit context implementation has been specified as context-param.
# Not meant to be customized by application developers.

org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext

What does that mean? If we do not specify WebApplicationContext in the < context param / > tag, the XmlWebApplicationContext class will be used by default. Generally, we will not actively specify it in the process of using Spring

Construction method
public class ContextLoader {
    
    /**
	 * Name of servlet context parameter (i.e., {@value}) that can specify the
	 * config location for the root context, falling back to the implementation's default otherwise.
	 * @see org.springframework.web.context.support.XmlWebApplicationContext#DEFAULT_CONFIG_LOCATION
	 */
	public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";
    
	/** Map from (thread context) ClassLoader to corresponding 'current' WebApplicationContext. */
	private static final Map<ClassLoader, WebApplicationContext> currentContextPerThread = new ConcurrentHashMap<>(1);

	/** The 'current' WebApplicationContext, if the ContextLoader class is deployed in the web app ClassLoader itself. */
	@Nullable
	private static volatile WebApplicationContext currentContext;


	/** The root WebApplicationContext instance that this loader manages. */
	@Nullable
	private WebApplicationContext context;

	/**
	 * Create a new {@code ContextLoader} that will create a web application context
	 * based on the "contextClass" and "contextConfigLocation" servlet context-params.
	 * See class-level documentation for details on default values for each.
	 */
	public ContextLoader() {
	}

	/**
	 * Create a new {@code ContextLoader} with the given application context.
     * This constructor is useful in Servlet 3.0+ environments where instance-based
	 * registration of listeners is possible through the {@link ServletContext#addListener} API.
	 */
	public ContextLoader(WebApplicationContext context) {
		this.context = context;
	}
    
    // ...  Omit other related configuration attributes
}
  • In the overview of the web In the XML file, you can see that the defined contextConfigLocation parameter is spring mybatis XML configuration file path
  • currentContextPerThread: used to save the mapping relationship between the current ClassLoader class loader and the WebApplicationContext object
  • currentContext: if the class loader of the current thread is the class loader of the ContextLoader class, this attribute is used to save the WebApplicationContext object
  • context: WebApplicationContext instance object

The class loader involves the "two parent delegation mechanism" of the JVM. In MyBatis source code analysis - basic support layer There is a simple description, you can refer to it

initWebApplicationContext

initWebApplicationContext(ServletContext servletContext) method to initialize the WebApplicationContext object. The code is as follows:

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    // <1> If root already exists_ WEB_ APPLICATION_ CONTEXT_ If the WebApplicationContext object corresponding to attribute throws an IllegalStateException.
    // For example, on the web There are multiple contextloaders in XML
    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!");
    }

    // <2> Print log
    servletContext.log("Initializing Spring root WebApplicationContext");
    Log logger = LogFactory.getLog(ContextLoader.class);
    if (logger.isInfoEnabled()) {
        logger.info("Root WebApplicationContext: initialization started");
    }
    // Record start time
    long startTime = System.currentTimeMillis();

    try {
        // Store context in local instance variable, to guarantee that
        // it is available on ServletContext shutdown.
        if (this.context == null) {
            // <3> Initialize the context, that is, create the context object
            this.context = createWebApplicationContext(servletContext);
        }
        // <4> If it is a subclass of ConfigurableWebApplicationContext, configure and refresh it if it is not refreshed
        if (this.context instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
            if (!cwac.isActive()) { // < 4.1 > not refreshed (active)
                // 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) { // < 4.2 > if there is no parent container, load and set it.
                    // 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);
                }
                // < 4.3 > configure the context object and refresh it
                configureAndRefreshWebApplicationContext(cwac, servletContext);
            }
        }
        // <5> Record in servletContext
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

        // <6> Record to currentContext or currentContextPerThread
        ClassLoader ccl = Thread.currentThread().getContextClassLoader();
        if (ccl == ContextLoader.class.getClassLoader()) {
            currentContext = this.context;
        }
        else if (ccl != null) {
            currentContextPerThread.put(ccl, this.context);
        }
        if (logger.isInfoEnabled()) {
            long elapsedTime = System.currentTimeMillis() - startTime;
            logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
        }

        // <7> Return to context
        return this.context;
    }
    catch (RuntimeException | Error ex) {
        logger.error("Context initialization failed", ex);
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
        throw ex;
    }
}
  1. If the Root WebApplicationContext object already exists in the ServletContext (the context of the Servlet), an exception will be thrown because the object can no longer be initialized

  2. Print the log. When starting the SSM project, will you see the log "Initializing Spring root WebApplicationContext"

  3. If the context is empty, call the createWebApplicationContext(ServletContext sc) method to initialize a Root WebApplicationContext object. The method is as follows:

    protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
        // <1> Get the class of context (by default, it is read from the ContextLoader.properties configuration file, which is XmlWebApplicationContext)
        Class<?> contextClass = determineContextClass(sc);
        // <2> Judge whether the class of context conforms to the type of ConfigurableWebApplicationContext
        if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
            throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
                    "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
        }
        // <3> The object of the class that creates the context
        return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
    }
    
  4. If it is a subclass of ConfigurableWebApplicationContext and is not refreshed, configure and refresh it

    1. If it is not refreshed (activated), by default, it meets this condition, so it will be executed next
    2. If there is no parent container, load and set it. By default, the loadParentContext(ServletContext servletContext) method returns an empty object, that is, there is no parent container
    3. Call the configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) method to configure the context object and refresh it
  5. Save the context object in ServletContext

  6. Set the context object to the currentContext or currentContextPerThread object. The difference is whether the class loaders are the same. The specific purpose is unclear 😈

  7. Returns the initialized context object

configureAndRefreshWebApplicationContext

configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) method to configure the ConfigurableWebApplicationContext object and refresh it. The method is as follows:

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
    // <1> If wac uses the default number, reset the id attribute
    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
        // Case 1: use contextId attribute
        String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
        if (idParam != null) {
            wac.setId(idParam);
        }
        else { // Case 2: automatic generation
            // Generate default id...
            wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                    ObjectUtils.getDisplayString(sc.getContextPath()));
        }
    }

    // <2> Set the ServletContext property of context
    wac.setServletContext(sc);
    // <3> Set the profile address of context
    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);
    }

    // <4> Customize the context
	customizeContext(sc, wac);
	// <5> Refresh the context and perform initialization
	wac.refresh();
}
  1. If wac uses the default number, reset the id attribute. By default, we will not set the number of wac, so we will execute it. In fact, the generation rules of id are also divided into two cases: using contextId to be configured by the user in the < context param / > tag and automatic generation. 😈 By default, the second case will be taken

  2. Set the ServletContext property of wac

  3. [key] set the profile address of context. For example, web. Com in our overview What you see in XML

    <!-- Spring and MyBatis Configuration file for -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-mybatis.xml</param-value>
    </context-param>
    
  4. Customize wac and ignore it temporarily

  5. [key] trigger the refresh event of wac and execute initialization. Here, we will initialize some Spring containers, involving Spring IOC related content

closeWebApplicationContext

closeWebApplicationContext(ServletContext servletContext) method to close the WebApplicationContext container object. The method is as follows:

public void closeWebApplicationContext(ServletContext servletContext) {
    servletContext.log("Closing Spring root WebApplicationContext");
    try {
        // Close context
        if (this.context instanceof ConfigurableWebApplicationContext) {
            ((ConfigurableWebApplicationContext) this.context).close();
        }
    }
    finally {
        // Remove currentContext or currentContextPerThread
        ClassLoader ccl = Thread.currentThread().getContextClassLoader();
        if (ccl == ContextLoader.class.getClassLoader()) {
            currentContext = null;
        }
        else if (ccl != null) {
            currentContextPerThread.remove(ccl);
        }
        // Remove from ServletContext
        servletContext.removeAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
    }
}

Called when the Servlet container is destroyed, it is used to close the WebApplicationContext object and clean up the related resource objects

Servlet WebApplicationContext container

In the overview of the web XML, we have seen that in addition to initializing a Root WebApplicationContext container, a DispatcherServlet object will be injected into the ServletContext context of the Servlet container, and a Servlet WebApplicationContext container will also be initialized during the process of initializing the object

The class diagram of dispatcher servlet is as follows:

You can see that DispatcherServlet is a Servlet object. When injected into the Servlet container, its init method will be called to complete some initialization work

  • HttpServletBean is responsible for setting ServletConfig to the current Servlet object. Its Java doc:

    /**
     * Simple extension of {@link javax.servlet.http.HttpServlet} which treats
     * its config parameters ({@code init-param} entries within the
     * {@code servlet} tag in {@code web.xml}) as bean properties.
     */
    
  • FrameworkServlet is responsible for initializing the Spring Servlet WebApplicationContext container. At the same time, this class overrides doGet, doPost and other methods, and delegates all types of requests to the doService method. doService is an abstract method that needs to be implemented by subclasses. Its Java doc:

    /**
     * Base servlet for Spring's web framework. Provides integration with
     * a Spring application context, in a JavaBean-based overall solution.
     */
    
  • DispatcherServlet is responsible for initializing various components of Spring MVC, processing client requests, and coordinating the work of various components. Its Java doc:

    /**
     * Central dispatcher for HTTP request handlers/controllers, e.g. for web UI controllers
     * or HTTP-based remote service exporters. Dispatches to registered handlers for processing
     * a web request, providing convenient mapping and exception handling facilities.
     */
    

The Servlet implementation class of each layer is responsible for executing the corresponding logic and is well organized. Let's look at it one by one

HttpServletBean

org.springframework.web.servlet.HttpServletBean abstract class, which implements EnvironmentCapable and EnvironmentAware interfaces, inherits HttpServlet abstract class and is responsible for integrating ServletConfig into Spring

Construction method
public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware {

	@Nullable
	private ConfigurableEnvironment environment;

	/**
	 * Check whether the {@ properties} property in the configlink configuration will have the corresponding property
	 * The default is empty
	 */
	private final Set<String> requiredProperties = new HashSet<>(4);

	protected final void addRequiredProperty(String property) {
		this.requiredProperties.add(property);
	}

    /**
     * The EnvironmentAware interface is implemented and the Environment object is automatically injected
     */
	@Override
	public void setEnvironment(Environment environment) {
		Assert.isInstanceOf(ConfigurableEnvironment.class, environment, "ConfigurableEnvironment required");
		this.environment = (ConfigurableEnvironment) environment;
	}

    /**
     * Implements the EnvironmentAware interface and returns the Environment object
     */
	@Override
	public ConfigurableEnvironment getEnvironment() {
		if (this.environment == null) {
            // If Environment is empty, a StandardServletEnvironment object is created
			this.environment = createEnvironment();
		}
		return this.environment;
	}

	/**
	 * Create and return a new {@link StandardServletEnvironment}.
	 */
	protected ConfigurableEnvironment createEnvironment() {
		return new StandardServletEnvironment();
	}
}

As for the xxxAware interface, when Spring initializes the Bean, it will call its setXxx method to inject an object, which will not be analyzed in this paper

init method

init() method, which overrides the method in GenericServlet and is responsible for setting ServletConfig to the current Servlet object. The method is as follows:

@Override
public final void init() throws ServletException {
    // Set bean properties from init parameters.
    // <1> Parse the < init param / > tag and encapsulate it in PropertyValues pvs
    PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
    if (!pvs.isEmpty()) {
        try {
            // < 2.1 > convert the current Servlet object into a BeanWrapper object. Thus, pvs can be injected into the BeanWrapper object in the way of Spring
            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
            ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
            // < 2.2 > register the user-defined Attribute Editor. Once the Resource type attribute is encountered, it will be resolved using the ResourceEditor
            bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
            // < 2.3 > empty implementation, which is left to be covered by subclasses. At present, there is no subclass implementation
            initBeanWrapper(bw);
            // < 2.4 > inject pvs into the BeanWrapper object in the way of Spring
            bw.setPropertyValues(pvs, true);
        }
        catch (BeansException ex) {
            if (logger.isErrorEnabled()) {
                logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
            }
            throw ex;
        }
    }
    // Let subclasses do whatever initialization they like.
    // Let subclasses implement it. Check the FrameworkServlet#initServletBean() method
    initServletBean();
}
  1. Parse the < init param / > tag of Servlet configuration and encapsulate it into PropertyValues pvs object. Among them, ServletConfigPropertyValues is the private static class of HttpServletBean, inheriting MutablePropertyValues class and the encapsulated implementation class of ServletConfig. The code of this class is as follows:

    private static class ServletConfigPropertyValues extends MutablePropertyValues {
        public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties) throws ServletException {
            // Gets the collection of missing attributes
            Set<String> missingProps = (!CollectionUtils.isEmpty(requiredProperties) ? new HashSet<>(requiredProperties) : null);
    
            // <1> Traverse the initialization parameter set of ServletConfig, add it to ServletConfigPropertyValues, and remove it from missingProps
            Enumeration<String> paramNames = config.getInitParameterNames();
            while (paramNames.hasMoreElements()) {
                String property = paramNames.nextElement();
                Object value = config.getInitParameter(property);
                // Add to ServletConfigPropertyValues
                addPropertyValue(new PropertyValue(property, value));
                // Remove from missingProps
                if (missingProps != null) {
                    missingProps.remove(property);
                }
            }
            // Fail if we are still missing properties.
            if (!CollectionUtils.isEmpty(missingProps)) {
                throw new ServletException("...");
            }
        }
    }
    

    In its construction method, you can see that some configuration items defined by the < init param / > tag are parsed into PropertyValue objects, such as the web. Net object outlined above The configuration in XML is as follows:

    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-mvc.xml</param-value>
    </init-param>
    
  2. If there are < init param / > initialization parameters

    1. Convert the current Servlet object into a BeanWrapper object. Thus, pvs can be injected into the BeanWrapper object in the way of Spring. In short, BeanWrapper is a tool provided by Spring to manipulate Java Bean properties. It can be used to directly modify the properties of an object
    2. Once the attribute of Resource editor is resolved, the attribute of Resource editor will be registered
    3. Call the initBeanWrapper(BeanWrapper bw) method to initialize the current Servlet object, which is empty and left to be covered by subclasses. It seems that there is no subclass implementation at present
    4. Traverse the attribute value in the pvs and inject it into the BeanWrapper object, that is, set it into the current Servlet object. For example, the contextConfigLocation attribute in the FrameworkServlet will be set to the above classpath: Spring MVC XML value
  3. [key] call the initServletBean() method, which is empty and implemented by subclasses. Complete the custom initialization logic. Check the FrameworkServlet#initServletBean() method

FrameworkServlet

org. springframework. web. servlet. The frameworkservlet abstract class implements the ApplicationContextAware interface, inherits the HttpServletBean abstract class, and is responsible for initializing the Spring Servlet WebApplicationContext container

Construction method
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
    // ...  Omit some attributes
    
    /** Default context class for FrameworkServlet. */
	public static final Class<?> DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;

	/** WebApplicationContext implementation class to create. */
	private Class<?> contextClass = DEFAULT_CONTEXT_CLASS;

	/** Explicit context config location. Address of the profile */
	@Nullable
	private String contextConfigLocation;

	/** Should we publish the context as a ServletContext attribute?. */
	private boolean publishContext = true;

	/** Should we publish a ServletRequestHandledEvent at the end of each request?. */
	private boolean publishEvents = true;

	/** WebApplicationContext for this servlet. */
	@Nullable
	private WebApplicationContext webApplicationContext;

	/** Whether the tag is a WebApplicationContext injected through {@ link #setApplicationContext} */
	private boolean webApplicationContextInjected = false;

	/** Mark whether the ContextRefreshedEvent event has been received, i.e. {@ link #onApplicationEvent(ContextRefreshedEvent)} */
	private volatile boolean refreshEventReceived = false;

	/** Monitor for synchronized onRefresh execution. */
	private final Object onRefreshMonitor = new Object();

	public FrameworkServlet() {
	}

	public FrameworkServlet(WebApplicationContext webApplicationContext) {
		this.webApplicationContext = webApplicationContext;
	}
    
    @Override
	public void setApplicationContext(ApplicationContext applicationContext) {
		if (this.webApplicationContext == null && applicationContext instanceof WebApplicationContext) {
			this.webApplicationContext = (WebApplicationContext) applicationContext;
			this.webApplicationContextInjected = true;
		}
	}
}
  • contextClass attribute: the type of WebApplicationContext created. The default is xmlwebapplicationcontext Class, which is also used during the creation of Root WebApplicationContext container

  • contextConfigLocation attribute: the address of the configuration file, for example: classpath: Spring MVC xml

  • Webapplicationcontext attribute: webapplicationcontext object, the key of this article, Servlet WebApplicationContext container, has four creation methods

    1. Through the above construction method
    2. The ApplicationContextAware interface is implemented and injected through Spring, that is, the setApplicationContext(ApplicationContext applicationContext) method
    3. Through the findWebApplicationContext() method, see below
    4. Through the createWebApplicationContext(WebApplicationContext parent) method, see below
initServletBean

initServletBean() method, which rewrites the method of the parent class, will be called in the last step of init() method of HttpServletBean to further initialize the current Servlet object. Currently, it is mainly to initialize the Servlet WebApplicationContext container. The code is as follows:

@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> Initialize WebApplicationContext object
        this.webApplicationContext = initWebApplicationContext();
        // <2> Empty implementation, leaving subclass coverage. At present, there is no subclass implementation
        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");
    }
}
  1. Call the initWebApplicationContext() method to initialize the Servlet WebApplicationContext object
  2. Call the initFrameworkServlet() method to customize the current Servlet object. It is empty and left to be covered by subclasses. It seems that there is no subclass implementation at present
initWebApplicationContext

initWebApplicationContext() method [core] initializes the Servlet WebApplicationContext object as follows:

protected WebApplicationContext initWebApplicationContext() {
    // <1> Get the root WebApplicationContext object
    WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    // <2> Get WebApplicationContext wac object
    WebApplicationContext wac = null;

    // In the first case, if the constructor has passed in the webApplicationContext attribute, it will be used directly
    if (this.webApplicationContext != null) {
        // A context instance was injected at construction time -> use it
        wac = this.webApplicationContext;
        // If it is of type ConfigurableWebApplicationContext and is not activated, it will be initialized
        if (wac instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
            if (!cwac.isActive()) { // not active
                // 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);
                }
                // Configure and initialize wac
                configureAndRefreshWebApplicationContext(cwac);
            }
        }
    }
    // In the second case, get the corresponding WebApplicationContext object from ServletContext
    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();
    }
    // Third, create a WebApplicationContext object
    if (wac == null) {
        // No context instance is defined for this servlet -> create a local one
        wac = createWebApplicationContext(rootContext);
    }

    // <3> If the refresh event is not triggered, the refresh event is triggered actively
    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.
        synchronized (this.onRefreshMonitor) {
            onRefresh(wac);
        }
    }

    // <4> Set context to ServletContext
    if (this.publishContext) {
        // Publish the context as a servlet context attribute.
        String attrName = getServletContextAttributeName();
        getServletContext().setAttribute(attrName, wac);
    }

    return wac;
}
  1. Call the WebApplicationContextUtils#getWebApplicationContext((ServletContext sc) method to get the Root WebApplicationContext object from the ServletContext. You can go back to step 5 of the ContextLoader#initWebApplicationContext method. You will feel very familiar with it

  2. There are three ways to obtain the WebApplicationContext wac object

    1. If the constructor has passed in the webApplicationContext attribute, it will be directly referenced to wac, that is, the first and second creation methods mentioned in the constructor above

      If the wac is of ConfigurableWebApplicationContext type and is not refreshed (not activated), call the configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) method to configure and refresh, as shown below

      If the parent container is empty, set it to the Root WebApplicationContext object obtained in step 1 above

    2. Call the findWebApplicationContext() method to obtain the corresponding WebApplicationContext object from the ServletContext, which is the third creation method mentioned in the above construction method

      @Nullable
      protected WebApplicationContext findWebApplicationContext() {
          String attrName = getContextAttribute();
          // You need to configure the contextAttribute attribute attribute before you can find it. Generally, we won't configure it
          if (attrName == null) {
              return null;
          }
          // From ServletContext, obtain the WebApplicationContext object corresponding to the property name
          WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
          // If it does not exist, an IllegalStateException is thrown
          if (wac == null) {
              throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
          }
          return wac;
      }
      

      This is not usually done

    3. Call the createWebApplicationContext(@Nullable WebApplicationContext parent) method to create a WebApplicationContext object

      protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
          // <a>Get the class of context, xmlwebapplicationcontext class
          Class<?> contextClass = getContextClass();
          // Throw ApplicationContextException if it is not of ConfigurableWebApplicationContext type
          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");
          }
          // <b>Create an object of context class
          ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
      
          // <c> Set the environment, parent and configLocation properties
          wac.setEnvironment(getEnvironment());
          wac.setParent(parent);
          String configLocation = getContextConfigLocation();
          if (configLocation != null) {
              wac.setConfigLocation(configLocation);
          }
          // <d> Configure and initialize wac
          configureAndRefreshWebApplicationContext(wac);
      
          return wac;
      }
      

      <a>Get the class object of context. The default is xmlwebapplicationcontext Class. If it is not of ConfigurableWebApplicationContext type, an exception will be thrown

      <b>Create an instance object of context

      <c> Set the environment, parent and configLocation properties. Among them, configLocation is an important attribute

      <d> Call the configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) method to configure and refresh. See below

  3. If the refresh event is not triggered, the onrefresh (ApplicationContext) method will be called to trigger the refresh event. This method is empty and implemented by the subclass DispatcherServlet

  4. Set context to ServletContext

configureAndRefreshWebApplicationContext

configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) method to configure and initialize wac objects. The methods are as follows:

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
    // <1> If wac uses the default number, reset the id attribute
    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
        // Case 1: use contextId attribute
        if (this.contextId != null) {
            wac.setId(this.contextId);
        }
        // Case 2: automatic generation
        else {
            // Generate default id...
            wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                    ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
        }
    }

    // <2> Set the servletContext, servletConfig and namespace properties of wac
    wac.setServletContext(getServletContext());
    wac.setServletConfig(getServletConfig());
    wac.setNamespace(getNamespace());
    // <3> Add listener SourceFilteringListener to wac
    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
    // <4>
    ConfigurableEnvironment env = wac.getEnvironment();
    if (env instanceof ConfigurableWebEnvironment) {
        ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
    }

    // <5> Execute the logic after processing WebApplicationContext. At present, it is an empty method without any implementation
    postProcessWebApplicationContext(wac);
    // <6> Execute custom initialization context
    applyInitializers(wac);
    // <7> The wac is initialized by refreshing the wac
    wac.refresh();
}

In fact, the processing logic is similar to that of the ContextLoader#configureAndRefreshWebApplicationContext method

  1. If wac uses the default number, reset the id attribute
  2. Set the servletContext, servletConfig and namespace properties of wac
  3. Add listener SourceFilteringListener to wac
  4. Configure the Environment object and ignore it temporarily
  5. Execute the logic after processing WebApplicationContext, empty method, without any implementation
  6. Customize wac and ignore it temporarily
  7. [key] trigger the refresh event of wac and execute initialization. Here, we will initialize some Spring containers, involving Spring IOC related content
onRefresh

onRefresh(ApplicationContext context) method. After the Servlet WebApplicationContext refresh is completed, trigger the initialization of Spring MVC components. The method is as follows:

/**
 * Template method which can be overridden to add servlet-specific refresh work.
 * Called after successful context refresh.
 * <p>This implementation is empty.
 * @param context the current WebApplicationContext
 * @see #refresh()
 */
protected void onRefresh(ApplicationContext context) {
    // For subclasses: do nothing by default.
}

This is an empty method. The specific implementation is in the subclass DispatcherServlet. The code is as follows:

// DispatcherServlet.java
@Override
protected void onRefresh(ApplicationContext context) {
    initStrategies(context);
}

/**
 * Initialize the strategy objects that this servlet uses.
 * <p>May be overridden in subclasses in order to initialize further strategy objects.
 */
protected void initStrategies(ApplicationContext context) {
    // Initialize MultipartResolver
    initMultipartResolver(context);
    // Initialize LocaleResolver
    initLocaleResolver(context);
    // Initialize ThemeResolver
    initThemeResolver(context);
    // Initialize HandlerMappings
    initHandlerMappings(context);
    // Initialize HandlerAdapters
    initHandlerAdapters(context);
    // Initializing HandlerExceptionResolvers 
    initHandlerExceptionResolvers(context);
    // Initialize RequestToViewNameTranslator
    initRequestToViewNameTranslator(context);
    // Initialize ViewResolvers
    initViewResolvers(context);
    // Initialize FlashMapManager
    initFlashMapManager(context);
}

Initialize the nine components, which are just mentioned here first and will be analyzed in subsequent documents

onRefresh method can be triggered in two ways:

  • Method 1: if refreshEventReceived is false, that is, no refresh event is received (to prevent repeated initialization of related components), call it directly in the initWebApplicationContext method
  • Method 2: trigger the refresh event of wac in the configureAndRefreshWebApplicationContext method

Why can the above method 2 trigger the call of this method?

First, see Step 3 of the configureAndRefreshWebApplicationContext method, and add a SourceFilteringListener listener, as follows:

// <3> Add listener SourceFilteringListener to wac
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

After listening to relevant events, it will be delegated to ContextRefreshListener for processing. It is a private internal class of FrameworkServlet. Let's see how it handles it. The code is as follows:

private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {

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

This event is directly delegated to the onApplicationEvent method of the FrameworkServlet, as follows:

public void onApplicationEvent(ContextRefreshedEvent event) {
    // Mark refreshEventReceived as true
    this.refreshEventReceived = true;
    synchronized (this.onRefreshMonitor) {
        // Handle the ApplicationContext object in the event, empty implementation, and the subclass DispatcherServlet will be implemented
        onRefresh(event.getApplicationContext());
    }
}

The refresh method is set to true, so the refresh method will be called first, and then the refresh method will be set to true, which means that the refresh method will be triggered first, and then it will return to the above method

summary

This part analyzes the creation process of Spring MVC containers, namely Root WebApplicationContext and Servlet WebApplicationContext containers. They are parent-child relationships, and the creation process is not very complex. The prefix is created by the ContextLoaderListener listening to the corresponding events after the Servlet container such as Tomcat or Jetty is started. The latter is created during the initialization of DispatcherServlet, because it is an HttpServlet object, and its init method will be called to complete the initialization related work

DispatcherServlet is the core class of Spring MVC, which is equivalent to a scheduler. The request processing process is completed by scheduling various components through it, which will be analyzed in subsequent articles

Reference article: taro source code Source code analysis of MyBatis

Tags: Spring MVC source code analysis

Posted by sarun on Sun, 01 May 2022 22:30:54 +0300