I don't understand. You call the most complete spring dispatcher request process analysis in the whole network. If you don't read it, you'll lose money

servlet

Before understanding the process of dispatcher servlet, we should first understand the technology of servlet.

In a narrow sense, servlet is just an interface. In a broad sense, it is a specification.

In traditional Java Web projects, the doGet and doPost methods are rewritten by inheriting HttpServlet (realizing the service interface), and then identified in web.xml. These servlets are managed through tomcat as a servlet container.

tomcat does not consider io model communication,   There are only two main functions.

  • Encapsulate request and response objects
  • Call the service method of the servlet

This is the key to understanding the dispatcher process. First of all, you should know what tomcat does

We can take a look at the source code of the service method of HttpServlet

protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String method = req.getMethod();
        long lastModified;
        if (method.equals("GET")) {
            lastModified = this.getLastModified(req);
        } else if (method.equals("HEAD")) {
            lastModified = this.getLastModified(req);
            this.maybeSetLastModified(resp, lastModified);
            this.doHead(req, resp);
        } else if (method.equals("POST")) {
            this.doPost(req, resp);
        } else if (method.equals("PUT")) {
            this.doPut(req, resp);
        } else if (method.equals("DELETE")) {
            this.doDelete(req, resp);
        } else if (method.equals("OPTIONS")) {
            this.doOptions(req, resp);
        } else if (method.equals("TRACE")) {
            this.doTrace(req, resp);
        } else {
            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[]{method};
            errMsg = MessageFormat.format(errMsg, errArgs);
            resp.sendError(501, errMsg);
        }

    }
Copy code

In other words, in the traditional servlet project, tomcat encapsulates the request, calls the servlet of the corresponding path according to the url, executes the service method, and then judges the request type to call doGet(), doPost(), etc.

Relationship between dispatcher servlet and Servlet

dispatcherServlet dependency is very complex. In short, it indirectly implements the servlet interface. Let's first look at the methods of the servlet interface

public interface Servlet {
    void init(ServletConfig var1) throws ServletException;

    ServletConfig getServletConfig();

    void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

    String getServletInfo();

    void destroy();
}
Copy code

Let's focus on init and service methods later.

For spring MVC, the dispatcher servlet needs to be configured in web.xml to be managed by tomcat. For spring projects, tomcat only needs to manage spring's own servlet. We don't need to write servlets. We use bean s to let the spring container manage them. The spring container and tomcat container here are not the same concept.

Spring boot is based on automatic configuration and does not need to be configured manually.

According to the above, tomcat will call the service method of spring's dispatcherServlet to handle requests under any path. Due to the complex dependency, the service method is in the parent class of the dispatcher. In conclusion,   Finally, the doDispatch method of dispatch will be called  , There will be some process in the middle, which is ignored here.

technological process

The ugly face Sutra will only throw you a picture

Hard memorizing this eight part essay, completely unaware of his concept and implementation details, is not good for our thoughts. So analyze his source code.

initialization

First, initialize the spring container and refresh the container before interaction. For spring MVC, tomcat will first execute the init method of dispatcher (actually the init method of HttpServletBean) to refresh the container, and the bottom layer is the refresh method called. For springboot, the run method of the main method will be executed, and a refresh method will be executed to refresh the container. Although the init method will still be executed, the refresh method inside should not be executed again. (although I don't know why spring boot can not be executed, according to the interruption point, it does not execute.). In short, we have the same goal.

Request process

I'll go through the previous steps again. After the request comes, tomcat encapsulates the request and response objects, and then calls the service method, that is, the doDispatch method of dispatchServlet. Look at the source code directly

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;
		boolean multipartRequestParsed = false;

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

		try {
			ModelAndView mv = null;
			Exception dispatchException = null;

			try {
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

				// Determine handler for the current request.
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				// Determine handler adapter for the current request.
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				// Process last-modified header, if supported by the handler.
				String method = request.getMethod();
				boolean isGet = "GET".equals(method);
				if (isGet || "HEAD".equals(method)) {
					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
						return;
					}
				}

				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				// Actually invoke the handler.
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}

				applyDefaultViewName(processedRequest, mv);
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			}
			catch (Exception ex) {
				dispatchException = ex;
			}
			catch (Throwable err) {
				// As of 4.3, we're processing Errors thrown from handler methods as well,
				// making them available for @ExceptionHandler methods and other scenarios.
				dispatchException = new NestedServletException("Handler dispatch failed", err);
			}
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}
		catch (Exception ex) {
			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		}
		catch (Throwable err) {
			triggerAfterCompletion(processedRequest, response, mappedHandler,
					new NestedServletException("Handler processing failed", err));
		}
		finally {
			if (asyncManager.isConcurrentHandlingStarted()) {
				// Instead of postHandle and afterCompletion
				if (mappedHandler != null) {
					mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
				}
			}
			else {
				// Clean up any resources used by a multipart request.
				if (multipartRequestParsed) {
					cleanupMultipart(processedRequest);
				}
			}
		}
	}
