Spring MVC Source Analysis 3: Initialization and Request Forwarding of Dispatcher Servlet

When we first learned Servlet programming and java web, there were not so many frameworks. The simple thing we need to do to develop a simple function is to inherit the HttpServlet, rewrite the doGet, doPost method as needed, and jump to our defined jsp page. After the Servlet class is written, the Servlet class is registered in web.xml.

There is nothing else besides that. We start the web server, enter the address in the browser, you can see the browser output page we have written. To better understand this process, you need to learn about the three stages of the Servlet life cycle, called "init-service-destroy".

The above knowledge, I think, is enough for you to understand the design idea of Spring MVC. Spring MVC can certainly be described as a complex framework, but at the same time it follows the simplest rule in the Servlet world, which is "init-service-destroy". We want to analyze the initialization process of Spring MVC, which is in fact the init() method of Dispatcher Servlet class. Let's take this simple point of view and open the source code of Dispatcher Servlet for a glimpse.

Configuration element reading

Open the source code of the Dispatcher Servlet class with Eclipse IDE, and take a look at ctrl+T.

The initialization entry method init() of the Dispatcher Servlet class is defined in the parent class HttpServletBean. As a class directly inherited from the HttpServlet class, the HttpServletBean class overrides the init() method of the HttpServlet class and implements its initialization behavior.

@Override
    public final void init() throws ServletException {
        if (logger.isDebugEnabled()) {
            logger.debug("Initializing servlet '" + getServletName() + "'");
        }

        // Set bean properties from init parameters.
        try {
            PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
            ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
            bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.environment));
            initBeanWrapper(bw);
            bw.setPropertyValues(pvs, true);
        }
        catch (BeansException ex) {
            logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
            throw ex;
        }

        // Let subclasses do whatever initialization they like.
        initServletBean();

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

The initServletBean() method here is an empty method without any implementation in the HttpServletBean class. Its purpose is to leave the subclasses to implement their own initialization logic, which is what we often call the template method design pattern. Spring MVC uses this pattern vividly. The init() method is the template method in the template method pattern. The real initialization process of Spring MVC is triggered by the initServletBean() method overwritten in the subclass Framework Servlet.

Look again at the code wrapped in try,catch blocks in the init() method, which involves BeanWrapper, PropertyValues, ResourceEditor, which are very low-level classes within Spring. To delve into the details of specific code implementation above, you need to have a fairly in-depth understanding of Spring framework source code. Let's first avoid the complexity and simplify it. We will analyze what the try and catch code does from the aspects of code effect and design idea.

  • Register a string to the resource file editor so that the configuration elements under the Servlet can specify the source of the Spring MVC framework bean configuration file in the form of "classpath:".
  • The configuration elements in web.xml under the Dispatcher Servlet are read into the Dispatcher Servlet using JavaBean (that is, through the setter method).

I would like to illustrate these two points with the following example.

The Dispatcher Servlet configuration I registered in web.xml is as follows:

<!-- springMVC Configuration Start -->
    <servlet>
        <servlet-name>appServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring/spring-servlet.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>appServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    <!-- springMVC Configuration End -->

As you can see, I registered an element named contextConfigLocation with the value of "classpath:spring/spring-servlet.xml", which is often used to specify the path of Spring MVC configuration files. The above try,catch block wrapped Code plays a role in converting the string "classpath:spring/spring-servlet.xml" into a resource file under the classpath path path path for the framework to initialize the read configuration elements. In my project, the configuration file spring-servlet.xml is under the spring folder.

Another function is to read out the value of contextConfigLocation and set it to Dispatcher Servlet through the setContextConfigLocation() method, which is defined in the Framework Servlet class, which is the direct parent of Dispatcher Servlet in the inheritance class diagram above.

We hit a breakpoint on the setContextConfigLocation() method to start the web project, and you can see the following debugging results.

The author of the HttpServletBean class is Rod Johnson, the father of Spring. As a master of POJO programming philosophy, he used the idea of dependency injection to read configuration elements in the design of HttpServletBean class. The purpose of his extraction of the class HttpServletBean is to "read the configuration information of the Servlet class in the way of dependency injection", and this is obviously a setter injection.

