Spring MVC Source Analysis 2: Spring MVC Design Concept and Dispatcher Servlet

Posted by duncanmaclean on Sun, 25 Aug 2019 11:58:56 +0200

From: https://my.oschina.net/lichhao/blog

Introduction to Spring MVC

Spring MVC is becoming more and more popular as an emerging presentation layer framework after Struts 2. It is believed that javaee developers should have heard a little about Spring MVC even if they have not used it. I try to unveil the mystery of Spring MVC one by one through the analysis of Spring MVC's design idea and source code implementation. The code in this paper is based on Spring's 3.1.3 RELEASE version.

Any framework has its own specific application areas. The design and implementation of the framework must be to cope with a lot of common, tedious and basic work in this field. As a framework of presentation layer, Spring MVC must also face several major topics of presentation layer in the field of Web development and give its own answers.

  • Mapping URL s to frameworks.
  • http request parameter binding
  • Generation and output of http response

These three topics constitute a complete web request process, each part has a very broad extension. What is the Spring MVC framework's answer to these questions?

To learn a framework, the first thing is to understand its design ideas. Look at the framework abstractly and globally. The most valuable reference is the core interface defined by this framework. The core interface defines the framework and expresses the design idea of the framework in the most abstract sense.

Next, I introduce the core interfaces and classes of Spring MVC in turn, using a web request process as the carrier.

The user enters the address of http://www.xxxx.com/aaa/bbb.ccc in the browser. After returning, the browser initiates an HTTP request. When the request arrives at your server, it will first be received by Dispatcher Servlet, the front-end transponder registered by Spring MVC in web.xml. Dispatcher Servlet is a standard servlet whose function is to accept and forward web requests to the internal framework processing unit.

Handler Mapping interface

Let's take a look at the first core interface that appears in front of you. It's the Handler Mapping interface defined in the org. spring framework. web. servlet package:

package org.springframework.web.servlet;

import javax.servlet.http.HttpServletRequest;

public interface HandlerMapping {

    String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping";

    String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern";

    String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping";

    String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";

    String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";

    HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;

}

For ease of reading, I removed the comments from the source code, but I strongly recommend that you remember to read it, so that you can get the most accurate design instructions for this class or interface from the framework designer. Some constants defined in a class, let's leave it alone. The key is the only way in this interface:

HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;

This method is easy to understand even for a java beginner: it has only one parameter of type HttpServletRequest, the throws Exception declaration indicates that it does not handle any type of exception, and Handler Execution Chain is its return type.

Dispatcher Servlet accepts the request and finds the corresponding Handler

Back to the Dispatcher Servlet process, when the Dispatcher Servlet receives a web request, a List (a bit awkward) composed of a handler Mapping implementation class registered in the Dispatcher Servlet class will be traversed in a loop after several forwards by the standard Servlet class processing method doGet or doPost. With the HTTP ServletRequest object of the web request as the parameter, the getHandler method is called in turn. The first call result, which is not null, will be returned. This traversal method in the Dispatcher Servlet class is not long. Paste it to let you have a more intuitive understanding.

/**
     * Return the HandlerExecutionChain for this request.
     * <p>Tries all handler mappings in order.
     * @param request current HTTP request
     * @return the HandlerExecutionChain, or <code>null</code> if no handler could be found
     */
    protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        for (HandlerMapping hm : this.handlerMappings) {
            if (logger.isTraceEnabled()) {
                logger.trace(
                        "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
            }
            HandlerExecutionChain handler = hm.getHandler(request);
            if (handler != null) {
                return handler;
            }
        }
        return null;
    }

Yes, the first step is so simple. After a web request is processed, a Handler Execution Chain object is obtained, which is the answer given by Spring MVC to the URL mapping. It should be noted that the getHandler method parameter of the Handler Mapping interface is HttpServletRequest, which means that the implementation class of Handler Mapping can use all the information in the HttpServletRequest to make a "decision" for the generation of the Handler ExecutionChain object. This includes request headers, URL paths, cookie s, session s, parameters, and everything else you can get from a web request (most commonly URL paths).

The first extension point of SpirngMVC is here. We can write arbitrary implementation classes of Handler Mapping to determine the generation of a web request to the Handler Execution Chain object based on any strategy. It can be said that from the declaration of the first core interface, Spring MVC has exposed its flexibility and ambition: Brother is playing with Open-Closed.

