SpringMVC Source Series HandlerAdapter

Posted by tmharrison on Fri, 15 Oct 2021 18:38:24 +0200

1. Write before

1. Overview

Previously we talked about finding the corresponding mapper by request, that is, finding the corresponding method, so how to call this method. Because different methods of receiving requests have different ways, the simplest is that the method name is different, so how to call. Here we use the adapter mode in design mode, make a judgment by adapting, and then by notCall methods the same way, which is the main function of HandlerAdpater

2. Request process

Having previously described HandlerMapping, we found the handler by request, so let's move on to the following code for doDispatch():

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 {
                 // Check if the file has been uploaded
				processedRequest = checkMultipart(request);
                 // Whether processing flags for file uploads are required
				multipartRequestParsed = (processedRequest != request);

				// Determine handler for the current request.
                 // Map our requests to logical methods
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
                      // If no mapper is found, the interface method setting is not found returns 404
					noHandlerFound(processedRequest, response);
					return;
				}

				// Determine handler adapter for the current request.
                 // Find the corresponding configurator through the mapper
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				// Process last-modified header, if supported by the handler.
				String method = request.getMethod();
				boolean isGet = HttpMethod.GET.matches(method);
				if (isGet || HttpMethod.HEAD.matches(method)) {
					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
						return;
					}
				}
				
                 // Execute the interceptor's preceding methods in sequence
				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				// Actually invoke the handler.
                 // Calling interface methods through an adapter 
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
			
				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}

				applyDefaultViewName(processedRequest, mv);
                 // Post-method of executing interceptor executes in reverse order of configuration 
				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);
				}
			}
		}
	}

Let's first see that no corresponding Handler object is found. If no Handler object is found, execute noHandler Found (processedRequest, response);The specific code is as follows:

	protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
         // Log Printing
		if (pageNotFoundLogger.isWarnEnabled()) {
			pageNotFoundLogger.warn("No mapping for " + request.getMethod() + " " + getRequestUri(request));
		}
         // Is there an exception
		if (this.throwExceptionIfNoHandlerFound) {
			throw new NoHandlerFoundException(request.getMethod(), getRequestUri(request),
					new ServletServerHttpRequest(request).getHeaders());
		}
		else {
             // Return 404 without exception
			response.sendError(HttpServletResponse.SC_NOT_FOUND);
		}
	}

The above code is really simple, to determine if you need to print the log, to print the log directly if you need it, to throw an exception if you make a mistake when looking up the Handler, and to set the status code to 404 if no exception occurs, and then return.

The corresponding Handler Adapter is then obtained from the corresponding Handler, at which point getHandler Adapter (mappedHandler.getHandler()) is called;Method, the specific code is as follows:

	protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
		if (this.handlerAdapters != null) {
			for (HandlerAdapter adapter : this.handlerAdapters) {
				if (adapter.supports(handler)) {
					return adapter;
				}
			}
		}
		throw new ServletException("No adapter for handler [" + handler +
				"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
	}

This runs to the HandlerAdapter, which is an interface and its common subclasses

2. HandlerAdapter

1.HandlerAdapter interface

There are three methods in this interface

public interface HandlerAdapter {
	// Determine if the adapter supports incoming handler s
	boolean supports(Object handler);
	// Use the given handler to process the current request request and invoke the corresponding logical method
	@Nullable
	ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
	// Return handler last modified time This method has been marked as obsolete
	@Deprecated
	long getLastModified(HttpServletRequest request, Object handler);
}

2. Common Subclasses

The HandlerAdapter system provides some subclasses, but we can also extend them, such as Spring Boot.

HttpRequestHandlerAdapter

SimpleControllerHandlerAdapter

RequestMappingHandlerAdapter

RequestMappingHandlerAdapter

3. Initialization

The HandlerAdapter has the same initialization idea as the previously described Handler Mapping, call chain:

org.springframework.web.servlet.HttpServletBean#init()
--> org.springframework.web.servlet.FrameworkServlet#initServletBean()
--> org.springframework.web.servlet.FrameworkServlet#initWebApplicationContext()
--> org.springframework.web.servlet.DispatcherServlet#onRefresh()
--> org.springframework.web.servlet.DispatcherServlet#initStrategies()
--> org.springframework.web.servlet.DispatcherServlet#initHandlerAdapters()