Copy code

In fact, there are only a few important methods to compare the pictures of ugly faces.

//Get handlerchain from Handler mapper
mappedHandler = getHandler(processedRequest);
//Get adapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
//Execute handler to get ModelAndView
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
//Judge whether the view in mv is empty
applyDefaultViewName(processedRequest, mv);
//View resolution
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
Copy code

I think this should be the easiest to understand source code in spring. Let's analyze it one by one.

Mapper

The first is the getHandler () method

protected HandlerExecutionChain getHandler(HttpServletRequest request) {
        //this.handlerMappings is a HandlerMapping array. After the Spring container is started, all defined mapper objects will be stored in this array
		if (this.handlerMappings != null) {
			for (HandlerMapping mapping : this.handlerMappings) {
				HandlerExecutionChain handler = mapping.getHandler(request);
				if (handler != null) {
					return handler;
				}
			}
		}
		return null;
	}
Copy code

This is very simple to understand. Because there are many kinds of handlers, there are many kinds of mapping. You need to find the correct mapping

  • RequestMappingUrlHandlerMapping is the most common handler mapping identified by annotations
  • BeanNameUrlHandlerMapping
  • SimpleUrlHandlerMapping
  • ControllerClassNameHandlerMapping

The first one is the most common. It uses the @ requestMapping annotation to identify the handler mapping. Others inherit the controller and httpservlet. I won't say more.

Through the loop, until the correct processor mapper is found, the handle is actually a HandlerExecutionChain, which is actually composed of interceptor array + handler.

Adapter

After obtaining the processor chain in the previous step, obtain the adapter through the getHandlerAdapter method, and the parameter is handler (the interceptor array is not used yet)

HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
Copy code

The famous one is used here   Adapter mode   Yes

If you do not use the adapter, when you add a handle type in the future, because the logic between different handles is completely different, you need to judge the handle type by using if else in the dispatch, and then perform the operation.

protected HandlerAdapter getHandlerAdapter(Object handler) {
		if (this.handlerAdapters != null) {
			for (HandlerAdapter adapter : this.handlerAdapters) {
				if (adapter.supports(handler)) {
					return adapter;
				}
			}
		};
	}
Copy code

this.handlerAdapters here is to get the adapter array, which is a bit like the above. Get the appropriate adapter return.

The adapter executes the handle method

The adapter executes using the handle method

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
Copy code

Finally, a model view is obtained. In the ancient jsp era, data was transferred and jsp views were parsed through modelandview. However, since the front and back ends are separated now, only json strings are transmitted using the @ ResponseBody annotation, so the modelandview here is empty.

So we won't pay attention to the following view parsing part

Interceptor

So what is the function of interceptors in handerchain.

In fact, the handle method is executed in the adapter   around   I will process an interceptor, but I didn't write it.

last

I won't talk about the handling of tomcat

Summary

The process of spring MVC is very simple if you don't dig deeply. Basically, anyone with development experience can roughly understand the process. But in fact, I omitted a part above, which is the function of the adapter   handle   Method and mapper   getHandler   method. This is difficult to understand, so I only introduce the separation of front and back ends and common annotations. I try to explain the source code from the top to the bottom.

Source code analysis of mapper

Simplified edition of Mianjing

  1. During ioc initialization, the mapper will also be initialized, and the mapping correspondence will be placed in a map
  2. Mapper   getHandler   Method to obtain the corresponding handler from the map through the suffix of the url

Detailed Edition

Let's talk about the inheritance relationship of HandlerMapping

In fact, the most commonly used is
RequestMappingHandlerMapping  , It corresponds to the handle mapper of @ Controller @RequestMapping. I will only introduce this mapper here.

Let's look directly at the request process