Handler Execution Chain is the next core class we need to understand. From the name, it can be seen intuitively that this object is an encapsulation of the execution chain. As everyone familiar with Struts 2 knows, Action objects are also packaged by layer-by-layer interceptors. Here we can make an analogy to show that Spring MVC absorbs some of the design ideas of Struts 2.

The code for the HandlerExecutionChain class is not long. It is defined in the org. spring framework. web. servlet package. For a more intuitive understanding, code first.

package org.springframework.web.servlet;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.springframework.util.CollectionUtils;

public class HandlerExecutionChain {

    private final Object handler;

    private HandlerInterceptor[] interceptors;

    private List<HandlerInterceptor> interceptorList;

    public HandlerExecutionChain(Object handler) {
        this(handler, null);
    }

    public HandlerExecutionChain(Object handler, HandlerInterceptor[] interceptors) {
        if (handler instanceof HandlerExecutionChain) {
            HandlerExecutionChain originalChain = (HandlerExecutionChain) handler;
            this.handler = originalChain.getHandler();
            this.interceptorList = new ArrayList<HandlerInterceptor>();
            CollectionUtils.mergeArrayIntoCollection(originalChain.getInterceptors(), this.interceptorList);
            CollectionUtils.mergeArrayIntoCollection(interceptors, this.interceptorList);
        }
        else {
            this.handler = handler;
            this.interceptors = interceptors;
        }
    }

    public Object getHandler() {
        return this.handler;
    }

    public void addInterceptor(HandlerInterceptor interceptor) {
        initInterceptorList();
        this.interceptorList.add(interceptor);
    }

    public void addInterceptors(HandlerInterceptor[] interceptors) {
        if (interceptors != null) {
            initInterceptorList();
            this.interceptorList.addAll(Arrays.asList(interceptors));
        }
    }

    private void initInterceptorList() {
        if (this.interceptorList == null) {
            this.interceptorList = new ArrayList<HandlerInterceptor>();
        }
        if (this.interceptors != null) {
            this.interceptorList.addAll(Arrays.asList(this.interceptors));
            this.interceptors = null;
        }
    }

    public HandlerInterceptor[] getInterceptors() {
        if (this.interceptors == null && this.interceptorList != null) {
            this.interceptors = this.interceptorList.toArray(new HandlerInterceptor[this.interceptorList.size()]);
        }
        return this.interceptors;
    }

    @Override
    public String toString() {
        if (this.handler == null) {
            return "HandlerExecutionChain with no handler";
        }
        StringBuilder sb = new StringBuilder();
        sb.append("HandlerExecutionChain with handler [").append(this.handler).append("]");
        if (!CollectionUtils.isEmpty(this.interceptorList)) {
            sb.append(" and ").append(this.interceptorList.size()).append(" interceptor");
            if (this.interceptorList.size() > 1) {
                sb.append("s");
            }
        }
        return sb.toString();
    }

}

It's a mess. I'm sure you haven't seen it all, and you don't have to. In fact, it only needs two lines.

private final Object handler;

    private HandlerInterceptor[] interceptors;

As we expected, there was a physical execution object and a bunch of interceptors. Isn't that the implementation of Struts 2? Spring MVC has not avoided suspicion, but still uses this kind of encapsulation. Once you get the execution chain of Handler Execution Chain, the next step will be around it.

Handler Interceptor Interface

Handler Interceptor is also the core interface of Spring MVC, which is defined as follows:

