“This is the 29th day of my participation in the First Challenge 2022. For details: First Challenge 2022.”

If the background

SpringMVC acts as a presentation layer framework for REST services. Analysis of SpringMVC design ideas and source code implementation, from the abstract sense of the design level and the implementation of the code level two aspects, one by one to uncover the mysterious veil of SpringMVC.

The design and implementation of a framework for a particular domain of application must be designed to deal with many general, foundational tasks in that domain. SpringMVC, as a presentation layer framework, gives its own answer:

  • URL to framework mapping.
  • HTTP request parameter binding
  • Generation and output of HTTP responses

To compose a complete Web request flow, a framework, the first step is to understand its design idea. Look at the framework abstractly and globally. The most valuable of these is the core interface defined by the framework. The core interface defines the skeleton of the framework and expresses the design ideas of the framework in the most abstract sense.

Taking a Web request flow as the carrier, the core interfaces and classes of SpringMVC are introduced successively.

Http request tool, enter www.xxxx.com/aaa/bbb.ccc…

Look at the core interfaces appear in front of you, it is in the org. Springframework. Web. Servlet defined package HandlerMapping interface:

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;
}
Copy the code

Get the most accurate design instructions for this class or interface from the framework designer. Class, but let’s leave it at that. The key is the only method in this interface:

HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
Copy the code

It has a parameter of type HttpServletRequest, a throws Exception declaration indicating that it does not handle exceptions of any kind, and HandlerExecutionChain is its return type.

DispatcherServlet processing mechanism

When the DispatcherServlet receives a Web request, doGet or doPost is used as the standard Servlet class processing method. After several forwarding times, A List of HandlerMapping implementation classes eventually registered in the DispatcherServlet class is iterated in a loop.

Call the getHandler method in turn, taking the HttpServletRequest object of the Web request as an argument. The first call, which is not null, is returned. The traversal method in the DispatcherServlet class is not long.

/** * 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; }Copy the code

The getHandler method of the HandlerMapping interface is HttpServletRequest. The implementation class of HandlerMapping can use all the information in HttpServletRequest to make the generation “decision” of the HandlerExecutionChain object. This includes request headers, URL paths, cookies, sessions, parameters, and everything else you can get from a Web request (the most common is url paths).

This is where the first extension point for SpringMVC comes in. It is possible to write any HandlerMapping implementation class that determines the generation of a Web request to the HandlerExecutionChain object based on any policy. From the first core interface declaration, SpringMVC exposes its flexibility and ambition: I play open-closed.

The HandlerExecutionChain class, the next core class, is an object that encapsulates an execution chain.

HandlerExecutionChain class code is not long, it defined in the org. Springframework. Web. Servlet package, in order to more intuitive understanding of the code on the 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(); }}Copy the code
The basic interface to the Handler class
private final Object handler;
private HandlerInterceptor[] interceptors;
Copy the code

As we expected, one real execution object, and a bunch of interceptors. SpringMVC doesn’t shy away from this encapsulation. Once you have the HandlerExecutionChain, the next step of processing will revolve around it.

HandlerInterceptor is also the core interface of SpringMVC and 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;

}
Copy the code
  1. The entire execution sequence of the HandlerExecutionChain is now clear: Before actually calling its handler object, the array of the HandlerInterceptor interface implementation classes will be iterated, its preHandle methods will be called, and the actual handler object will be called.

  2. When the handler object is called, it generates the required response data, and its postHandle methods are called in turn before writing the processing results to the HttpServletResponse object (SpringMVC called the render view). After the view is rendered, the final afterCompletion methods are called in turn, and the entire processing of the Web request ends.

  3. Using interceptors before and after the execution of a processing object has become a classic framework design approach. So what exactly does SpringMVC’s interceptor do?

HandlerInterceptor is the second SpringMVC extension point exposed. By customizing interceptors, we can do anything we want before a request is actually processed, before the request is processed but not output to the response, and after the request is output to the response.

The HandlerExecutionChain class references the declared handler Object as Object.

The basic interface of the HandlerAdapter class

Another core interface in SpringMVC, HandlerAdapter:

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);

}
Copy the code

In DispatcherServlet, in addition to the list of HandlerMapping implementation classes, there is also a list of HandlerAdapter implementation classes registered, as shown in the code.

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

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

Next, we use the DispatcherServlet 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?" ); }Copy the code

The Handler object in the HandlerExecutionChain is passed in as a parameter, the list of HandlerAdapter implementation classes registered in the DispatcherServlet class is traversed, The supports method then returns the first HandlerAdapter object that returns true. The Handle method in this HandlerAdapter implementation class handles the handler object and returns ModelAndView, an object that contains the view and data.

HandlerAdapter is the third extension point provided by SpringMVC. You can provide your own implementation class to handle handler objects.

ModelAndView is an aggregate class for views and data in SpringMVC. Is abstracted from SpringMVC’s last core interface, View:

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; }Copy the code

It is passed as a Map object to the Render method in the View implementation class, which completes the rendering of the View to the response. The View implementation class is the return result from the Handle method in the HandlerAdapter.

ModelAndView to the real View implementation class has a resolution process, ModelAndView can have a real View object, or it can just have a View name, SpringMVC is responsible for resolving the View name to the real View object.

Summary analysis

The complete Web request is handled in SpringMVC and the core classes and interfaces involved.

In a typical SpringMVC call, the handler object encapsulated in the HandlerExecutionChain is an instance of a class identified by the @Controller annotation. According to the @RequestMapping annotation at the class level and method level, By default registered DefaultAnnotationHandlerMapping (subsequent versions RequestMappingHandlerMapping class, but for backward compatibility, DefaultAnnotationHandlerMapping can also use) generated HandlerExecutionChain object, By AnnotationMethodHandlerAdapter again (RequestMappingHandlerAdapter for subsequent version update, AnnotationMethodHandlerAdapter can also be used) to perform the HandlerExecutionChain object, to produce the final ModelAndView object, again by the render method specific View object rendering views.