The Spring framework used in this article is version 5.1

Front knowledge

In terms of design, Spring Web MVC uses the front-end controller pattern to design around a central Servlet, the DispatcherServlet, which provides common logic for handling requests, Delegating the work to configurable components makes the Spring Web MVC framework very flexible.

Like other servlets, dispatcherServlets need to be configured using Java code according to the Servlet specification or in a web.xml file.

When Spring Web MVC starts, DispatcherServlet loads first, then DispatcherServlet loads the components required for request mapping, view resolution, and exception handling according to the configuration.

Here is an example of configuring DispatcherServlet in a web.xml file:

<web-app>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/app-context.xml</param-value>
    </context-param>

    <servlet>
        <servlet-name>app</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value></param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>app</servlet-name>
        <url-pattern>/app/*</url-pattern>
    </servlet-mapping>

</web-app>
Copy the code

Some beans that do concrete work

When a request is processed, the DispatcherServlet delegates the following beans to process the request and provide the appropriate response results.

HandlerMapping

To map requests to handlers, HandlerMapping has two main implementations:

  • RequestMappingHandlerMapping
  • SimpleUrlHandlerMapping

The former is used to support @requestMapping annotations, while the latter supports display registration of controllers.

HandlerAdapter

Help the DispatcherServlet invoke a handler that matches the request path to process the request.

Use adapters to keep the DispatcherServlet free of handler implementation details, such as the Controller calling the @Controller annotation needing to handle the annotation.

HandlerExceptionResolver

Different policies are used to handle exceptions, such as whether the HTTP status code returned is 5xx or 4xx.

ViewResolver

Parse views, such as JSP and FreeMarker template files.

LocaleResolver, LocaleContextResolver

Provide localization support, such as multi-language, time zone, etc.

ThemeResolver

Provides theme support for personalized layouts.

MultipartResolver

Parse multi-part requests.

FlashMapManager

Manage the FlashMap that holds Falsh attributes, which are used to pass data across requests.

Source code analysis

The DispatcherServlet class is defined as follows:

public class DispatcherServlet extends FrameworkServlet
Copy the code

As you can see, it inherits from the class FrameworkServlet. Let’s look at the overall class inheritance relationship:

From the perspective of class inheritance, HTTP requests will be processed through the implementation of Servlet interface, where classes related to servlets are inherited from top to bottom:

  • GenericServlet, an abstract class, is implementedServletInterface.
  • HttpServlet, an abstract class, inherits fromGenericServlet.
  • HttpSerlvetBean, an abstract class, inherits fromHttpServlet.
  • FrameworkServlet, an abstract class, inherits fromHttpServlet.
  • DispatcherServlet, a concrete class, inherits fromFrameworkServlet.

GenericServlet and HttpServlet classes simply encapsulate and extend the Servlet interface. Therefore, we can focus our analysis on HttpSerlvetBean, FrameworkServlet and DispatcherServlet classes.

When the DispatcherServlet is initialized, the order of invocation between these three classes is shown below:

According to the Servlet specification, there is an init() method in the Servlet interface that will be called by the container once a Servlet implementation has been instantiated. Spring Web MVC loads the entire framework by overwriting the init() method in the HttpSerlvetBean class.

HttpServletBean class

The init() method of the HttpSerlvetBean class does two main things. One is to read the initialization parameters, such as the contextConfigLocation parameter, from the web.xml file. The second is to call the initServletBean() method implemented by the subclass to do the specific initialization.

The init() method is implemented as follows:

@Override
public final void init(a) throws ServletException {

    // Read the initialization parameters from the web.xml file
    PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
    if(! pvs.isEmpty()) {try {
            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);
            }
            throwex; }}// Calls the 'initServletBean()' method implemented by the subclass.
    initServletBean();
}
Copy the code

What is interesting in the above code is the way initialization parameters are read from the web.xml file, using the contextConfigLocation parameter as an example.

For the contextConfigLocation parameter, there is a private variable in the FrameworkServlet subclass of HttpServletBean with the following definitions:

@Nullable
private String contextConfigLocation;
Copy the code

And corresponding Get and Set methods:

public void setContextConfigLocation(@Nullable String contextConfigLocation) {
	this.contextConfigLocation = contextConfigLocation;
}

@Nullable
public String getContextConfigLocation(a) {
	return this.contextConfigLocation;
}
Copy the code

However, Spring does not call the setContextConfigLocation() method directly to assign the contextConfigLocation variable. Instead, BeanWrapper is used with the ResourceEditor to assign the variable.

After the init() method reads the initialization parameters, it calls the initServletBean() method, which is an empty method in the HttpServletBean protected, defined as follows:

protected void initServletBean(a) throws ServletException
Copy the code

The actual initialization is done by overriding the initServletBean() method in the FrameworkServlet subclass of HttpServletBean.

FrameworkServlet class

The initServletBean() method is implemented as follows:

protected final void initServletBean(a) throws ServletException {
    try {
        // Initialize the WebApplicationContext
    	this.webApplicationContext = initWebApplicationContext();
    	
    	// A Hook that gives subclasses the chance to do some work after the context is initialized
    	initFrameworkServlet();
    }
    catch (ServletException | RuntimeException ex) {
    	logger.error("Context initialization failed", ex);
    	throwex; }}Copy the code

In this method, the following two things are done:

  • callinitWebApplicationContext()Method initializationwebApplicationContext.
  • Call reservedinitFrameworkServlet()Method gives subclasses a chance to do some extra work after initialization.

In fact, the current initFrameworkServlet () is a no use empty function, and a subclass nor to overwrite it, so we need to focus only on initWebApplicationContext () method.

The realization of the initWebApplicationContext () method is as follows:

protected WebApplicationContext initWebApplicationContext(a) {
    WebApplicationContext rootContext =
            WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;

    if (this.webApplicationContext ! =null) {
        // Use the webApplicationContext parameter if provided at instantiation time.
        wac = this.webApplicationContext;
        if (wac instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
            if(! cwac.isActive()) {// If the context has not been refreshed, set an ID for it and refresh it
                if (cwac.getParent() == null) {
                    // If the context does not have a parent, set one for it.cwac.setParent(rootContext); } configureAndRefreshWebApplicationContext(cwac); }}}if (wac == null) {
        // If no context is provided at instantiation time, look for one in ServletContext.
        wac = findWebApplicationContext();
    }
    if (wac == null) {
        // If no existing context is found above, create one yourself
        wac = createWebApplicationContext(rootContext);
    }

    // If onRefresh has not already been called, call it manually
    if (!this.refreshEventReceived) {
        synchronized (this.onRefreshMonitor) {
            // Call onRefresh implemented by the subclass to initialize the policy objectonRefresh(wac); }}if (this.publishContext) {
        // Put WebApplicationContext into ServletContext
        String attrName = getServletContextAttributeName();
        getServletContext().setAttribute(attrName, wac);
    }

    return wac;
}
Copy the code

This method does the following:

First, check whether the WebApplicationContext parameter is passed in via the constructor FrameworkServlet(WebApplicationContext). If so, some configuration is done to the passed webApplicationContext, such as setting the parent context and the context ID.

Second, check whether a webApplicationContext is provided in the ServletContext. If so, bring it and use it directly.

Third, if has not been seen in the first step and second step webApplicationContext available, then call createWebApplicationContext () method creates a himself.

Fourth, do a backstop and determine whether the onRefresh() method was called by the value of the refreshEventReceived variable, or if it never was, trigger a call.

OnRefresh method in addition to the initWebApplicationContext () method calls, in FrameworkServlet onApplicationEvent () method is also called the onRefresh method.

The onApplicationEvent() method is implemented as follows:

public void onApplicationEvent(ContextRefreshedEvent event) {
    this.refreshEventReceived = true;
    synchronized (this.onRefreshMonitor) { onRefresh(event.getApplicationContext()); }}Copy the code

In the above code, in addition to calling the onRefresh() method, the variable refreshEventReceived is set to true, ensuring that the onRefresh() method is called only once.

So who is calling the onApplicationEvent() method?

For this problem we’ll look at some of the used to create webApplicationContext createWebApplicationContext () method of implementation:

protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) { Class<? > contextClass = getContextClass();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");
    }
    ConfigurableWebApplicationContext wac =
            (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

    wac.setEnvironment(getEnvironment());
    wac.setParent(parent);
    // Use the configuration file specified by configLocation
    String configLocation = getContextConfigLocation();
    if(configLocation ! =null) {
        wac.setConfigLocation(configLocation);
    }
    // Configure the webApplicationContext
    configureAndRefreshWebApplicationContext(wac);

    return wac;
}
Copy the code

In this approach, two main things are done: One is to use configLocation specified by the configuration file to load the beans, the second is called configureAndRefreshWebApplicationContext () method to configure webApplicationContext.

If you still have impression, you may remember this place initWebApplicationContext () method also calls the code configureAndRefreshWebApplicationContext () method:

if (this.webApplicationContext ! =null) {
    // Use the webApplicationContext parameter if provided at instantiation time.
    wac = this.webApplicationContext;
    if (wac instanceof ConfigurableWebApplicationContext) {
        ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
        if(! cwac.isActive()) {// If the context has not been refreshed, set an ID for it and refresh it
            if (cwac.getParent() == null) {
                // If the context does not have a parent, set one for it.
                cwac.setParent(rootContext);
            }
            // Configure the webApplicationContextconfigureAndRefreshWebApplicationContext(cwac); }}}Copy the code

As a result, we are going to study in configureAndRefreshWebApplicationContext are doing () method of unknown things.

The realization of the configureAndRefreshWebApplicationContext () method is as follows:

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
    if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
        // If the context ID is native, set a more readable ID for it
        // Use the ID specified in the configuration file
        if (this.contextId ! =null) {
            wac.setId(this.contextId);
        }
        else {
            // Generate a default ID
            wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                    ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
        }
    }

    wac.setServletContext(getServletContext());
    wac.setServletConfig(getServletConfig());
    wac.setNamespace(getNamespace());
    // Add a Listener to listen for refresh events of the webApplicationContext
    wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

    // Initialize PropertySource
    ConfigurableEnvironment env = wac.getEnvironment();
    if (env instanceof ConfigurableWebEnvironment) {
        ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
    }

    postProcessWebApplicationContext(wac);
    
    / / call ApplicationContextInitializer
    applyInitializers(wac);
    
    // Call the Refresh method of webApplicationContext
    wac.refresh();
}
Copy the code

Here are a few things you do in the code above:

  • First, set an ID for the webApplicationContext.
  • Second, add a ContextRefreshListener to listen for refresh events of the webApplicationContext.
  • Third, initialize PropertySource.
  • Fourth, call the Refresh method of the webApplicationContext.

In step 2 above, the ContextRefreshListener class is implemented as follows:

private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {

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

As you can see from the implementation of this class, the inner class of the FrameworkServlet calls the onApplicationEvent() method upon receiving a context-refreshed event.

We are now ready to answer the previous question: Who is calling the onApplicationEvent() method? .

Through the above analysis we can know in configureAndRefreshWebApplicationContext () method of webApplicationContext have set up a listener, The listener calls the onApplicationEvent() method after listening to the context Refresh.

Since onApplicationEvent () method with initWebApplicationContext () will be called onRefresh () method, then the onRefresh () method and did what?

The onRefresh() method is defined in the FrameworkServlet as follows:

protected void onRefresh(ApplicationContext context) {}Copy the code

As you can see, it is an empty method in the FrameworkServlet class. The logic is implemented by overwriting this method with DispatcherServlet, a subclass of FrameworkServlet, and its implementation will be analyzed below.

DispatcherServlet class

The onRefresh() method in the DispatcherServlet class is implemented as follows:

@Override
protected void onRefresh(ApplicationContext context) {
	initStrategies(context);
}

protected void initStrategies(ApplicationContext context) {
    // Initializes the component required by the multi-part request
    initMultipartResolver(context);
    // Initialize the primary key used to provide localization support
    initLocaleResolver(context);
    // Initialize the component used to provide theme support
    initThemeResolver(context);
    // Initialize the component used to request the mapping
    initHandlerMappings(context);
    // Initializes the component used to adapt the handler
    initHandlerAdapters(context);
    // Initialize the component used for exception handling
    initHandlerExceptionResolvers(context);
    // Initialize the component used for view resolution
    initRequestToViewNameTranslator(context);
    initViewResolvers(context);
    // Initializes the component used to manage Flash attributes
    initFlashMapManager(context);
}
Copy the code

As you can see from the code above, the onRefresh() method delegates the specific work to the initStrategies() method.

In the initStrategies() method, a number of policy objects are initialized to support different functions, such as request mapping, view resolution, exception handling, and so on.

At this point, the DispatcherServlet initialization is complete.

Unfinished, to be continued.