Having understood the design philosophy of the HttpServletBean class, we also know how to benefit from it. Specifically, we inherit the HttpServletBean class (as Dispatcher Servlet does), define an attribute in the class, add a setter method to the attribute, and we can define values in the element. After the class is initialized, the value is injected. We can use it directly, avoiding the use of the template getInitParameter() method, and enjoying the function of resource editor in Spring free of charge. We can directly specify the resource file under the class path in web.xml through "classpath:".

Note that although Spring MVC itself uses strings to declare and set contextConfigLocation parameters for the convenience of later initialization contexts, it can also be successfully obtained by declaring it as a Resource type. Readers are encouraged to inherit HttpServletBean and write a Servlet class for testing, and set a parameter to debug it. This will help you better understand the process of obtaining configuration parameters.

Establishment of Container Context

As mentioned in the previous article, Spring MVC uses the Spring container to accommodate its own configuration elements and has its own bean container context. In the process of Spring MVC initialization, the key step is to establish the container context, which occurs in the Framework Servlet class and is triggered by the initServletBean() method in the init() method above.

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

        try {
            this.webApplicationContext = initWebApplicationContext();
            initFrameworkServlet();
        }
        catch (ServletException ex) {
            this.logger.error("Context initialization failed", ex);
            throw ex;
        }
        catch (RuntimeException ex) {
            this.logger.error("Context initialization failed", ex);
            throw ex;
        }

        if (this.logger.isInfoEnabled()) {
            long elapsedTime = System.currentTimeMillis() - startTime;
            this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
                    elapsedTime + " ms");
        }
    }

The initFramework Servlet () method is an empty method without any implementation, except for some boilerplate code, so what this initServletBean() method does is very clear:

this.webApplicationContext = initWebApplicationContext();

This simple and straightforward code breaks through the Framework Servlet class, which is designed for the purpose of extracting the process of creating a Web Application Context in the Spring MVC class system.

The initWeb Application Context () method encapsulates the entire process of establishing the Spring container context. The logic in the method is as follows:

  1. Gets the root context initialized by ContextLoaderListener and registered in ServletContext, which is recorded as rootContext
  2. If the webApplication Context is no longer empty, it means that the Servlet class is programmatically registered in the container (ServletContext.addServlet() in Servlet 3.0+), and the context is programmatically passed in. If the incoming context has not been initialized, set the rootContext context to its parent context and initialize it, otherwise it will be used directly.
  3. By referring to the WAC variable as null, we can determine whether the setting of the context has been completed in step 2 (that is, whether the context has been programmatically passed in). If wac==null is established, the Servlet is not programmatically registered in the container. At this time, with the value of contextAttribute attribute as the key, the context is found in ServletContext, which indicates that the context has been initialized in other ways and registered under contextAttribute for direct use.
  4. Check whether the reference to the WAC variable is null. If wac==null holds, the context initialization strategy in steps 2 and 3 is unsuccessful. Call createWeb Application Context (rootContext) to create a new container context with rootContext as the parent context for Spring MVC configuration elements. In most cases, the context we use is the new context.
  5. The three strategies mentioned above will call back the onRefresh (Application Context context) method, which is overridden in the Dispatcher Servlet class. Based on the above context, the default implementation of class initialization in Spring MVC is completed.
  6. Finally, publish this context to ServletContext, that is, set the context as an attribute of ServletContext with a key related to the registered name of the Servlet class in web.xml. You can decide whether to publish to ServletContext by changing the value of publishContext, which defaults to true.

By tracking the code in the Framework Servlet class at the above six points, we can clearly understand the process of setting up the entire container context, and understand the design purpose of the Framework Servlet class, which is used to establish a Spring container context associated with the Servlet and register it in the ServletContext. Leaving the Spring MVC system aside, we can also get the benefits of integration with the Spring container by inheriting the Framework Servlet class, which, like HttpServletBean, is a class that can be used independently. In the whole Spring MVC design, the principle of opening and closing is embodied everywhere, and this is obviously one of them.

Initialize Spring MVC default implementation class

The initialization process flows through the Framework Servlet class, establishes the context, and then enters the Dispatcher Servlet class through the callback of the onRefresh (Application Context context) method.

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

The Dispatcher Servlet class overrides the onRefresh (Application Context context) method in the parent Framework Servlet, providing initialization of various programming elements of Spring MVC. Of course, these programming elements exist as bean s in the container context. The specific initialization strategy is encapsulated in the initStrategies() method.

