preface

In order to solve the cross-domain problem, CORS method is adopted. Access-control-allow-origin = access-control-allow-methods = access-Control-allow-headers = access-control-allow-headers = access-control-allow-headers = access-control-allow-origin = access-Control-allow-methods = access-Control-allow-headers In the HandlerInterceptor#preHandle(), you need to intercept the cross-domain request (options), and then use the custom annotation to determine whether the requested controller supports the cross-domain request, and set the corresponding response header. (The project is based on Spring3.2. x), however, it is found that the request does not enter the preHandle. (There is only one custom preHandle in the project, and there is no case that it is returned by another HandlerInterceptor.) Because the cross-domain request type is Options, it cannot get the corresponding handler and HandlerInterceptor, so it is returned directly, and does not enter the interceptor. (There is a default handler after Spring4.x that supports handling options). Therefore, the knowledge learned in the debug process can be used to troubleshoot problems faster next time.

An overview of the process by which Dispathcher processes requests

component instructions
Dispatcher Responsible for receiving user requests and coordinating internal components to respond to requests
HandlerMapping Get the handler and interceptors from Request
HandlerAdapter The adapter of the processor. The variable implementation of processors in Spring, whether through implementing the Controller interface, or using the @RequestMapping annotation to treat a method as a handler, causes the calling handler to be undefined. So you need a processor adapter to unify the call logic.
ViewResolver Parse the view and return the data

Inheritance diagram for Dispathcer

As you can see from the inheritance view, the Dispatcher is an implementation class of servlets. That is, a J2EE compliant processor.

A Servlet is an interface that contains the following methods

public interface Servlet {
   
    /** * Parses the configuration file (web.xml), initializes */
    public void init(ServletConfig config) throws ServletException;

    public ServletConfig getServletConfig(a);

    /** * The business logic is implemented in this method * which is called by the Web container (e.g., Tomcat) */
    public void service(ServletRequest req, ServletResponse res)
            throws ServletException, IOException;

    public String getServletInfo(a);

    public void destroy(a);
}

Copy the code

The HttpServlet class is related to the HTTP protocol. This class focuses on how to handle HTTP requests. For example, it defines doGet to handle GET requests and doPost to handle POST requests. If we want to write a Web application based on servlets, we should inherit this class and override the specified methods. All processing of GET and POST requests is invoked by the Service method. As follows:

public abstract class HttpServlet extends GenericServlet
        implements java.io.Serializable {
  
  /** * Implements the service method of the Servlet and turns the request into an HTTP request ** / calls the internal method service(HttpServletRequest req, HttpServletResponse resp) to handle the HTTP request ** /
  public void service(ServletRequest req, ServletResponse res)
            throws ServletException, IOException {
        HttpServletRequest request;
        HttpServletResponse response;

        try {
            request = (HttpServletRequest) req;
            response = (HttpServletResponse) res;
        } catch (ClassCastException e) {
            throw new ServletException("non-HTTP request or response");
        }
        service(request, response);
    }
  
  /** * HTTP request distribution */
   protected void service(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        String method = req.getMethod();

        if (method.equals(METHOD_GET)) {
            long lastModified = getLastModified(req);
            if (lastModified == -1) {
                // servlet doesn't support if-modified-since, no reason
                // to go through further expensive logic
                doGet(req, resp);
            } else {
                long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
                    doGet(req, resp);
                } else{ resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED); }}}else if (method.equals(METHOD_HEAD)) {
            long lastModified = getLastModified(req);
            maybeSetLastModified(resp, lastModified);
            doHead(req, resp);

        } else if (method.equals(METHOD_POST)) {
            doPost(req, resp);

        } else if (method.equals(METHOD_PUT)) {
            doPut(req, resp);

        } else if (method.equals(METHOD_DELETE)) {
            doDelete(req, resp);

        } else if (method.equals(METHOD_OPTIONS)) {
            doOptions(req, resp);

        } else if(method.equals(METHOD_TRACE)) { doTrace(req, resp); }}// Other methods
}
        
Copy the code

Instead of implementing servlets directly, Dispatcher inherits HttpServlets. HTTP request processing flow:

HttpServlet.service -> FrameworkServlet.service -> FrameworkServlet.processRequest -> DispatcherServlet.doService -> DispatcherServlet.doDispatch

Dispatcher#doDispatch

The Dispatcher handles requests within the doDispatch method

// Omit the internal implementation, just look at the core
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
   //S1 gets the request's Handler and interceptors
   HandlerExecutionChain mappedHandler = getHandler(processedRequest, false);
		if (mappedHandler == null || mappedHandler.getHandler() == null) {
			noHandlerFound(processedRequest, response);
			return;
		}
  /* * interceptor #preHandle; /* * interceptor #preHandle; Until one of the interceptors returns false * or the loop ends */
  if(! mappedHandler.applyPreHandle(processedRequest, response)) {return;
	 }
  
  //S3 gets Handler for the HandlerAdapter, which calls Handler to get results
	HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
  
  //S4 executes handler#handle and returns ModelAndView
  ModelAndView mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
  
  Similarly, a for loop executes HandlerInterceptor#postHandle
  mappedHandler.applyPostHandle(processedRequest, response, mv);
  
  //S6 parses and renders the view
  processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
Copy the code

The three core steps above are:

1. Get HandlerExecutionChain, which is the handler and interceptor

2. Obtain the Adapter of the handler

3. Execute handler#handle and return the result

Let’s look at the implementation of the three steps

Get HandlerExecutionChain

//HandlerExecutionChain mappedHandler = getHandler(processedRequest, false);
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) {
         returnhandler; }}return null;
}
Copy the code

