Source code decryption of spring MVC startup process

Keywords: xml Spring JavaEE Attribute

As we know, spring MVC is finally deployed through Tomcat. When the application is deployed in the Servlet, the main steps are (reference from http://download.oracle.com/otn-pub/jcp/Servlet-3.0-fr-eval-oth-jspec/Servlet-3_-final-spec.pdf):

When a web application is deployed into a container, the following steps must be

performed, in this order, before the web application begins processing client

requests.

■ Instantiate an instance of each event listener identified by a element

in the deployment descriptor.

■ For instantiated listener instances that implement ServletContextListener , call

the contextInitialized() method.

■ Instantiate an instance of each filter identified by a element in the

deployment descriptor and call each filter instance's init() method.

■ Instantiate an instance of each servlet identified by a element that

includes a element in the order defined by the load-on-

startup element values, and call each servlet instance's init() method.

When the application is deployed to the container, the following steps need to be performed before the application of the corresponding customer's request:

  • Create and initialize event listeners marked by elements.
  • For a time listener, if the ServletContextListener interface is implemented, its contextInitialized() method is called.
  • Create and initialize the filter marked by the element and call its init() method.
  • Create and initialize the servlet marked by the element in the order defined in and call its init() method.

So for the application deployed under Tomcat, the listener will be initialized first, then the filter, and finally the servlet.

In our spring MVC, the simplest web.xml configuration is as follows

  <?xml version="1.0" encoding="UTF-8"?>
  <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">
  
     <!--Tell the loader to go to this location to load spring Related configuration of-->
     <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-mvc.xml</param-value>
     </context-param>
     <!--Configure front end controller-->
     <servlet>
        <servlet-name>springMvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--SpringMVC configuration file-->
        <init-param>
           <param-name>contextConfigLocation</param-name>
           <param-value>classpath:spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>0</load-on-startup>
     </servlet>
  
     <servlet-mapping>
        <servlet-name>springMvc</servlet-name>
        <url-pattern>/</url-pattern>
     </servlet-mapping>
     <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
     </listener>
     <!--To solve the problem of disorderly code filter-->
     <filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
           <param-name>encoding</param-name>
           <param-value>utf-8</param-value>
        </init-param>
     </filter>
  
     <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
     </filter-mapping>
  
  </web-app>

Now let's take a look at how spring MVC starts the container step by step and loads relevant information according to the configuration file and the above initialization process.

Initialize Listener

The listener class we defined here is ContextLoaderListener. Let's take a look at the specific class definition

/**
 * Bootstrap listener to start up and shut down Spring's root {@link WebApplicationContext}.
 * Simply delegates to {@link ContextLoader} as well as to {@link ContextCleanupListener}.
 *
 * <p>As of Spring 3.1, {@code ContextLoaderListener} supports injecting the root web
 * application context via the {@link #ContextLoaderListener(WebApplicationContext)}
 * constructor, allowing for programmatic configuration in Servlet 3.0  environments.
 * See {@link org.springframework.web.WebApplicationInitializer} for usage examples.
 *
 * @author Juergen Hoeller
 * @author Chris Beams
 * @since 17.02.2003
 * @see #setContextInitializers
 * @see org.springframework.web.WebApplicationInitializer
 */
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {

The ContextLoaderListener class inherits the ContextLoader class and implements the ServletContextListener interface. According to the startup program, its contextInitialized() method will be called

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

Now let's take a look at the initialization process of the application context

//Initializing the context of a web application
//ServletContext is officially called servlet context. The server will create an object for each project, which is the ServletContext object. This object is globally unique, and it is shared by all servlets within the project. So it's called global application sharing object.
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
       /*
          First, through webapplicationcontext. Root? Web? Application? Context? Attribute
          This static variable of String type takes a root IoC container as a global variable
         Stored in the application object, if any, there can only be one
          If it is found that the root WebApplicationContext, the root IoC container, already exists when it is initialized
          Therefore, only one object of ContextLoader class or its subclass is allowed in web.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!");
   }
   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 the context does not exist, create it
      if (this.context == null) {
         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);
            }
            /**
             * Configure and refresh the root IOC container of the application. bean creation and initialization will be performed here. It will eventually call
             * {@link org.springframework.context.support.AbstractApplicationContext#refresh refresh()Method}
             * And the bean classes in the IOC container will be placed in the application
             */
            configureAndRefreshWebApplicationContext(cwac, servletContext);
         }
      }
      //Configure the application in the servletContext in the way of attribute configuration. Because the servletContext is unique to the whole application, you can get the application according to the key value, so you can get all the information of the application
      servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
       ....
}

As you can see, the entire execution process of initWebApplicationContext() method is to create the application context, which is the root IOC container. And set the application context to servletContext in the way of setAttribute, so that all kinds of application information can be obtained by using servletContext in the whole application.

