Interface21 - Web - Dispatcher Servlet (Dispatcher Servlet initialization process)

Keywords: Java Spring xml Attribute github

Preface

Recently, I plan to spend some time to look at Spring's source code, but now Spring's source code has too many iterated versions, which are huge and seem tired, so I'm going to start with the original version (interface21), only for learning, understanding its design ideas, and slowly studying the content of each version change.

Starting from a typical web engineering example of interface21, petclinic covers the main functions of Spring, such as APO, IOC, JDBC, Web MVC, transaction, internationalization, theme switching, parameter checking, etc.

Following the previous article, after learning about the ContextLoader Listener (loading Spring Web Application Context), see how the Dispatcher Servlet initialization process, the key controller of Spring mvc, is ~~~~

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>

Here's one thing to note: the configuration of load-on-startup. For the meaning of the servlet parameter, you can first look at the explanation in the web-app_2_3.dtd file. The following basically means that if you don't configure load-on-startup or the value is negative, the servlet container is free to choose when to load the servlet (instantiate and execute the init method of the servlet), if For 0 or positive number, you must load the servlet and execute its init method at container startup. The smaller the value, the higher the priority of servlet loading. Looking back at our Dispatcher Servlet, because load-on-startup is configured as 1, the init method will be executed at startup, that is, the Dispatcher Servlet initialization process that this article focuses 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 element 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
-->

Execute the sequence diagram (if you can't see clearly, you can click on the original map)

Brief analysis of each step in the sequence diagram

The entry to be executed is the init method of the HttpServletBean class. Since the load-on-startup parameter of the Dispatcher Servlet is configured as 1, the init method of the Servlet is automatically invoked when the Servlet container (tomcat) is started.

Step description:

  1. First, the init method of the parent HttpServletBean of Dispatcher Servlet is executed.
    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 initialization parameters of the Servlet, create an instance of BeanWrapperImpl, and set the 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 the Framework Servlet.
        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 get the WebApplicationContext from the attributes of the ServletContext loaded by ContextLoaderListener
  6. Create a context Xml Web Application Context whose parent context is the Web Application Context loaded by the previous Context Loader Listener. For the relationship between these two contexts and the content responsible for loading, refer to this figure, image source ( http://jinnianshilongnian.iteye.com/blog/1602617/)
  7. Execute the waca.setServletContext method of context Xml Web Application Context to load the petclinic-servlet.xml configuration file (internationalization, theme, Handler Mapping, Handler Adapter, view parsing, etc.). Refer to the previous article on the loading process of context of Web Application Context. Spring Web Application Context Loading Process The process is the same.
  8. Set this context to the ServletContext property
  9. The initFrameworkServlet method that enters the Dispatcher Servlet class mainly performs some initialization work
        protected void initFrameworkServlet() throws ServletException {
            initLocaleResolver();
            initThemeResolver();
            initHandlerMappings();
            initHandlerAdapters();
            initViewResolver();
        }
  10. Internationalization-related: Execute the initLocaleResolver method, get the localeResolver bean from the context, and use the default AcceptHeaderLocaleResolver if not
        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. Topic-related: Execute the initThemeResolver method, get the themeResolver bean from the context, and use the default FixedThemeResolver if not
        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 initHandlerMapping method to get the HandlerMapping type bean from the context, and use the default BeanNameUrlHandlerMapping if not
        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 initHandler Adapters method to get the Handler Adapter type bean from the context, and use the default SimpleController Handler Adapter if not
        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 get the viewResolver bean from the context, and use the default InternalResourceViewResolver if not
        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 the Framework Servlet class
  16. Return to the HttpServletBean class
  17. Servlet's init method has been executed

In addition, we can pay attention to the destruction method of the Servlet. Similarly, we can also perform some operations such as resource destruction, destroy the single bean object created by the factory, publish ContextClosedEvent events, 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

Posted by Duxie on Thu, 03 Jan 2019 21:42:10 -0800