interface21 – web – DispatcherServlet (DispatcherServlet initialization process)

Foreword

Recently I plan to take some time to take a good look at the source code of Spring. However, there are too many iterated versions of Spring’s source code. It is relatively large and looks tiring, so I plan to start with the initial version (interface21), just for learning. Understand its design philosophy, and then slowly study the content of each version change. . .

Let’s start with a typical web project example of interface21, pet clinic – petclinic, because this project basically covers Spring’s APO, IOC, JDBC, Web MVC, transactions, internationalization, theme switching, parameter verification and other main functions. . .

Following the previous article, after understanding the process of ContextLoaderListener (loading Spring Web Application Context), let’s take a look at the key controller of Spring mvc – DispatcherServlet initialization process~~~~~~~

Corresponding web.xml configuration

 <servlet>
        <servlet-name>petclinic</servlet-name>
        <servlet-class>com.interface21.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>petclinic</servlet-name>
        <url-pattern>*.htm</url-pattern>
    </servlet-mapping>

There is one thing that needs attention here, the configuration of load-on-startup. Regarding the meaning of this Servlet parameter, you can first read the explanation in the web-app_2_3.dtd file, as follows, the general meaning is that if load-on- is not configured startup or the value is a negative number, the servlet container is free to choose when to load the servlet (instantiate and execute the Servlet’s init method). If it is 0 or a positive number, the Servlet must be loaded when the container starts and its init method is executed. And the Servlet with the smaller value is loaded first. Looking back at our DispatcherServlet, since load-on-startup is configured as 1, the init method will be executed at startup, which is the DispatcherServlet initialization process that this article will focus on:

<!--
The load-on-startup element indicates that this servlet should be
loaded (instantiated and have its init() called) on the startup
of the web application. The optional contents of
these elements must be an integer indicating the order in which
the servlet should be loaded. If the value is a negative integer,
or the element is not present, the container is free to load the
servlet whenever it chooses. If the value is a positive integer
or 0, the container must load and initialize the servlet as the
application is deployed. The container must guarantee that
servlets marked with lower integers are loaded before servlets
marked with higher integers. The container may choose the order
of loading of servlets with the same load-on-start-up value.

Used in: servlet
-->

Execution timing diagram (if you can’t see clearly, you can click to view the original picture)

Brief analysis of each step in the timing diagram

The entry point for execution is the init method of the HttpServletBean class. Since the load-on-startup parameter of DispatcherServlet is configured to 1, the init method of the Servlet will be automatically called when the Servlet container (tomcat) starts.

