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.
-
How is the method called?
-
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:
- 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.
- Get the parameters through the parameter parser, then invoke the method through reflection
- 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.