From the call chain above, we invoke the initHandlerAdapters initialization processing adapter method

	private void initHandlerAdapters(ApplicationContext context) {
		this.handlerAdapters = null;

		if (this.detectAllHandlerAdapters) {
			// Find all HandlerAdapters in the ApplicationContext, including ancestor contexts.
             // Take it from the spring container, and if you add the @EnableWebMvc comment here, there are three
			Map<String, HandlerAdapter> matchingBeans =
					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
			if (!matchingBeans.isEmpty()) {
				this.handlerAdapters = new ArrayList<>(matchingBeans.values());
				// We keep HandlerAdapters in sorted order.
                // If it is not empty, we process the sort because the adapter has a matching order
				AnnotationAwareOrderComparator.sort(this.handlerAdapters);
			}
		}
		else {
			try {
				HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
				this.handlerAdapters = Collections.singletonList(ha);
			}
			catch (NoSuchBeanDefinitionException ex) {
				// Ignore, we'll add a default HandlerAdapter later.
			}
		}

		// Ensure we have at least some HandlerAdapters, by registering
		// default HandlerAdapters if no other adapters are found.
         // If the adapter is not available from Spring above, then default from the configuration file
		if (this.handlerAdapters == null) {
			this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
			if (logger.isTraceEnabled()) {
				logger.trace("No HandlerAdapters declared for servlet '" + getServletName() +
						"': using default strategies from DispatcherServlet.properties");
			}
		}
	}

The code above simply removes the three HandlerMapping s from the spring container if the @EnableWebMvc annotation is added, and if not, calls the getDefaultStrategies() method.

protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
		if (defaultStrategies == null) {
			try {
				// Load default strategy implementations from properties file.
				// This is currently strictly internal and not meant to be customized
				// by application developers.
                  // Value'DispatcherServlet.properties'from default configuration 
				ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
                  // Load Properties
				defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
			}
			catch (IOException ex) {
				throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());
			}
		}
         // Traverse default mapper injection
		String key = strategyInterface.getName();
		String value = defaultStrategies.getProperty(key);
		if (value != null) {
			String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
			List<T> strategies = new ArrayList<>(classNames.length);
			for (String className : classNames) {
				try {
					Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
					Object strategy = createDefaultStrategy(context, clazz);
					strategies.add((T) strategy);
				}
				catch (ClassNotFoundException ex) {
					throw new BeanInitializationException(
							"Could not find DispatcherServlet's default strategy class [" + className +
							"] for interface [" + key + "]", ex);
				}
				catch (LinkageError err) {
					throw new BeanInitializationException(
							"Unresolvable class definition for DispatcherServlet's default strategy class [" +
							className + "] for interface [" + key + "]", err);
				}
			}
			return strategies;
		}
		else {
			return Collections.emptyList();
		}
	}

The code above says that if it is not available in the spring container, it will be taken from the default configuration file, which looks at the jar package spring-webmvc-5.3.9.jar in IDEA and opens to see one of the configurations

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
	org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
	org.springframework.web.servlet.function.support.HandlerFunctionAdapter

3. Adaptation Calls

We have called HandlerAdapter through the doDispatch() method above, and we have described this interface and its subclasses, so next we will introduce the interface according to the class diagram

1.HttpRequestHandlerAdapter

Let's first look at the structure of this class to see if some of spring's extension points can be used to initialize some data, as follows:


You can see that this class does not implement spring's extension point, that is, it implements the HandlerAdapter interface

public class HttpRequestHandlerAdapter implements HandlerAdapter {

	@Override
	public boolean supports(Object handler) {
         // Determine if this handler implements the HttpRequestHandler interface
		return (handler instanceof HttpRequestHandler);
	}

	@Override
	@Nullable
	public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		// This is just to turn the handler around and call the corresponding handleRequest() method
		((HttpRequestHandler) handler).handleRequest(request, response);
		return null;
	}
	// The getLastModified() method has been marked as obsolete, omitted here
}

We can see from the above that this class is very simple, calling the support method for adapting, this method is only a strong judgment, and the logical method called by adapting is appropriate and well understood.

Test Code

@Component("/name")
public class BeanNameController implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        System.out.println("Realization Controller Interface mode");
        return null;
    }
}

Within the getHandlerAdapter() method, the handlerAdapter collection is traversed to find out if the handlerAdapter matches the Handler, and if it finds one, it returns directly, so the matching is sequential. The first step is BeanNameUrlHandlerMapping, where Bean's name is "/"All begin with matches, and this class is a class that implements the HttpRequestHandler interface before returning to this adapter. After returning to this adapter, continue executing code down, first executing the interceptor's preHandle() method, and then calling to handle() through the adapter.Method, this can be seen from the class to force a direct call, very simple.

2.SimpleControllerHandlerAdapter