Let's focus on the configureandrefresh webapplicationcontext () method.

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()));
      }
   }
   //Set the ServletContext to the properties of the application
   wac.setServletContext(sc);
   //Get the contextConfigLocation parameter value configured in web.xml
   String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
   if (configLocationParam != null) {
      //Set the configuration information of web.xml to application
      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);
   //Call the refresh method of the application to load the IOC container
   wac.refresh();
}

Let's trace the debug code to see the actual information.

We go to the refresh() method

As you can see, the loading process of bean class in IOC container is completed in refresh.

So far, spring MVC has finished listening to event listeners marked by elements.

Initialize Filter

After the initialization of the listener, the creation and initialization of the filter will be performed. We use the CharacterEncodingFilter here. Let's take a look at the specific class diagram information of this class.

Because it implements the Filter interface, its corresponding init(FilterConfig filterConfig) method is called. We found the implementation of this method in its parent class, GenericFilterBean.

@Override
public final void init(FilterConfig filterConfig) throws ServletException {
   this.filterConfig = filterConfig;
   //Set the set initialization parameter information to pvs
   PropertyValues pvs = new FilterConfigPropertyValues(filterConfig, this.requiredProperties);
   if (!pvs.isEmpty()) {
      try {
         //Wrap the specific filter class
         BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
         //Create the corresponding resource loading class
         ResourceLoader resourceLoader = new ServletContextResourceLoader(filterConfig.getServletContext());
         Environment env = this.environment;
         if (env == null) {
            env = new StandardServletEnvironment();
         }
         bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, env));
         initBeanWrapper(bw);
         bw.setPropertyValues(pvs, true);
      }
      catch (BeansException ex) {
         String msg = "Failed to set bean properties on filter '"  
            filterConfig.getFilterName()   "': "   ex.getMessage();
         logger.error(msg, ex);
         throw new NestedServletException(msg, ex);
      }
   }
   //Implementation by subclasses
   initFilterBean();
   ...
}

There is no initialization. In fact, the main function of Filter is to process doFilter() when there is a request coming. In the start-up phase, the processing is relatively small.

Initialization of Servlet

The last step of web application startup is to create and initialize a Servlet. We will analyze it from the class of dispatcher Servlet we use. This class is the front-end controller, which is mainly used to distribute user requests to specific implementation classes and return specific response information.

According to the class diagram, DispatchServlet implements the Servlet interface, so according to the loading process, its init(ServletConfig config) method will be called finally. Looking for the implementation of init(ServletConfig config) method from DispatchServlet, we will find that the method does not exist. Then we continue to look up, look in its parent class, and finally find the method in GenericServlet

public void init(ServletConfig config) throws ServletException {
this.config = config;
//It is implemented by subclasses.
this.init();
}

We found the specific implementation of init() method in HttpServletBean.

public final void init() throws ServletException {
   if (logger.isDebugEnabled()) {
      logger.debug("Initializing servlet '"   getServletName()   "'");
   }
   // Set bean properties from init parameters.
   //Set property information
   PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
   if (!pvs.isEmpty()) {
      try {
         //The specific implementation is used for packaging, and the packer mode is used
         BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
         ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
         bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
         initBeanWrapper(bw);
         //Set the attribute information set in web.xml to bw
         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.
   //Implemented by subclass
   initServletBean();

   if (logger.isDebugEnabled()) {
      logger.debug("Servlet '"   getServletName()   "' configured successfully");
   }
}

Among them, the initServletBean() method is handed over to the subclass for processing, and we finally find the corresponding implementation in the FrameworkServlet class

@Override
protected final void initServletBean() throws ServletException {
   getServletContext().log("Initializing Spring FrameworkServlet '"   getServletName()   "'");
   if (logger.isInfoEnabled()) {
      logger.info("FrameworkServlet '"   getServletName()   "': initialization started");
   }
   long startTime = System.currentTimeMillis();

   try {
      //Initialize web application container
      this.webApplicationContext = initWebApplicationContext();
      initFrameworkServlet();
   }
   ...
}

In this part, we mainly create the web application container context and initialize it. Let's trace the initialization process

protected WebApplicationContext  () {
   //Get to root IOC container
   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) {
               //Parent the root IOC container to the servlet's IOC container
               //If the current Servlet has a WebApplicationContext, that is, a sub IoC container
               // If the root IoC container obtained above exists, the root IoC container will be the parent container of the child IoC container
               cwac.setParent(rootContext);
            }
            //Configure and refresh the sub container, load the corresponding bean entity class in the sub IOC container
            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
      //If there is no sub IOC container in the current Servlet, go to find
      wac = findWebApplicationContext();
   }
   if (wac == null) {
      // No context instance is defined for this servlet -> create a local one
      //If not, create a
      wac = createWebApplicationContext(rootContext);
   }
   ...
   return wac;
}