package org.springframework.web.servlet;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public interface HandlerInterceptor {

    boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
        throws Exception;

    void postHandle(
            HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
            throws Exception;

    void afterCompletion(
            HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception;

}

At this point, the whole execution context of Handler Execution Chain is clear: before calling its handler object, the array of handler Interceptor interface implementation classes will be traversed, its preHandle method will be invoked in turn, and then the real handler object will be invoked.

After the handler object is invoked, the required response data is generated, and its postHandle method is invoked in turn before the processing result is written to the HttpServletResponse object (Spring MVC is called Rendering View). After rendering the view, the afterCompletion method will be invoked sequentially, and the whole process of processing web requests will end.

It has become a classic framework design routine to use interceptors before an object is executed. Interceptors in Struts 2 do complex tasks such as parameter binding, so what exactly do Spring MVC interceptors do? We don't care for the moment. Although it's an important detail, it's a detail after all. Let's understand something more important first.

Handler Interceptor, the second extension point of Spring MVC, is exposed. Through a custom interceptor, we can do whatever we want at three points: before a request is actually processed, before the request is processed but not yet output to the response, and after the request has been output to the response. The success of Struts 2 framework is due to the design of this interceptor. Spring MVC absorbs this design idea and divides three different time points more reasonably, thus providing greater extensibility for the process of web request processing.

What exactly is the handler object declared in this HandlerExecutionChain class that is referenced by Object? How was it called?

HandlerAdapter

Before answering these questions, look at another core interface in Spring MVC, Handler Adapter:

package org.springframework.web.servlet;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public interface HandlerAdapter {

    boolean supports(Object handler); 

    ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;

    long getLastModified(HttpServletRequest request, Object handler);

}

In Dispatcher Servlet, in addition to the list of Handler Mapping implementation classes, a list of Handler Adapter implementation classes is also registered, as evidenced by the code.

/** List of HandlerMappings used by this servlet */
    private List<HandlerMapping> handlerMappings;

    /** List of HandlerAdapters used by this servlet */
    private List<HandlerAdapter> handlerAdapters;

Next, let's answer the above question with another piece of code in the Dispatcher Servlet class:

/**
     * Return the HandlerAdapter for this handler object.
     * @param handler the handler object to find an adapter for
     * @throws ServletException if no HandlerAdapter can be found for the handler. This is a fatal error.
     */
    protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
        for (HandlerAdapter ha : this.handlerAdapters) {
            if (logger.isTraceEnabled()) {
                logger.trace("Testing handler adapter [" + ha + "]");
            }
            if (ha.supports(handler)) {
                return ha;
            }
        }
        throw new ServletException("No adapter for handler [" + handler +
                "]: Does your handler implement a supported interface like Controller?");
    }

Summary of Request Process

This code is already clear. Handler objects in Handler Execution Chain are passed in as parameters, the list of implementation classes registered in the Dispatcher Servlet class is traversed, and then the first support method is returned to the true Handler Adapter object. Handler Adapter is used to implement handl in the class. The e method handles the handler object and returns ModelAndView, which contains views and data. Handler Adapter is the third extension point provided by Spring MVC. You can provide your own implementation class to handle handler objects.

The code for the Model AndView object is not pasted. It is an aggregate class of views and data in Spring MVC. The view is abstracted from the last core interface View of Spring MVC:

package org.springframework.web.servlet;

import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public interface View {

    String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + ".responseStatus";

    String PATH_VARIABLES = View.class.getName() + ".pathVariables";

    String getContentType();

    void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception;

}

All data is finally passed as a Map object to the render method in the View implementation class, and the render method is invoked to render the view to the response. This View implementation class is the return result from the handle method in Handler Adapter. Of course, there is a process of parsing from ModelAndView to the real View implementation class. ModelAndView can have a real view object or just a view name. Spring MVC is responsible for parsing the view name into a real view object.

So far, we have learned about a typical complete web request processing process in Spring MVC and the core classes and interfaces involved.

In a typical Spring MVC call, encapsulating handler objects in Handler Execution Chain is an instance of a class identified by the @Controller annotation. According to the @RequestMapping annotation at the class level and method level, the default registered Default Annotation Handler Mapping (updated to RequestMapping Handler Mapping class in 3.1.3) But for backward compatibility, Default Annotation Handler Mapping can also be used) to generate Handler Execution Chain objects, which are then executed by Annotation MethodHandler Adapter (updated to RequestMapping Handler Adapter class in 3.1.3), but Annotation MethodHandler Adapter can also be used for backward compatibility. ExecutionChain object, generates the final ModelAndView object, and then renders the view by the render method of the specific View object.

As a presentation layer framework, Spring MVC is not as radical as Struts 2, and does not adopt the design idea of completely decoupling from the Web container. Instead, it relies on the original Servlet framework object and formulates a rigorous process through reasonable abstraction. As a result, execution efficiency is higher than Struts 2, and flexibility has increased by a level.

Topics: Java Spring Struts JavaEE