With the introduction above, we can see that this is very understandable, similar to the above

Corresponding class contents:

public class SimpleControllerHandlerAdapter implements HandlerAdapter {

	@Override
	public boolean supports(Object handler) {
         // Strengthen Judgment
		return (handler instanceof Controller);
	}

	@Override
	@Nullable
	public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		// Strong call method
		return ((Controller) handler).handleRequest(request, response);
	}

	// The getLastModified() method has been marked as obsolete, omitted here
}

Test Code

@Component("/name")
public class BeanNameController implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        System.out.println("Realization Controller Interface mode");
        return null;
    }
}

3.HandlerFunctionAdapter

class diagram

You can see that this class implements the Ordered interface, no other special interface, and you can see that it does not utilize Spring's extension points. Next, let's look at the support method for this class. The code is as follows:

@Override
public boolean supports(Object handler) {
	return handler instanceof HandlerFunction;
}

As long as the handler's type is HandlerFunction, it returns true, so when will it return true? From the last blog post, we can see that if the handler found is RouterFunctionMapping, this method returns true.

4. RequestMappingHandlerAdapter (Key)

Class introduction

This class is mainly used to accommodate classes with @Controller and @RequestMapping annotations. First, look at this class diagram

Here we implement the ApplicationContextAware, BeanFactoryAware, InitializingBean interfaces. All three interface implementations will run at container startup. The method of the interface in ApplicationContextAware should not be looked at because the parent class implements and does not call the method in this class. Next, look at the setBeanFactory method in BeanFactoryAware interfaceThe code is as follows:

	@Override
	public void setBeanFactory(BeanFactory beanFactory) {
		if (beanFactory instanceof ConfigurableBeanFactory) {
			this.beanFactory = (ConfigurableBeanFactory) beanFactory;
		}
	}

You can see that this class is Spring's bean factory set to the property beanFactory. Next, let's move on to the implementation of the afterPropertiesSet method in InitializingBean with the following code:

public void afterPropertiesSet() {
		// Do this first, it may add ResponseBody advice beans
  		//What's needed to initialize the @ControllerAdvice comment, which is described in a blog post
		initControllerAdviceCache();

  		//Gets the parameters of the invoked method
		if (this.argumentResolvers == null) {
        	//Spring provides a series of Handler MethodArgumentResolvers, which are also called here by users themselves
			List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
			this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
		}
  		//Getting the parameters of the bridging method
		if (this.initBinderArgumentResolvers == null) {
      		//Spring provides a series of Handler MethodArgumentResolvers, which are also called here by users themselves
			List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
			this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
		}
  		//Get the parameters returned by the method
		if (this.returnValueHandlers == null) {
      		//Spring provides a series of Handler MethodReturnValueHandlers, which are also called here by users themselves
			List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
			this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
		}
	}

This completes a series of initializations, along with parsers for method parameters and processors for processing method return values. These are useful when calling specified methods and processing return values

Supports()

After looking at the extensions above, we need to look at the supports method in this class. The code is as follows

public final boolean supports(Object handler) {
		return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}

supportsInternal() of the RequestMappingHandlerAdapter class

	@Override
	protected boolean supportsInternal(HandlerMethod handlerMethod) {
		return true;
	}

The second method returns true by default, so the matching rule is to add a @RequestMapping or @Controller class and all methods in this class that add @RequestMapping will be resolved to handler of HandlerMethod type

handle()

Introduce

The adapter method Supports() is described above. Next, the handle() method of the RequestMappingHandlerAdapter adapter is described in detail. This method, like other adapter classes, calls the corresponding interface method, but there are two points to consider here.

  1. How is the method called?

  2. How do the parameters of the method pass through the past?

There are several ways to think about how a method is called from the Java language perspective. Usually we call a method of another class in one class.

(1) The most common is to create an object of this class and call the method directly, but you must know the method name, but here we call, we don't know the method name

(2) Request forwarding through a Servlet to call another Servlet's method, but here the method we are called is just a common class

(3) Open a method to the outside world through an interface, similar to the HTTP ServerServlet and Controller interfaces above, but in this case, a class can only write one method, which is not appropriate.

(4) Call methods through reflection, which is the reflection used by the adapter to call interface methods

Initial call chain

Next, let's look at the corresponding method, calling handle() of the AbstractHandlerMethodAdapter class first

	@Override
	@Nullable
	public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {

		return handleInternal(request, response, (HandlerMethod) handler);
	}

