From a source perspective, how does spring MVC handle HTTP requests?

Posted by ralphuk100 on Fri, 18 Feb 2022 03:29:52 +0100

Experienced spring MVC users should have a general understanding of its HTTP request processing process. Spring MVC is an extension of the native Servlet, which maps the request method and the request processing method, that is, the path specified in our common @ RequestMapping and the mapping of the method with @ RequestMapping annotation. This mapping is pre parsed and initialized into the spring container when the spring MVC container starts. They are stored in a Map. The Key can be simply understood as the request path, and the Value can be simply understood as the request processor, that is, the corresponding controller method. After the request comes, the corresponding processor method is obtained according to the request uri, and then the reflection call is made.

So what is the detailed processing flow of spring MVC? We will learn the details from the source code step by step:

The first is the initialization process. How to initialize the Controller controller and the request mapper RequestMapping during the Spring container startup process. After the initialization process is completed, the result is to register the corresponding relationship between the request mapping RequestMapping and the processor method in the request mapper registry. So how does the Spring MVC framework receive http requests when they arrive?

Let's analyze it!

First of all, before analysis, we need to have a basic knowledge reserve, that is, the execution process of Servlet. We all know that the spring MVC framework follows the Servlet specification and is extended through Servlet, so its execution process must be inseparable from the native Servlet.

In the Servlet container "usually refers to Tomcat", the service method of the Servlet will be executed every time the Http request reaches the Servlet container. What is the relationship between the service method of the Servlet and the spring MVC execution process?

Let's first take a look at the inheritance diagram of DispatcherServlet, the core processor of spring MVC, as follows:

You can see that the top-level parent class is the subclass of Servlet, which implements HttpServlet. Let's see where this service method is called?

According to the skills introduced before, you can look up from the bottom sub class one by one to see which class is called in the init method. After finding it, it is found that it is invoked in FrameworkServlet, as follows:

/**
 * Overriding the method of the parent class to increase the processing of PATCH requests
 */
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) {
  // Gets the request method type. Converts the request method type to the HttpMethod enumeration type
  HttpMethod httpMethod = HttpMethod.resolve(req.getMethod());

  // If it is a PATCH type, go directly to the processRequest method.
  // PATCH request: PUT request method is used to replace resources, while PATCH method is used to update some resources
  if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
    processRequest(req, resp);
  }
  else {
    // Call the service method of the parent HttpServlet. The parent method will judge whether the request type is get or post, so as to call the doGet or doPost methods of the subclass
    super.service(req, resp);
  }
}

The first step is to obtain the request type of HTTP request. You can understand this method a little. You can obtain the request type from a Map collection according to the method name. You can see that there are the following 8 enumeration types in this enumeration class, that is, there are 8 HTTP request methods.

public enum HttpMethod {
  GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE;
}

If it is a PATCH type request, go directly to the processRequest method. What is this PATCH request?

As we all know, in the RestFULL design specification, the PUT request is used to replace resources, while the PATCH request is used to update some resources.

Then look at super Service Method, if it is not a PATCH request and the Method exists, will execute this Method. After this Method is clicked, it will directly call the service Method of HttpServlet. In this Method, doGet, doPost or other types of methods will be called according to the request type POST or GET. As you can see, this is used to dispatch the design pattern.

In this method, if an interface requested by GET mode is called, the doGet method will be called, and this doGet method is overwritten by the subclass FrameworkServlet, so the doGet method of FrameworkServlet will be called. Let's take a look at the doGet method of FrameworkServlet, as follows:

/**
 * The exception is omitted when processing the get request
 */
@Override
protected final void doGet(HttpServletRequest req, HttpServletResponse resp) {
  // Processing get requests
  processRequest(req, resp);
}
It's called directly processRequest Method, and the processing just seen above PATCH If the method requested is the same, continue the analysis processRequest Method. The core code of this method is as follows:
/**
 * Handle specific http requests and omit exceptions
 */