Step description:

  1. First, execute the init method of HttpServletBean, the parent class of DispatcherServlet;
    public final void init() throws ServletException {
            this.identifier = "Servlet with name '" + getServletConfig().getServletName() + "' ";
    
            logger.info(getIdentifier() + "entering init...");
    
            // Set bean properties
            try {
                PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), requiredProperties);
                BeanWrapper bw = new BeanWrapperImpl(this);
                bw.setPropertyValues(pvs);
                logger.debug(getIdentifier() + "properties bound OK");
    
                // Let subclasses do whatever initialization they like
                initServletBean();
                logger.info(getIdentifier() + "configured successfully");
            } catch (BeansException ex) {
                String mesg = getIdentifier() + ": error setting properties from ServletConfig";
                logger.error(mesg, ex);
                throw new ServletException(mesg, ex);
            } catch (Throwable t) {
                // Let subclasses throw unchecked exceptions
                String mesg = getIdentifier() + ": initialization error";
                logger.error(mesg, t);
                throw new ServletException(mesg, t);
            }
        }
  2. Get the Servlet’s initialization parameters, create a BeanWrapperImpl instance, and set attribute values;
  3. Execute the initServletBean method of FrameworkServlet, a subclass of HttpServletBean;
     protected final void initServletBean() throws ServletException {
            long startTime = System.currentTimeMillis();
            logger.info("Framework servlet '" + getServletName() + "' init");
            this.webApplicationContext = createWebApplicationContext();
            initFrameworkServlet();
            long elapsedTime = System.currentTimeMillis() - startTime;
            logger.info("Framework servlet '" + getServletName() + "' init completed in " + elapsedTime + " ms");
        }
  4. Call the createWebApplicationContext method of FrameworkServlet.
     private WebApplicationContext createWebApplicationContext() throws ServletException {
            getServletContext().log("Loading WebApplicationContext for servlet '" + getServletName() + "'");
            ServletContext sc = getServletConfig().getServletContext();
            WebApplicationContext parent = WebApplicationContextUtils.getWebApplicationContext(sc);
            String namespace = getNamespace();
    
            WebApplicationContext waca = (this.contextClass != null) ?
                    instantiateCustomWebApplicationContext(this.contextClass, parent, namespace):
                    new XmlWebApplicationContext(parent, namespace);
            logger.info("Loading WebApplicationContext for servlet '" + getServletName() + "': using context class '" + waca.getClass().getName() + "'");
            waca.setServletContext(sc);
    
            if (this.publishContext) {
                // Publish the context as a servlet context attribute
                String attName = getServletContextAttributeName();
                sc.setAttribute(attName, waca);
                logger.info("Bound context of servlet '" + getServletName() + "' in global ServletContext with name '" + attName + "'");
            }
            return waca;
        }
  5. Enter the createWebApplicationContext method and obtain the WebApplicationContext from the properties of the ServletContext. The context is loaded by the ContextLoaderListener.
  6. Create a child context XmlWebApplicationContext. Its parent context is the WebApplicationContext loaded by the previous ContextLoaderListener. Regarding the relationship between the two contexts and the content responsible for loading, please refer to this picture. Picture source (http://jinnianshilongnian.iteye.com/blog/ 1602617/)
  7. Execute the waca.setServletContext method of the sub-context XmlWebApplicationContext to load the petclinic-servlet.xml configuration file (internationalization, theme, HandlerMapping, HandlerAdapter, view parsing and other related configurations). For the loading process of the WebApplicationContext context, please refer to the previous article (Spring Web Application Context loading process), the processes are the same;
  8. Set the subcontext to the ServletContext property
  9. Enter the initFrameworkServlet method of the DispatcherServlet class to mainly perform some initialization work.
     protected void initFrameworkServlet() throws ServletException {
            initLocaleResolver();
            initThemeResolver();
            initHandlerMappings();
            initHandlerAdapters();
            initViewResolver();
        }
  10. Internationalization related: Execute the initLocaleResolver method to obtain the localeResolver bean from the context. If not, use the default AcceptHeaderLocaleResolver.
     private void initLocaleResolver() throws ServletException {
            try {
                this.localeResolver = (LocaleResolver) getWebApplicationContext().getBean(LOCALE_RESOLVER_BEAN_NAME);
                logger.info("Loaded locale resolver [" + this.localeResolver + "]");
            } catch (NoSuchBeanDefinitionException ex) {
                // We need to use the default
                this.localeResolver = new AcceptHeaderLocaleResolver();
                logger.info("Unable to locate locale resolver with name '" + LOCALE_RESOLVER_BEAN_NAME + "': using default [" + this.localeResolver + "]");
            } catch (BeansException ex) {
                // We tried and failed to load the LocaleResolver specified by a bean
                throw new ServletException("Fatal error loading locale resolver with name '" + LOCALE_RESOLVER_BEAN_NAME + "': using default", ex);
            }
        }
  11. Theme related: Execute the initThemeResolver method and obtain the themeResolver bean from the context. If not, use the default FixedThemeResolver.
     private void initThemeResolver() throws ServletException {
            try {
                this.themeResolver = (ThemeResolver) getWebApplicationContext().getBean(THEME_RESOLVER_BEAN_NAME);
                logger.info("Loaded theme resolver [" + this.themeResolver + "]");
            } catch (NoSuchBeanDefinitionException ex) {
                // We need to use the default
                this.themeResolver = new FixedThemeResolver();
                logger.info("Unable to locate theme resolver with name '" + THEME_RESOLVER_BEAN_NAME + "': using default [" + this.themeResolver + "]");
            } catch (BeansException ex) {
                // We tried and failed to load the ThemeResolver specified by a bean
                throw new ServletException("Fatal error loading theme resolver with name '" + THEME_RESOLVER_BEAN_NAME + "': using default", ex);
            }
        }
  12. Execute the initHandlerMappings method to obtain the HandlerMapping type bean from the context. If not, use the default BeanNameUrlHandlerMapping.
     private void initHandlerMappings() throws ServletException {
            this.handlerMappings = new ArrayList();
    
            // Find all HandlerMappings in the ApplicationContext
            String[] hms = getWebApplicationContext().getBeanDefinitionNames(HandlerMapping.class);
            for (int i = 0; i < hms.length; i + + ) {
                initHandlerMapping(hms[i]);
                logger.info("Loaded handler mapping [" + hms[i] + "]");
            }
    
            // Ensure we have at least one HandlerMapping, by registering
            // a default HandlerMapping if no other mappings are found.
            if (this.handlerMappings.isEmpty()) {
                initDefaultHandlerMapping();
                logger.info("No HandlerMappings found in servlet '" + getServletName() + "': using default");
            } else {
                // We keep HandlerMappings in sorted order
                Collections.sort(this.handlerMappings, new OrderComparator());
            }
        }
     private void initDefaultHandlerMapping() throws ServletException {
            try {
                HandlerMapping hm = new BeanNameUrlHandlerMapping();
                hm.setApplicationContext(getWebApplicationContext());
                this.handlerMappings.add(hm);
            } catch (ApplicationContextException ex) {
                throw new ServletException("Error initializing default HandlerMapping: " + ex.getMessage(), ex);
            }
        }
  13. Execute the initHandlerAdapters method to obtain the HandlerAdapter type bean from the context. If not, use the default SimpleControllerHandlerAdapter.
     private void initHandlerAdapters() throws ServletException {
            this.handlerAdapters = new ArrayList();
    
            String[] has = getWebApplicationContext().getBeanDefinitionNames(HandlerAdapter.class);
            for (int i = 0; i < has.length; i + + ) {
                initHandlerAdapter(has[i]);
                logger.info("Loaded handler adapter [" + has[i] + "]");
            }
    
            // Ensure we have at least one HandlerAdapter, by registering
            // a default HandlerAdapter if no other adapters are found.
            if (this.handlerAdapters.isEmpty()) {
                initDefaultHandlerAdapter();
                logger.info("No HandlerAdapters found in servlet '" + getServletName() + "': using default");
            } else {
                // We keep HandlerAdapters in sorted order
                Collections.sort(this.handlerAdapters, new OrderComparator());
            }
        }
     private void initDefaultHandlerAdapter() throws ServletException {
            try {
                HandlerAdapter ha = new SimpleControllerHandlerAdapter();
                ha.setApplicationContext(getWebApplicationContext());
                this.handlerAdapters.add(ha);
            } catch (ApplicationContextException ex) {
                throw new ServletException("Error initializing default HandlerAdapter: " + ex.getMessage(), ex);
            }
        }
  14. Execute the initViewResolver method to obtain the viewResolver bean from the context. If not, use the default InternalResourceViewResolver.
     private void initViewResolver() throws ServletException {
            try {
                this.viewResolver = (ViewResolver) getWebApplicationContext().getBean(VIEW_RESOLVER_BEAN_NAME);
                logger.info("Loaded view resolver [" + viewResolver + "]");
            } catch (NoSuchBeanDefinitionException ex) {
                // We need to use the default
                this.viewResolver = new InternalResourceViewResolver();
                try {
                    this.viewResolver.setApplicationContext(getWebApplicationContext());
                } catch (ApplicationContextException ex2) {
                    throw new ServletException("Fatal error initializing default ViewResolver");
                }
                logger.info("Unable to locate view resolver with name '" + VIEW_RESOLVER_BEAN_NAME + "': using default [" + this.viewResolver + "]");
            } catch (BeansException ex) {
                // We tried and failed to load the ViewResolver specified by a bean
                throw new ServletException("Fatal error loading view resolver: bean with name '" + VIEW_RESOLVER_BEAN_NAME + "' is required in servlet '" + getServletName() + "': using default", ex);
            }
        }
  15. Return to FrameworkServlet class
  16. Return to HttpServletBean class
  17. The init method of Servlet has been executed.

In addition, you can pay attention to the destruction method of the Servlet. Similarly, it also performs some resource destruction and other operations, destroys the singleton bean object created by the factory, publishes the ContextClosedEvent event, etc.;

 public void destroy() {
        getServletContext().log("Closing WebApplicationContext for servlet '" + getServletName() + "'");
        getWebApplicationContext().close();
    }
 public void close() {
        logger.info("Closing application context [" + getDisplayName() + "]");

        // destroy all cached singletons in this context,
        // invoking DisposableBean.destroy and/or "destroy-method"
        getBeanFactory().destroySingletons();

        // publish respective event
        publishEvent(new ContextClosedEvent(this));
    }

interface21 code reference

https://github.com/peterchenhdu/interface21