Call handleInternal(request, response, (HandlerMethod) handler) method

	@Override
	protected ModelAndView handleInternal(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

		ModelAndView mav;
        // Check if the requested session is supported
		checkRequest(request);

		// Execute invokeHandlerMethod in synchronized block if required.
        // By default, this value is false, so the code in else is executed
		if (this.synchronizeOnSession) {
			HttpSession session = request.getSession(false);
			if (session != null) {
				Object mutex = WebUtils.getSessionMutex(session);
				synchronized (mutex) {
					mav = invokeHandlerMethod(request, response, handlerMethod);
				}
			}
			else {
				// No HttpSession available -> no mutex necessary
				mav = invokeHandlerMethod(request, response, handlerMethod);
			}
		}
		else {
			// No synchronization on session demanded at all...
            // Call the corresponding method
			mav = invokeHandlerMethod(request, response, handlerMethod);
		}

		if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
			if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
				applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
			}
			else {
				prepareResponse(response);
			}
		}

		return mav;
	}

Continue to invoke the URL mapping method invokeHandlerMethod(request, response, handlerMethod)

	@Nullable
	protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
		// Wrap request and response as ServletWebRequest objects
		ServletWebRequest webRequest = new ServletWebRequest(request, response);
		try {
            //Return Web Data Binder Factory
			WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
            //Return to Model Factory
			ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
            
            // Returns the method to be invoked
			ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
            // Parser for setting parameters
			if (this.argumentResolvers != null) {
				invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
			}
            // Setting the return type parameter of a method
			if (this.returnValueHandlers != null) {
				invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
			}
            // Setting the return type parameter of a method
			invocableMethod.setDataBinderFactory(binderFactory);
            // Set the corresponding method parameter name finder, which is the parameter name used to find the method, because only arg0 is returned by the JDK itself
			invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
			
            // Create models and containers to try
			ModelAndViewContainer mavContainer = new ModelAndViewContainer();
            // Setting the read-only Input property
			mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
            // Initialize model
			modelFactory.initModel(webRequest, mavContainer, invocableMethod);
            // Set Ignore Redirection Default Model
			mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
 			
            // Create Asynchronous Request
			AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
            // Set the corresponding timeout
			asyncWebRequest.setTimeout(this.asyncRequestTimeout);
            
            // Manager for Asynchronous Requests
			WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
			// Set up tasks
            asyncManager.setTaskExecutor(this.taskExecutor);
            // Set Asynchronous Requests
			asyncManager.setAsyncWebRequest(asyncWebRequest);
			// Register your interceptor
            asyncManager.registerCallableInterceptors(this.callableInterceptors);
			asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);

			if (asyncManager.hasConcurrentResult()) {
				Object result = asyncManager.getConcurrentResult();
				mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
				asyncManager.clearConcurrentResult();
				LogFormatUtils.traceDebug(logger, traceOn -> {
					String formatted = LogFormatUtils.formatValue(result, !traceOn);
					return "Resume with async result [" + formatted + "]";
				});
				invocableMethod = invocableMethod.wrapConcurrentResult(result);
			}
			
            // True call handling
			invocableMethod.invokeAndHandle(webRequest, mavContainer);
			if (asyncManager.isConcurrentHandlingStarted()) {
				return null;
			}
			
            // Get the corresponding modelAndView object
			return getModelAndView(mavContainer, modelFactory, webRequest);
		}
		finally {
			webRequest.requestCompleted();
		}
	}

The above assigns a series of parameters, and finally invokeAndHandle(webRequest, mavContainer) actually calls the method

	public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {
		
        // Method to invoke the request through reflection
		Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
		setResponseStatus(webRequest);

		if (returnValue == null) {
			if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
				disableContentCachingIfNecessary(webRequest);
				mavContainer.setRequestHandled(true);
				return;
			}
		}
		else if (StringUtils.hasText(getResponseStatusReason())) {
			mavContainer.setRequestHandled(true);
			return;
		}

		mavContainer.setRequestHandled(false);
		Assert.state(this.returnValueHandlers != null, "No return value handlers");
		try {
			this.returnValueHandlers.handleReturnValue(
					returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
		}
		catch (Exception ex) {
			if (logger.isTraceEnabled()) {
				logger.trace(formatErrorForReturnValue(returnValue), ex);
			}
			throw ex;
		}
	}

Continue calling invokeForRequest(webRequest, mavContainer, providedArgs)

	@Nullable
	public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {
		// Get the method parameters
		Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
		if (logger.isTraceEnabled()) {
			logger.trace("Arguments: " + Arrays.toString(args));
		}
        // Call the method through reflection
		return doInvoke(args);
	}