First, loop the mapper list in the dispatcher and call the getHandler method (written above). In fact, it calls the getHandler method of AbstractHandlerMapping

AbstractHandlerMapping

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		//Omitted version
		Object handler = getHandlerInternal(request);
		HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
		return executionChain;
	}
Copy code

This getHandlerInternal() returns the handle we need. Click it and it will be called
AbstractHandlerMethodMapping   Method of

getHandlerInternal()

protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    //Get the url, such as / user/login
    String lookupPath = initLookupPath(request);
    //Get HandlerMethod, core method
    HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
    return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
Copy code

Let's look at the return value first. A   HandlerMethod  , This is our processor. As mentioned earlier, spring MVC has a variety of handlers. The processing logic of each handler is different. The handlers obtained through different mappers are different. Later, they need to be uniformly standardized and executed through adapters. We'll talk about this later, anyway   HandlerMethod   This object is very important.

HandlerMethod class

Take a look at the construction of the HandlerMethod class

//Omitted a lot
public class HandlerMethod {

   private final Object bean;

   private final Method method;
 }
Copy code

Let's introduce these two member variables. Bean is the bean of controller, and method is the method mapped to controller. If method doesn't understand, you can see the principle of reflection. In short, the invoke method of method will be executed in the adapter later, that is, the method mapped on the controller will be executed.

Let's return to the method just now and take a look at the core method   lookupHandlerMethod

lookupHandlerMethod()

protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
		List<Match> matches = new ArrayList<>();
		//a key
		List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);
		addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);
		Match bestMatch = matches.get(0);
		handleMatch(bestMatch.mapping, lookupPath, request);
		return bestMatch.handlerMethod;
	}
Copy code

I have madly omitted this code and deleted some multi mapping or empty cases. Let's first understand a process of normal code.

Let's analyze the second line of key code, this   mappingRegistry  . getMappingsByDirectPath(lookupPath). We'll look at this first   mappingRegistry   What is it?

mappingRegistry class

class MappingRegistry {

   private final Map<T, MappingRegistration<T>> registry = new HashMap<>();

   private final MultiValueMap<String, T> pathLookup = new LinkedMultiValueMap<>();

   private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();

   private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();

   private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
}
Copy code

mappingRegistry is an internal class, which contains a pile of maps. The getMappingsByDirectPath method is only from   pathLookup   In this map, we just use lookupPath (url, such as / user/login) as the key to get the value. This value is our handler.

At this time, the question arises. When did these map s get the content

When the spring IOC container is initialized, the mapper will also be initialized, and the mapping correspondence will be placed in the pathLookup map. The specific process is related to container initialization, so I won't describe it in detail.

Summary

It can be found that this request is not used at all
The methods of RequestMappingHandlerMapping are the methods of its parent class AbstractHandlerMapping. After that, the process is very simple. It just encapsulates the handlerMethod into a handerchain and returns.

Source code analysis of adapter

Simplified edition of Mianjing

  • call   RequestMappingHandlerAdapter   The handle method of the controller actually calls the controller method through reflection and returns a value
  • Select a specific return value parser according to the return value (for example, if an object is returned with @ Response annotation, use RequestResponseBodyMethodProcessor to serialize the object into json)
  • Finally, the returned mv is null, skipping the view parser.

Detailed Edition

HandlerAdapter

The module of the above process introduces that there are many kinds of adapters. They all implement the HandleAdapter interface. Take a look at the source code of the interface

public interface HandlerAdapter {
  boolean supports(Object handler);

  ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
}

Copy code

Similarly, only these two methods are important.

The first method is circular judgment above   Is it appropriate   The second method is the execution method in the dispatcher, which returns a modelAndView. Is it easy to see, so let's take a look at his implementation class.

Because there are many kinds of handle s, there will be many adapters

We usually come out   HandlerMethod   Using
RequestMappingHandlerAdapter  , It's the most complicated here. Why is it complicated, because it involves a lot of url mapping, parameter and return value processing. Let me start with a simple example,
SimpleServletHandlerAdapter, a handler adapter of servlet type.

SimpleServletHandlerAdapter

public class SimpleServletHandlerAdapter implements HandlerAdapter {
   public boolean supports(Object handler) {
      return (handler instanceof Servlet);
   }

   public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) {
      ((Servlet) handler).service(request, response);
      return null;
   }
}
Copy code