protected final void processRequest(HttpServletRequest req, 
      HttpServletResponse resp) {
  // The code for handling international and asynchronous calls in the middle is omitted for the time being
  try {
    // Call the doService method to process the request
    doService(req, resp);
  }
  catch (Throwable ex) {
    failureCause = ex;
    throw new NestedServletException("Request processing failed", ex);
  }
  finally {
    // Reset ContextHolder
    ...
  }
}

You can see that the core is to call a doService method. Looking at the implementation of doService method, we found that it is an abstract method without implementation. Did you see the application of template design pattern once? We find the implementation of this method in its subclass and find that it is implemented in DispatcherServlet. The core code is as follows:

@Override
protected void doService(HttpServletRequest req, HttpServletResponse resp) {
  // Print request log
  logRequest(req);
  // Set up a bunch of parsers, that is, the nine components of Spring
  //  You can see that it is set in the request and used later. It is obtained directly from the request
  req.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
  req.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
  req.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
  req.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
  if (this.flashMapManager != null) {
    req.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
  }
  try {
    // The doDispatch method is called to process the request. Spring MVC is the core method used to process the request
    doDispatch(req, resp);
  }
  finally {
    // ellipsis
  }
}

The core code is still one line, that is, call the doDispatch method, which is the real core processing method of spring MVC. All processing logic is completed in this method, including file upload, interceptor, exception handler, etc. Let's first look at the context of the core processing request. Other specific functions can be understood slowly. The core logic code is as follows. In the following code, some codes related to checksum variable definition are deleted for clarity:

protected void doDispatch(HttpServletRequest req, HttpServletResponse resp) {
  HttpServletRequest processedRequest = req;
  // Processor execution chain
  HandlerExecutionChain mappedHandler = null;
  boolean multipartRequestParsed = false;
  try {
    try {
      // Check whether there is a binary stream in the request to determine whether the request is a file upload request.
      //  If the MultipartResolver component is not configured in the container, the file upload request is ignored directly
      processedRequest = checkMultipart(req);

      // File upload request needs to be processed
      multipartRequestParsed = (processedRequest != req);

      // Find request processor on request
      // There are usually three ways to declare a controller:
      // (1)@Controller 
      // (2) Implement the HttpRequestHandler interface and override the handleRequest method,
            take controller Class pass@Component Label as Bean
      // (3) Configure through xml
      mappedHandler = getHandler(processedRequest);

      // If the corresponding handler is not found, 404 is returned directly
      if (mappedHandler == null) {
        noHandlerFound(processedRequest, resp);
        return;
      }
      // Find the adapter corresponding to the current request processor. The RequestMappingHandlerAdaptor is usually used. The other two adapters are not commonly used
      HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
      // Before processing the request, execute the method configured in the interceptor, obtain all configured interceptors, cycle through and execute in turn
      //  If the interceptor returns false during execution, it will return directly and the target method will not be executed again
      if (!mappedHandler.applyPreHandle(processedRequest, resp)) {
        return;
      }
      // Execute the real corresponding business methods in spring MVC
      //   The implementation subclasses of HandlerAdapter include AbstractHandlerMethodAdapter and HttpRequestHandlerAdapter
      mv = ha.handle(processedRequest, resp, mappedHandler.getHandler());
      // Find the view path prefix + uri + suffix
      applyDefaultViewName(processedRequest, mv);
      // After the request interceptor executes the processing method
      mappedHandler.applyPostHandle(processedRequest, resp, mv);
    }
    catch (Exception ex) {
      dispatchException = ex;
    }
    // Render the processed results in page view. For example: jump to Jsp page
    processDispatchResult(processedRequest, resp, mappedHandler, mv, dispatchException);
  }
  catch (Exception ex) {
    // If an exception occurs during page rendering, the afterCompletion method will be executed directly
    triggerAfterCompletion(processedRequest, resp, mappedHandler, ex);
  }
  finally {
    // Some aftercare work, such as cleaning up the temporary files left by file upload, etc
  }
}

After the above method is executed, one request of spring MVC is processed. Due to space reasons, the specific file upload judgment, view rendering return, exception handling and interceptor execution logic will be introduced in the following articles.

The annotated source code has been uploaded to github at: https://github.com/wb02125055...

You can fork yourself. If you have any questions, leave a message below!

Topics: Java Spring Spring Boot