The code above consists of two steps, the first is to get the parameters by the getMethodArgumentValues() method, the second is to do the corresponding method by doInvoke(), to do it by reflection, and then to return the result.

So first, let's look at the processing of getting arguments. The overall idea is to get all the parameters of this method first, then through the parameter parser, now find the parameters in the request by type, then by parameter name, so that the parameters carried by the request are automatically assigned to the arguments.

	protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {
		// Get the parameters of the method, note that this is a direct call to the method, without a class name in front of it.
         // This method is in the InvocableHandlerMethod class and is a subclass of HandlerMethod
         // The mapper we got earlier is a subclass of InvocableHandlerMethod, so we call it directly
         // So what you get is an array of formal parameter lists for this method
         // Inside the getMethodParameters() method is the getter method of the HandlerMethod class
         // See its diagram below
		MethodParameter[] parameters = getMethodParameters();
        // If the parameter is empty, it means that the method does not require a formal parameter and returns the combination directly
		if (ObjectUtils.isEmpty(parameters)) {
			return EMPTY_ARGS;
		}
  		// Create Parameter Object Storage Method Arguments
		Object[] args = new Object[parameters.length];
        // By traversing, querying the arguments in the request, according to the method parameters, with the help of the parameter parser
		for (int i = 0; i < parameters.length; i++) {
			MethodParameter parameter = parameters[i];
            // Discover for initialization parameter names
			parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
            // Normally this returns Null
			args[i] = findProvidedArgument(parameter, providedArgs);
			if (args[i] != null) {
				continue;
			}
            // Processors that look for corresponding parameters, primarily those that traverse all parameters, call the support method in the process and return true whenever it is satisfied
            // Different parameter types have different processors
            // Initialization of parameter types is described later
			if (!this.resolvers.supportsParameter(parameter)) {
                 // Report an exception if no parameter parser is found
				throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
			}
			try {
                // Find the resolveArgument() parsing parameter to execute the parameter processor directly to get the arguments
				args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
			}
			catch (Exception ex) {
				// Leave stack trace for later, exception may actually be resolved and handled...
				if (logger.isDebugEnabled()) {
					String exMsg = ex.getMessage();
					if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
						logger.debug(formatArgumentError(parameter, exMsg));
					}
				}
				throw ex;
			}
		}
		return args;
	}

As you can see above, we will first query if there is a matching parameter parser for this parameter, and if it does not, we will report an exception. Take a look at the supportsParameter(parameter) method

	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return getArgumentResolver(parameter) != null;
	}

Continue calling getArgumentResolver()

	@Nullable
	private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
        // Try to get this from the cache first, it's cached
		HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
		if (result == null) {
             // Traverse through all parameter processors and call the supportsParameter method of each parameter processor until the result is true
             // The following describes where the parameter parser was initialized
			for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
                  // Judgement in turn
				if (resolver.supportsParameter(parameter)) {
					result = resolver;
                      // Cache if matched
					this.argumentResolverCache.put(parameter, result);
					break;
				}
			}
		}
		return result;
	}

Now that we have described how to match the parameter parser, let's take a look at resolveArgument()

When we actually look at the class structure, there are several methods used before and after, supportsParameter(), resolveArgument(), getArgumentResolver(), which are of the HandlerMethodArgumentResolverComposite class, and three side-by-side methods

   @Override
   @Nullable
   public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
   		NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
   	// Continue calling the getArgumentResolver() method to get the corresponding parameter parser
   	HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
        // If an exception is reported as null, it means there is no parser to resolve the parameter
   	if (resolver == null) {
   		throw new IllegalArgumentException("Unsupported parameter type [" +
   				parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
   	}
        // The parser is obtained above, and the parameters are parsed differently because of different parameter parsers
   	return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
   }

Our methods have different types of formal parameters, such as wrapper class, string, basic data type, array, reference data type, etc., which can be divided into two cases: the first is the common type, the second is the reference object, SpringMVC will automatically encapsulate the parameters into the object

Normal parameter matching

Normal parameters generally jump to the AbstractNamedValueMethodArgumentResolver class, and then we look at the resolveArgument() within it, with the following code:

public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

    // Get the method's parameter name information NamedValueInfo is a static internal class that contains the parameter's name, required, defaultValue information
    // If the parameter has a @RequestParam annotation, the annotation information is saved here in NamedValueInfo
	NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
    // Types of method parameters to get packaging
	MethodParameter nestedParameter = parameter.nestedIfOptional();

    // Remove the parameter name separately from the namedValueInfo above
	Object resolvedName = resolveEmbeddedValuesAndExpressions(namedValueInfo.name);
    // Non-empty judgment
	if (resolvedName == null) {
		throw new IllegalArgumentException(
				"Specified name must not resolve to null: [" + namedValueInfo.name + "]");
	}

    // Get the value of the parameter
	Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
    // Get empty value
	if (arg == null) {
         // Check to see if there is a default and set it
		if (namedValueInfo.defaultValue != null) {
			arg = resolveStringValue(namedValueInfo.defaultValue);
		}
		else if (namedValueInfo.required && !nestedParameter.isOptional()) {
            // Determine if a required value is required, if an exception is thrown directly
			handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
		}
        // Handle null values, set to false if the value is of Boolean type, otherwise default
		arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
	}
	else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
         // Get the value "" and the default value is not empty, then set the default value in
		arg = resolveStringValue(namedValueInfo.defaultValue);
	}

    // Type conversion occurs because URL requests are generally received as String types, and our parameters have various types
	if (binderFactory != null) {
		WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
		try {
             // Convert the type here to the correct parameter type
			arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
		}
		catch (ConversionNotSupportedException ex) {// Type conversion exception
			throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
					namedValueInfo.name, parameter, ex.getCause());
		}
		catch (TypeMismatchException ex) {// Type cannot find exception
			throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
					namedValueInfo.name, parameter, ex.getCause());
		}
         // The most extreme case value is empty &&Default value is empty &&Required exception
         if (arg == null && namedValueInfo.defaultValue == null &&
					namedValueInfo.required && !nestedParameter.isOptional()) {
			handleMissingValueAfterConversion(namedValueInfo.name, nestedParameter, webRequest);
		}
	}

	handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
	
    //Returns the processed parameter's
	return arg;
}

Since the RequestParamMethodArgumentResolver class does not implement the HandlerMethodArgumentResolver interface, the resolveArgument method of the parent class is called here, but getting the value of the corresponding parameter is to call the resolveName method of the subclass RequestParamMethodArgumentResolver, with the following code:

protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
    // Get Request
	HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
	if (servletRequest != null) {
		Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
		if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
			return mpArg;
		}
	}

	Object arg = null;
    // Processing Files
	MultipartRequest multipartRequest = request.getNativeRequest(MultipartRequest.class);
	if (multipartRequest != null) {
		List<MultipartFile> files = multipartRequest.getFiles(name);
		if (!files.isEmpty()) {
			arg = (files.size() == 1 ? files.get(0) : files);
		}
	}
	if (arg == null) {
        // Focus here or call the requested API to get the parameter return is an array
		String[] paramValues = request.getParameterValues(name);
		if (paramValues != null) {
			arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
		}
	}
	return arg;
}

Finally, we see resolveName(), the method to get the corresponding parameter, which uses the servlet's api to get the value of the specified parameter.

Reference type parameter matching

When our parameter has a reference type, SpringMVC automatically encapsulates the parameter into an object, where SpringMVC first creates the object and then matches the fields in it. The adapter we use at this time is

resolveArgument() of ModelAttributeMethodProcessor

	@Override
	@Nullable
	public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

		Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer");
		Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory");
		// Gets the reference type parameter name
		String name = ModelFactory.getNameForParameter(parameter);
		ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
		if (ann != null) {
			mavContainer.setBinding(name, ann.binding());
		}

		Object attribute = null;
		BindingResult bindingResult = null;

		if (mavContainer.containsAttribute(name)) {
			attribute = mavContainer.getModel().get(name);
		}
		else {
			// Create attribute instance
			try {
                  // Create an object example even if it doesn't match any parameters, but the object must have only empty field values
				attribute = createAttribute(name, parameter, binderFactory, webRequest);
			}
			catch (BindException ex) {
				if (isBindExceptionRequired(parameter)) {
					// No BindingResult parameter -> fail with BindException
					throw ex;
				}
				// Otherwise, expose null/empty value and associated BindingResult
				if (parameter.getParameterType() == Optional.class) {
					attribute = Optional.empty();
				}
				else {
					attribute = ex.getTarget();
				}
				bindingResult = ex.getBindingResult();
			}
		}

		if (bindingResult == null) {
			// Bean property binding and validation;
			// skipped in case of binding failure on construction.
			WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
			if (binder.getTarget() != null) {
				if (!mavContainer.isBindingDisabled(name)) {
                      // The binding request parameter is where the request and object are bound
					bindRequestParameters(binder, webRequest);
				}
				validateIfApplicable(binder, parameter);
				if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
					throw new BindException(binder.getBindingResult());
				}
			}
			// Value type adaptation, also covering java.util.Optional
			if (!parameter.getParameterType().isInstance(attribute)) {
				attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
			}
			bindingResult = binder.getBindingResult();
		}

		// Add resolved attribute and BindingResult at the end of the model
		Map<String, Object> bindingResultModel = bindingResult.getModel();
		mavContainer.removeAttributes(bindingResultModel);
		mavContainer.addAllAttributes(bindingResultModel);

		return attribute;
	}