The logic is simple: the for loop matches the Request’s HandlerExecutionChain, where handlerMappings are defined as List. HandlerMapping is an interface with the following inheritance:

The getHandler method of HandlerMapping is as follows:

// Omit the internal implementation
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { 
   Object handler = getHandlerInternal(request);
   return getHandlerExecutionChain(handler, request);
}
Copy the code

1. Get handler via getHandlerInternal, which is a template method implemented by subclasses. There are two main implementations

1.1, AbstractHandlerMethodMapping# getHandlerInternal

protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
		String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
		HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
	}
Copy the code

1.2, AbstractUrlHandlerMapping# getHandlerInternal

protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
		String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
		Object handler = lookupHandler(lookupPath, request);
	}
Copy the code

The way to find a handler is to get the request URL of the request and then get the Controller based on the URL. This is how you use the @requestMapping annotation.

LookupHandler, for example

protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
		// Return a match (such as "/test" matches "/test)
		Object handler = this.handlerMap.get(urlPath);
		if(handler ! =null) {
			validateHandler(handler, request);
			return buildPathExposingHandler(handler, urlPath, urlPath, null);
		}
		// "/t*" matches both "/test" and "/team"
		List<String> matchingPatterns = new ArrayList<String>();
		for (String registeredPattern : this.handlerMap.keySet()) {
			if(getPathMatcher().match(registeredPattern, urlPath)) { matchingPatterns.add(registeredPattern); }}// Spring officially explains that the longest path is matched
		String bestPatternMatch = null;
		Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);
		if(! matchingPatterns.isEmpty()) { Collections.sort(matchingPatterns, patternComparator);if (logger.isDebugEnabled()) {
				logger.debug("Matching patterns for request [" + urlPath + "] are " + matchingPatterns);
			}
			bestPatternMatch = matchingPatterns.get(0);
		}
		if(bestPatternMatch ! =null) {
			handler = this.handlerMap.get(bestPatternMatch);
			validateHandler(handler, request);
    }
Copy the code

The core here is this.handlermap.get (urlPath), and all operations are to get data from the map. How is the map initialized?

The map is initialized using the registerHandler method, which each subclass can override to implement its own data initialization, but the final matching handler process is uniformly implemented by the parent class. The separation of data and operation is realized.

RegisterHandler is also very simple. The handler is fetched from the map based on the URL, and an error is reported if more than one handler exists (one URL cannot correspond to more than one handler).

If not, store it to handler.

2. Find the interceptor, wrap the processor and interceptor and return.

/** * The logic to get the interceptor is relatively simple and also matches the URL */
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
		HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
				(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
		chain.addInterceptors(getAdaptedInterceptors());

		String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
		for (MappedInterceptor mappedInterceptor : this.mappedInterceptors) {
			if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) { chain.addInterceptor(mappedInterceptor.getInterceptor()); }}return chain;
	}
Copy the code
public class HandlerExecutionChain {

	private static final Log logger = LogFactory.getLog(HandlerExecutionChain.class);

	private final Object handler;

	private HandlerInterceptor[] interceptors;

	private List<HandlerInterceptor> interceptorList;

	private int interceptorIndex = -1;
}
Copy the code

It’s not clear why you need both interceptors and interceptorList, which are of the same type.

Get HandlerAdapter

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

The HandlerAdapter interface is very simple to define. The support is used to check whether the handler is supported, and the handler is used to execute the handler methods.

Use of @responseBody and @requestBody

The controller input and output parameters are json, and the @responseBody and @requestBody annotations are used to convert HTTP requests to POJO objects. Message transformation is all done through HttpMessageConverter.

public interface HttpMessageConverter<T> {

 // Whether the current converter can convert the object type to HTTP packets
	boolean canWrite(Class
        clazz, MediaType mediaType);

	// HTTP media types supported by the converter
	List<MediaType> getSupportedMediaTypes(a);

	// Convert HTTP packets to a specific type
	T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
			throws IOException, HttpMessageNotReadableException;

	// Convert an object of a specific type into an HTTP message
	void write(T t, MediaType contentType, HttpOutputMessage outputMessage)
			throws IOException, HttpMessageNotWritableException;

}

Copy the code

The read method converts the HTTP request into a parameter object, and the write method converts the return value into an HTTP response packet. Spring parser HandlerMethodArgumentResolver defines the parameters and return values processor HandlerMethodReturnValueHandler unified handling.

// Parameter parser interface
public interface HandlerMethodArgumentResolver {

	// Whether the parser supports method arguments
	boolean supportsParameter(MethodParameter parameter);

	// Parses method parameters in HTTP packets
	Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception;

}

// Return value handler interface
public interface HandlerMethodReturnValueHandler {

	// Whether the processor supports return value types
	boolean supportsReturnType(MethodParameter returnType);

	// Parse the return value into an HTTP response message
	void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;

}
Copy the code

The overall processing process:

For @ ResponseBody and @ RequestBody are uniformly handled by RequestResponseBodyMethodProcessor. Namely RequestResponseBodyMethodProcessor namely HandlerMethodArgumentResolver also HandlerMethodReturnValueHandler is realized.

public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {
  // Support the RequestBody annotation parameter
  @Override
	public boolean supportsParameter(MethodParameter parameter) {
		return parameter.hasParameterAnnotation(RequestBody.class);
	}

  // Support the ResponseBody annotation return value
	@Override
	public boolean supportsReturnType(MethodParameter returnType) {
		return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
				returnType.hasMethodAnnotation(ResponseBody.class));
	}
  
  // Parse the parameters
  @Override
	protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter, Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
		Object arg = readWithMessageConverters(inputMessage, parameter, paramType);
		return arg;
	}

  // Parse the return value
	@Override
	public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
			throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException { writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage); }}Copy the code