First of all, spring can also use the servlet specification. For example, it inherits the HTTP servlet, but it will follow the mvc process, because this servlet is not managed by tomcat, but managed by the spring container as a bean.

You can see that the implementation class is as simple as its name. supports only determines whether the handler is a servlet type, and the handle only calls the service.

AbstractHandlerMethodAdapter

//Simplified version
public abstract class AbstractHandlerMethodAdapter implements HandlerAdapter {
    public final boolean supports(Object handler) {
        return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
    }

    protected abstract boolean supportsInternal(HandlerMethod handlerMethod);

    public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler){
		return handleInternal(request, response, (HandlerMethod) handler);
	}

    protected abstract ModelAndView handleInternal(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception;
}
Copy code

Look first
Abstract class of RequestMappingHandlerAdapter.

  • supports determines whether the type is   HandlerMethod  , In addition, there is a new method supportsInternal, but this method always returns true for RequestMappingHandlerAdapter. Therefore, we can think that when a handler is of type handlermethod, it will be processed by the adapter. The point here is   HandlerMethod  , It represents the type of handler we usually use
  • handle calls the new method handleInternal, and the handler as a parameter is forced to   HandlerMethod   type

handle method flow

Just now, I briefly introduced the interface of the parent class adapter. Next, let's look at the process

As mentioned above, the dispatcher will loop to obtain the corresponding handleadapter first, through the supports method, which I ignored.

Then the handle method will be called, which is actually
Of RequestMappingHandlerAdapter   handleInternal   method

RequestMappingHandlerAdapter

handleInternal()

protected ModelAndView handleInternal(){
    ModelAndView mav;
    mav = invokeHandlerMethod(request, response, handlerMethod);
    return mav;
}
Copy code

After the abstract is omitted, only the invokeHandlerMethod method method is left

invokeHandlerMethod()

protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
      HttpServletResponse response, HandlerMethod handlerMethod) {
    
        ServletWebRequest webRequest = new ServletWebRequest(request, response);
        ModelAndViewContainer mavContainer = new ModelAndViewContainer();
        //ServletInvocableHandlerMethod is a subclass of HandlerMethod
        ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
        //core
        invocableMethod.invokeAndHandle(webRequest, mavContainer);
        return getModelAndView(mavContainer, modelFactory, webRequest);
}
Copy code

I deleted a large number of asynchronous and mavcontainer parts here. It's meaningless. At the same time, I won't analyze them because I can't understand them.

The core of this code is
invocableMethod.invokeAndHandle(webRequest, mavContainer), analyze the following parameters.

  • Encapsulation of webRequest request
  • mavcontainer is empty when the front and rear ends are separated.

Subclass HandlerMethod

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {
        //Get the return value. providedArgs is the parameter
		Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
        //Return value resolution
		this.returnValueHandlers.handleReturnValue(
					returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
	}
Copy code
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
      Object... providedArgs) throws Exception {
	//Parameter parsers, such as @ RequestBody, objects, variables, etc., parse the parameters and put them into the args array,
   Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
   return doInvoke(args);
}
Copy code
protected Object doInvoke(Object... args) throws Exception {
        //Get the method, which is the method corresponding to the url of the controller
		Method method = getBridgedMethod();
        //Reflection calls the controller's method
		return method.invoke(getBean(), args);
	}
Copy code

This is the last step before the request to the controller. This is the function of InvocableHandlerMethod   doInvoke   method. There is only a simple reflection. After we get the Method object, we call the invoke method. That is to execute the method of mapping url under controller. Let me give an example here

@GetMapping
public ApiMsg get(@RequestParam(value = "current", required = false, defaultValue = "1") int current,
                  @RequestParam(value = "size", required = false, defaultValue = "10") int size){
    return adminReportService.selectAllReport(current,size);
}
Copy code

An extremely standard controller returns an Object object

I won't study the analysis of return value and parameter value.

Summary

For the writing method of separating the front and back ends and passing json, the modelandview is empty and directly processes the return value. I didn't pay attention to the processing of this modelandview.

summary

As the most commonly used mvc, it should be the most understandable spring source code for web development. There is also a lot of knowledge, including the processing of different handler s or the differences between different types, jsp parsing, etc. I don't study it deeply because I'm a shallow student. If anything is wrong, please let me know.

Keywords: Java Programming Spring Boot Programmer architecture

Added by tylerdurden on Thu, 21 Oct 2021 08:30:55 +0300