Above we see the approximate method of matching, this progressive paste binding method is more cumbersome, can be viewed through IDEA breakpoints, as long as you can find the code traversed in it

Last call chain

	@Nullable
	public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {
		// Get the method parameters
		Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
		if (logger.isTraceEnabled()) {
			logger.trace("Arguments: " + Arrays.toString(args));
		}
        // Call the method through reflection
		return doInvoke(args);
	}

The method getMethodArgumentValues() to get the parameters is described above. Next, let's look at calling the method doInvoke()

	@Nullable
	protected Object doInvoke(Object... args) throws Exception {
         // Getting Method
		Method method = getBridgedMethod();
		ReflectionUtils.makeAccessible(method);
		try {
			if (KotlinDetector.isSuspendingFunction(method)) {
				return CoroutinesUtils.invokeSuspendingFunction(method, getBean(), args);
			}
             // Focus here on invoking methods through reflection
			return method.invoke(getBean(), args);
		}
		catch (IllegalArgumentException ex) {
			assertTargetBean(method, getBean(), args);
			String text = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument");
			throw new IllegalStateException(formatInvokeError(text, args), ex);
		}
		catch (InvocationTargetException ex) {
			// Unwrap for HandlerExceptionResolvers ...
			Throwable targetException = ex.getTargetException();
			if (targetException instanceof RuntimeException) {
				throw (RuntimeException) targetException;
			}
			else if (targetException instanceof Error) {
				throw (Error) targetException;
			}
			else if (targetException instanceof Exception) {
				throw (Exception) targetException;
			}
			else {
				throw new IllegalStateException(formatInvokeError("Invocation failure", args), targetException);
			}
		}
	}

We've finished the invokeForRequest() method, got the parameters, called the method successfully, and went back to invokeAndHandle()

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {

	Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
    // Set the status code of the response. It should not be called here, it will be null
	setResponseStatus(webRequest);

    // If the return value is empty
	if (returnValue == null) {
		if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
            // disable cache
			disableContentCachingIfNecessary(webRequest);
            // Set this variable to true
			mavContainer.setRequestHandled(true);
			return;
		}
	}
	else if (StringUtils.hasText(getResponseStatusReason())) {// This is valuable
        // Set this variable to true
		mavContainer.setRequestHandled(true);
		return;
	}

    // Request needs to be processed
	mavContainer.setRequestHandled(false);
	Assert.state(this.returnValueHandlers != null, "No return value handlers");
	try {
        // Call the corresponding method to process the return value
		this.returnValueHandlers.handleReturnValue(
				returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
	}
	catch (Exception ex) {
		if (logger.isTraceEnabled()) {
			logger.trace(formatErrorForReturnValue(returnValue), ex);
		}
		throw ex;
	}
}

The code above calls handleReturnValue(), which is as follows:

public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
		ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

    // Finds the processor for the specified method return value
	HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
	if (handler == null) {
		throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
	}
    // This method is in the processor that calls the specified method return value
	handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}

The above code is mainly through selectHandler(returnValue, returnType);To find the processor for the corresponding method return value, the code is as follows:

private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
    // Determine if it is asynchronous
	boolean isAsyncValue = isAsyncReturnValue(value, returnType);
    // The supportsReturnType method of the processor that iterates through all method return values and returns directly whenever a match is found
	for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
		if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
			continue;
		}
		if (handler.supportsReturnType(returnType)) {
			return handler;
		}
	}
	return null;
}

The principle here is similar to that of the processor above which finds method parameters. All processors are traversed, calling the supportsReturnType method of the corresponding processor, and returning directly once the condition is met.

Next we go back to the invokeHandlerMethod(), where we've called invokeAndHandle(), and at the end of the method, getModelAndView()

	@Nullable
	private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
			ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {

		modelFactory.updateModel(webRequest, mavContainer);
         // To determine if this value is true, the value returned is true if it is not a view
         // Since we're basically front-end and back-end separated, we're going back here
		if (mavContainer.isRequestHandled()) {
			return null;
		}
		ModelMap model = mavContainer.getModel();
		ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
		if (!mavContainer.isViewReference()) {
			mav.setView((View) mavContainer.getView());
		}
		if (model instanceof RedirectAttributes) {
			Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
			HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
			if (request != null) {
				RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
			}
		}
		return mav;
	}