protected void initStrategies(ApplicationContext context) {
        initMultipartResolver(context);
        initLocaleResolver(context);
        initThemeResolver(context);
        initHandlerMappings(context);
        initHandlerAdapters(context);
        initHandlerExceptionResolvers(context);
        initRequestToViewNameTranslator(context);
        initViewResolvers(context);
        initFlashMapManager(context);
    }

Taking the initHandler Mappings (context) method as an example, we analyze the initialization strategies of these Spring MVC programming elements. The other methods are initialized with similar strategies.

private void initHandlerMappings(ApplicationContext context) {
        this.handlerMappings = null;

        if (this.detectAllHandlerMappings) {
            // Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
            Map<String, HandlerMapping> matchingBeans =
                    BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
            if (!matchingBeans.isEmpty()) {
                this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
                // We keep HandlerMappings in sorted order.
                OrderComparator.sort(this.handlerMappings);
            }
        }
        else {
            try {
                HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
                this.handlerMappings = Collections.singletonList(hm);
            }
            catch (NoSuchBeanDefinitionException ex) {
                // Ignore, we'll add a default HandlerMapping later.
            }
        }

        // Ensure we have at least one HandlerMapping, by registering
        // a default HandlerMapping if no other mappings are found.
        if (this.handlerMappings == null) {
            this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
            if (logger.isDebugEnabled()) {
                logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
            }
        }
    }

The detectAllHandlerMappings variable defaults to true, so when initializing the default implementation class of the HandlerMapping interface, all HandlerMapping type beans in the context are registered in the handlerMappings List variable. If you manually set it to false, you will try to get a Bean named handler Mapping, create a new List with only one element, and assign it to handler Mappings. If the handler Mappings variable is still empty after the above process, you have not provided your own handler Mapping type Bean definition in the context. At this point, Spring MVC will initialize handler Mappings using the default initialization strategy.

Point in getDefault Strategies and take a look.

@SuppressWarnings("unchecked")
    protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
        String key = strategyInterface.getName();
        String value = defaultStrategies.getProperty(key);
        if (value != null) {
            String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
            List<T> strategies = new ArrayList<T>(classNames.length);
            for (String className : classNames) {
                try {
                    Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
                    Object strategy = createDefaultStrategy(context, clazz);
                    strategies.add((T) strategy);
                }
                catch (ClassNotFoundException ex) {
                    throw new BeanInitializationException(
                            "Could not find DispatcherServlet's default strategy class [" + className +
                                    "] for interface [" + key + "]", ex);
                }
                catch (LinkageError err) {
                    throw new BeanInitializationException(
                            "Error loading DispatcherServlet's default strategy class [" + className +
                                    "] for interface [" + key + "]: problem with class file or dependent class", err);
                }
            }
            return strategies;
        }
        else {
            return new LinkedList<T>();
        }
    }

It is a paradigm approach that assumes the default initialization strategy for all Spring MVC programming elements. The content of the method is more straightforward, which is to pass the name of the class as the key, get the implementation class from the defaultStrategies property variable, and then reflect the initialization.

It should be noted that the initialization of defaultStrategies variable is loaded in the static initialization code block of Dispatcher Servlet.

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, DispatcherServlet.class);
            defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
        }
        catch (IOException ex) {
            throw new IllegalStateException("Could not load 'DispatcherServlet.properties': " + ex.getMessage());
        }
    }
private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";

In this Dispatcher Servlet. properties, the default implementation class of Spring MVC is recorded as a key-value pair in the spring-webmvc-3.1.3.RELEASE.jar jar package and in the org.springframework.web.servlet package.

# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
    org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
    org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
    org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter

org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
    org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
    org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

So far, we have analyzed the execution process of the initHandler Mappings (context) method, and other initialization processes are very similar to this method. After all the initialization methods are executed, Spring MVC formally completes the initialization and waits quietly for the arrival of Web requests.

summary

Looking back at the initialization process of Spring MVC, we can see that through three different class levels: HttpServletBean, Framework Servlet and Dispatcher Servlet, the designers of Spring MVC abstract three different responsibilities separately and fix the design pattern in three class levels by template method. Among them, HttpServletBean completes the dependency injection of configuration elements, Framework Servlet completes the establishment of container context, Dispatcher Servlet completes the initialization strategy of specific programming elements of Spring MVC.

Tags: Java Spring xml Programming Attribute

Posted on Sun, 25 Aug 2019 02:56:32 -0700 by donnierivera