The code is relatively long. In fact, the main function of initWebApplicationContext() function is to initialize the IOC container of a Web class. When tracing through debug, we can see that we will finally execute to createWebApplicationContext(), continue to trace the code execution process

protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
   Class<?> contextClass = getContextClass();
   ...
   //Initialize a ConfigurableWebApplicationContext object based on class information
   ConfigurableWebApplicationContext wac =
         (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
   //Setting environment information for web context
   wac.setEnvironment(getEnvironment());
   //Set its parent class as the root IOC container, which is unique to the whole application.
   wac.setParent(parent);
   //Set the location of its specific configuration information. Here is classpath:spring-mvc.xml
   String configLocation = getContextConfigLocation();
   if (configLocation != null) {
      wac.setConfigLocation(configLocation);
   }
   //Configure and refresh the IOC container of web application
   configureAndRefreshWebApplicationContext(wac);
   return wac;
}

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
   ....
   //Information about configuring containers
   wac.setServletContext(getServletContext());
   wac.setServletConfig(getServletConfig());
   wac.setNamespace(getNamespace());
   //Configuring load listeners for applications
   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);
   //Application initialization information
   applyInitializers(wac);
   //Refresh and load the Bean entity class
   wac.refresh();
}

As you can see here, in this case, the class loading is mainly based on the configuration file information, and a Listener SourceFilteringListener for container loading information is configured. Finally, the loading process of entity class in the container is carried out through the refresh() method. This refresh() method is the same method that we used to implement class initialization in Listener. So far, all classes configured in our application can be scanned and configured in our IOC container.

Because we configured the relevant container loaded listener, we sent the corresponding container to complete the broadcast information when calling the finishRefresh() method in the refresh() method, so that we could call our registered listener SourceFilteringListener. Let's take a look at the call logic

public SourceFilteringListener(Object source, ApplicationListener<?> delegate) {
   this.source = source;
   this.delegate = (delegate instanceof GenericApplicationListener ?
         (GenericApplicationListener) delegate : new GenericApplicationListenerAdapter(delegate));
}

@Override
public void onApplicationEvent(ApplicationEvent event) {
    if (event.getSource() == this.source) {
        onApplicationEventInternal(event);
    }
}

protected void onApplicationEventInternal(ApplicationEvent event) {
    if (this.delegate == null) {
        throw new IllegalStateException(
                "Must specify a delegate object or override the onApplicationEventInternal method");
    }
    /**
     * Here, delegate is the specific proxy class passed in
     */
    this.delegate.onApplicationEvent(event);
}

You can see that in the end, we call the onApplicationEvent method of ContextRefreshListener when we create this class. Let's take a look at this class

private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {

   @Override
   public void onApplicationEvent(ContextRefreshedEvent event) {
      //Finally, the onApplicationEvent method of the FrameworkServlet is called
      FrameworkServlet.this.onApplicationEvent(event);
   }
}

Continue tracking:

public void onApplicationEvent(ContextRefreshedEvent event) {
   this.refreshEventReceived = true;
   synchronized (this.onRefreshMonitor) {
      //onRefresh method called
      onRefresh(event.getApplicationContext());
   }
}

protected void onRefresh(ApplicationContext context) {
    // For subclasses: do nothing by default.
}

As you can see, the final onRefresh() is implemented by a subclass, which is our dispatcher servlet class.

It's finally our main player..

Let's see what our Lord has done

    //context an IoC sub container created for DispatcherServlet
    @Override
    protected void onRefresh(ApplicationContext context) {
        initStrategies(context);
    }

    //Initialize the policy information used by the servlet. Subclass can add more ER policy methods by overriding the method class
    protected void initStrategies(ApplicationContext context) {
        //Initializing MultipartResolver can support file upload
        initMultipartResolver(context);
        //Initialize local resolver
        initLocaleResolver(context);
        //Initialize theme resolver
        initThemeResolver(context);
        //Processor mapper that maps requests and methods
        initHandlerMappings(context);
        //Processor adapter
        initHandlerAdapters(context);
        //Processor exception resolver
        initHandlerExceptionResolvers(context);
        //
        initRequestToViewNameTranslator(context);
        //view resolver 
        initViewResolvers(context);
        //FlashMap Manager
        initFlashMapManager(context);
    }

We can see that it mainly initializes some parsers and processors we use. After receiving the request, we can parse the request, call the method, handle the exception and so on according to these parsers.

So far, the initialization of the Servlet is complete. The main reason for the complexity is that many methods are implemented in the parent class. The hierarchy is complex and requires a little tracking analysis.

This article is composed of Ken opened. Release!

Published 15 original articles, won praise 2, visited 10000+
Private letter follow

Posted by imurkid on Mon, 20 Jan 2020 05:11:51 -0800