This is the end of the whole method call process, which can be summarized briefly:

  1. First iterate through all the parameter processors according to the parameters of the method, calling supportsParameter() in the processor separately, return the parameter processor directly if it is satisfied, and then call resolveArgument() of the parameter processor.
  2. Get the parameters through the parameter parser, then invoke the method through reflection
  3. Processing the return value of a method is the same as iterating through all return value processors and calling the supportsReturnType method on those processors, which, if satisfied, returns directly to the processor, while calling the handleReturnValue method of the processor to process and encapsulate the return value of the method

Parameter Parser Initialization

We used parameter parsers above. We traversed to find the corresponding parameter parsers. How do these parameter parsers initialize?

We mentioned earlier that the RequestMappingHandlerAdapter class implements the InitializingBean interface, and afterPropertiesSet() is called when the SpringMVC container starts up as appropriate.

	public void afterPropertiesSet() {
		// Do this first, it may add ResponseBody advice beans
  		//What's needed to initialize the @ControllerAdvice comment, which is described in a blog post
		initControllerAdviceCache();

  		//Gets the parameters of the invoked method
		if (this.argumentResolvers == null) {
        	//Spring provides a series of Handler MethodArgumentResolvers, which are also called here by users themselves
			List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
			this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
		}
  		//Getting the parameters of the bridging method
		if (this.initBinderArgumentResolvers == null) {
      		//Spring provides a series of Handler MethodArgumentResolvers, which are also called here by users themselves
			List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
			this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
		}
  		//Get the parameters returned by the method
		if (this.returnValueHandlers == null) {
      		//Spring provides a series of Handler MethodReturnValueHandlers, which are also called here by users themselves
			List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
			this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
		}
	}

The parameter parser and return value processor we used above are initialized here.

	private List<HandlerMethodArgumentResolver> getDefaultInitBinderArgumentResolvers() {
		List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(20);

		// Annotation-based argument resolution
         // Note that the first RequestParamMethodArgumentResolver is added here
		resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
		resolvers.add(new RequestParamMapMethodArgumentResolver());
		resolvers.add(new PathVariableMethodArgumentResolver());
		resolvers.add(new PathVariableMapMethodArgumentResolver());
		resolvers.add(new MatrixVariableMethodArgumentResolver());
		resolvers.add(new MatrixVariableMapMethodArgumentResolver());
		resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
		resolvers.add(new SessionAttributeMethodArgumentResolver());
		resolvers.add(new RequestAttributeMethodArgumentResolver());

		// Type-based argument resolution
		resolvers.add(new ServletRequestMethodArgumentResolver());
		resolvers.add(new ServletResponseMethodArgumentResolver());

		// Custom arguments
		if (getCustomArgumentResolvers() != null) {
			resolvers.addAll(getCustomArgumentResolvers());
		}

		// Catch-all
		resolvers.add(new PrincipalMethodArgumentResolver());
         // //Note that the second RequestParamMethodArgumentResolver is added here
		resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));

		return resolvers;
	}

Notice that there are two RequestParamMethodArgumentResolver objects added above, except the first parameter has a false value and the second parameter has a true value. What is the difference between the two values? We also need to look at the supportsParameter method of the RequestParamMethodArgumentResolver class with the following code:

public boolean supportsParameter(MethodParameter parameter) {
     // Is there a RequestParam annotation
	if (parameter.hasParameterAnnotation(RequestParam.class)) {
		if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
			RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
			return (requestParam != null && StringUtils.hasText(requestParam.name()));
		}
		else {
			return true;
		}
	}
	else {
         // Is there a RequestPart annotation for complex request domains
		if (parameter.hasParameterAnnotation(RequestPart.class)) {
			return false;
		}
		parameter = parameter.nestedIfOptional();
         // Is it a parameter of the request
		if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
			return true;
		}
        // The difference is that if true, the basic type will return true otherwise it will return false directly
		else if (this.useDefaultResolution) {
			return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
		}
		else {
			return false;
		}
	}
}

The difference between the above two RequestParamMethodArgumentResolver s is that if true, the basic type will return true, if not true, it will be skipped directly. When representing our matching parameters, the basic data type will be put in the last match, the reference type will be matched first, that is, the wrapper class will have a higher priority than the basic data type.

Topics: Java Spring Spring MVC mvc