Spring MVC request processing. Your answer is guaranteed to pass the interview!

Time:2021-5-9

The spring version used in this article is 5.2.2.release

Nine components

Almost all the functions of spring MVC are completed by nine components, so it is very important to understand the role of the nine components for learning spring MVC.

/**File upload parser*/
private MultipartResolver multipartResolver;

/**Region parser for Internationalization*/
private LocaleResolver localeResolver;

/**Topic parser*/
private ThemeResolver themeResolver;

/**Handler mapping information*/
private List<HandlerMapping> handlerMappings;

/**Handler adapter*/
private List<HandlerAdapter> handlerAdapters;

/**Handler executes exception parser*/
private List<HandlerExceptionResolver> handlerExceptionResolvers;

/**Request to view converter*/
private RequestToViewNameTranslator viewNameTranslator;

/**Spring MVC allows redirection to carry parameters, store them in session, and destroy them when they are used up, so it's called flashmap*/
private FlashMapManager flashMapManager;

/**View parser*/
private List<ViewResolver> viewResolvers;
Copy code
  • Handlermapping: handlermapping information, according to the URL information carried by the request to find the processor (handler). Each request needs to be handled by the corresponding handler.
  • Handleradapter: a handler adapter. Spring MVC does not call the handler directly, but calls it through handleradapter, mainly to unify the calling mode of handler
  • Viewresolver: View resolver, used to resolve the view name of string type to view type. Viewresolver needs to find the template and Technology (that is, the type of view) to render, and the specific rendering process is completed by different views.
  • Multipart resolver: file upload parser, mainly used to process file upload request
  • Handlerexception resolver: the handler executes an exception resolver to handle exceptions uniformly
  • Requesttoviewnametranslator: converter from request to view
  • Localereresolver: regional resolver, used to support internationalization
  • Flashmapmanager: Spring MVC allows redirection to carry parameters, store them in session, and destroy them when they are used up, so it’s called flashmap
  • Theme resolver: theme resolver, used to support different themes

The top three heaviest of the nine components are handlermapping, handleradapter and viewresolver, because these are the three components that cannot be avoided when reading the source code.
“The latest video tutorial and learning route of basic Java in 2020!”

Commissioning preparation

Build a basic spring web project

Controller section

@Controller
public class IndexController {

    @RequestMapping("/index/home")
    public String home(String id, Student student, @RequestParam("code") String code) {
        System.out.println(student.getName());
        return "index";
    }

    @ResponseBody
    @RequestMapping("/index/list")
    public String list() {
        return "success";
    }
}
Copy code

Entity section

public class Student {

    private String name;
    private Integer gender;

   // getter、setter
}
Copy code

Or that sentence, spring source code is very huge, you can’t just see the trees without the forest, you need to read targeted, so this article only needs to focus on the main process.

Core approach

As we all know, spring MVC has a front-end controller, dispatcher servlet, which is used to distribute requests. The method used to process requests is doservice. The definition of this method is as follows

doService

/**
 * Exposes the DispatcherServlet-specific request attributes and delegates to {@link #doDispatch}
 * for the actual dispatching.
 */
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
 logRequest(request);

 // Keep a snapshot of the request attributes in case of an include,
 // to be able to restore the original attributes after the include.
 Map<String, Object> attributesSnapshot = null;
 if (WebUtils.isIncludeRequest(request)) {
  attributesSnapshot = new HashMap<>();
  Enumeration<?> attrNames = request.getAttributeNames();
  while (attrNames.hasMoreElements()) {
   String attrName = (String) attrNames.nextElement();
   if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
    attributesSnapshot.put(attrName, request.getAttribute(attrName));
   }
  }
 }

 // Make framework objects available to handlers and view objects.
 request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
 request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
 request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
 request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

 if (this.flashMapManager != null) {
  FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
  if (inputFlashMap != null) {
   request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
  }
  request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
  request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
 }

 try {
  //The method of real implementation
  doDispatch(request, response);
 }
 finally {
  if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
   // Restore the original attribute snapshot, in case of an include.
   if (attributesSnapshot != null) {
    restoreAttributesAfterInclude(request, attributesSnapshot);
   }
  }
 }
}
Copy code

doDispatch

Dodispatch is the real method used to process requests in doservice

/**
 *How to actually handle requests
 */
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 {
   //Verify whether it is a file upload request
   processedRequest = checkMultipart(request);
   multipartRequestParsed = (processedRequest != request);

   // Determine handler for the current request.
   //Find a suitable handler for the current request
   //The return value is a handler execution chain, that is, the processor execution chain
   mappedHandler = getHandler(processedRequest);
   if (mappedHandler == null) {
    noHandlerFound(processedRequest, response);
    return;
   }

   // Determine handler adapter for the current request.
   //Find the appropriate handleradapter according to the handler carried by handlerexecutionchain
   HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

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

   //Prehandle method of executing interceptor
   if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    return;
   }

   // Actually invoke the handler.
   //Using handleradapter to execute the corresponding processing method in handler
   mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

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

   //If the view is not set, the default view name is applied
   applyDefaultViewName(processedRequest, mv);
   //The posthandle method of executing interceptor
   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);
  }
  //Parsing views based on modelandview objects
  processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
 }
 catch (Exception ex) {
  triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
 }
 catch (Throwable err) {
  triggerAfterCompletion(processedRequest, response, mappedHandler,
    new NestedServletException("Handler processing failed", err));
 }
 finally {
  if (asyncManager.isConcurrentHandlingStarted()) {
   // Instead of postHandle and afterCompletion
   if (mappedHandler != null) {
    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
   }
  }
  else {
   // Clean up any resources used by a multipart request.
   if (multipartRequestParsed) {
    cleanupMultipart(processedRequest);
   }
  }
 }
}
Copy code

This method is the whole process of spring MVC processing request, which involves several important methods.

getHandler

This method is defined as follows

/**
 * Return the HandlerExecutionChain for this request.
 *Returns a handlerexecutionchain for this request
 */
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
 if (this.handlerMappings != null) {
  for (HandlerMapping mapping : this.handlerMappings) {
   HandlerExecutionChain handler = mapping.getHandler(request);
   if (handler != null) {
    return handler;
   }
  }
 }
 return null;
}
Copy code

The debugging information is as follows

Spring MVC request processing. Your answer is guaranteed to pass the interview!

According to the debugging information, the gethandler method mainly searches for a suitable handler from the list handlermappings collection, and the returned result is a handlerexecutionchain. Then get the handleradapter according to the handler carried in the handlerexecutionchain.

getHandlerAdapter

The gethandleradapter method is defined as follows

/**
  * Return the HandlerAdapter for this handler object.
  * @param handler the handler object to find an adapter for
  * @throws ServletException if no HandlerAdapter can be found for the handler. This is a fatal error.
  */
 protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
  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");
 }
Copy code

The debugging information is as follows

Spring MVC request processing. Your answer is guaranteed to pass the interview!

Similarly, the gethandleradapter method mainly searches for a suitable processor adapter (handleradapter) from the list handleradapters collection, and the returned result is a handleradapter.

As you can see, the real implementation class of handleradapter here is requestmappinghandleradapter.

processDispatchResult

The processdispatchresult method mainly forwards the encapsulated modelandview to the corresponding page after the method is executed. The definition is as follows

/**
 * Handle the result of handler selection and handler invocation, which is
 * either a ModelAndView or an Exception to be resolved to a ModelAndView.
 */
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
  @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
  @Nullable Exception exception) throws Exception {

 boolean errorView = false;

 if (exception != null) {
  if (exception instanceof ModelAndViewDefiningException) {
   logger.debug("ModelAndViewDefiningException encountered", exception);
   mv = ((ModelAndViewDefiningException) exception).getModelAndView();
  }
  else {
   Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
   mv = processHandlerException(request, response, handler, exception);
   errorView = (mv != null);
  }
 }

 // Did the handler return a view to render?
 if (mv != null && !mv.wasCleared()) {
  //This method is mainly called to render the view
  render(mv, request, response);
  if (errorView) {
   WebUtils.clearErrorRequestAttributes(request);
  }
 }
 else {
  if (logger.isTraceEnabled()) {
   logger.trace("No view rendering, null ModelAndView returned.");
  }
 }

 if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
  // Concurrent handling started during a forward
  return;
 }

 if (mappedHandler != null) {
  // Exception (if any) is already handled..
  mappedHandler.triggerAfterCompletion(request, response, null);
 }
}
Copy code

render

The render method is defined as follows

/**
 * Render the given ModelAndView.
 * <p>This is the last stage in handling a request. It may involve resolving the view by name.
 * @param mv the ModelAndView to render
 * @param request current HTTP servlet request
 * @param response current HTTP servlet response
 * @throws ServletException if view is missing or cannot be resolved
 * @throws Exception if there's a problem rendering the view
 */
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
 // Determine locale for request and apply it to the response.
 Locale locale =
   (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
 response.setLocale(locale);

 View view;
 String viewName = mv.getViewName();
 if (viewName != null) {
  // We need to resolve the view name.
  //According to the given view name, parse to get the view object
  view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
  if (view == null) {
   throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
     "' in servlet with name '" + getServletName() + "'");
  }
 }
 else {
  // No need to lookup: the ModelAndView object contains the actual View object.
  view = mv.getView();
  if (view == null) {
   throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
     "View object in servlet with name '" + getServletName() + "'");
  }
 }

 // Delegate to the View object for rendering.
 if (logger.isTraceEnabled()) {
  logger.trace("Rendering view [" + view + "] ");
 }
 try {
  if (mv.getStatus() != null) {
   response.setStatus(mv.getStatus().value());
  }
  view.render(mv.getModelInternal(), request, response);
 }
 catch (Exception ex) {
  if (logger.isDebugEnabled()) {
   logger.debug("Error rendering view [" + view + "]", ex);
  }
  throw ex;
 }
}
Copy code

resolveViewName

The resolveviewname method is defined as follows

@Nullable
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
  Locale locale, HttpServletRequest request) throws Exception {

 if (this.viewResolvers != null) {
  for (ViewResolver viewResolver : this.viewResolvers) {
   View view = viewResolver.resolveViewName(viewName, locale);
   if (view != null) {
    return view;
   }
  }
 }
 return null;
}
Copy code

The debugging information is as follows

Spring MVC request processing. Your answer is guaranteed to pass the interview!

According to the debugging information, we can see that the viewresolver that actually parses the view is the internalresourceviewresolver class, which is a type that we often configure

<!--  Definition view file parsing -- >
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
 <property name="prefix" value="/WEB-INF/views/" />
 <property name="suffix" value=".html" />
</bean>
Copy code

So far, we have the complete logic of spring MVC processing requests

Spring MVC request processing. Your answer is guaranteed to pass the interview!

The whole process of spring MVC processing requests has been sorted out.

However, there are two important problems that have not been solved: parameter binding and return value processing.

Because when writing methods in controller, there are various types of parameters. How does spring MVC handle different types of parameters Will spring MVC return modelandview after processing requests? If @ ResponseBody annotation is added?

Parameter binding

In the whole process, there is also one of the most important methods, that is, the method that actually executes the handler. The binding of parameters and the processing of return values are all in this method, that is to say

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

handle

The function of handle method is to execute the real processing method according to the request parameters, and return the appropriate modelandview object, or null. The method is defined as follows in the abstracthandlermethodadapter class

/**
 * This implementation expects the handler to be an {@link HandlerMethod}.
 */
@Override
@Nullable
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
  throws Exception {

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

You can see that this method implementation has only one line of code

handleInternal

Continue to delve into the handleinternal method

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

 ModelAndView mav;
 //Validates the specified request for the supported method types (get, post, etc.) and the required session
 checkRequest(request);

 // Execute invokeHandlerMethod in synchronized block if required.
 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...
  //The method to actually execute handler
  mav = invokeHandlerMethod(request, response, handlerMethod);
 }

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

 return mav;
}
Copy code

invokeHandlerMethod

Continue to delve into the invokehandlermethod method

/**
 * Invoke the {@link RequestMapping} handler method preparing a {@link ModelAndView}
 * if view resolution is required.
 *Execute the handler method of @ requestmapping annotation. If you need to parse the view, prepare a modelandview
 */
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
  HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

 ServletWebRequest webRequest = new ServletWebRequest(request, response);
 try {
  WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
  ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

  //The handlermethod interface encapsulates the information of the execution method and provides convenient access to method parameters, method return values, method comments, etc.
  ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
  if (this.argumentResolvers != null) {
   invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
  }
  if (this.returnValueHandlers != null) {
   invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
  }
  invocableMethod.setDataBinderFactory(binderFactory);
  invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

  //Modelandviewcontainer can be regarded as the context container of modelandview, which is associated with the information of model and view
  ModelAndViewContainer mavContainer = new ModelAndViewContainer();
  mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
  modelFactory.initModel(webRequest, mavContainer, invocableMethod);
  mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

  AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
  asyncWebRequest.setTimeout(this.asyncRequestTimeout);

  WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
  asyncManager.setTaskExecutor(this.taskExecutor);
  asyncManager.setAsyncWebRequest(asyncWebRequest);
  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);
  }

  //The method that actually executes the handler
  invocableMethod.invokeAndHandle(webRequest, mavContainer);
  if (asyncManager.isConcurrentHandlingStarted()) {
   return null;
  }

  //Get modelandeview object
  return getModelAndView(mavContainer, modelFactory, webRequest);
 }
 finally {
  webRequest.requestCompleted();
 }
}
Copy code

invokeAndHandle

The function of the invokeandhandle method is to execute and process the method that actually responds to the request. This method is defined as follows

/**
 * Invoke the method and handle the return value through one of the
 * configured {@link HandlerMethodReturnValueHandler HandlerMethodReturnValueHandlers}.
 * @param webRequest the current request
 * @param mavContainer the ModelAndViewContainer for this request
 * @param providedArgs "given" arguments matched by type (not resolved)
 */
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
  Object... providedArgs) throws Exception {

 //Method of executing handler
 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;
 }
}
Copy code

invokeForRequest

/**
 * Invoke the method after resolving its argument values in the context of the given request.
 * <p>Argument values are commonly resolved through
 * {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers}.
 * The {@code providedArgs} parameter however may supply argument values to be used directly,
 * i.e. without argument resolution. Examples of provided argument values include a
 * {@link WebDataBinder}, a {@link SessionStatus}, or a thrown exception instance.
 * Provided argument values are checked before argument resolvers.
 * <p>Delegates to {@link #getMethodArgumentValues} and calls {@link #doInvoke} with the
 * resolved arguments.
 * @param request the current request
 * @param mavContainer the ModelAndViewContainer for this request
 * @param providedArgs "given" arguments matched by type, not resolved
 * @return the raw value returned by the invoked method
 * @throws Exception raised if no suitable argument resolver can be found,
 * or if the method raised an exception
 * @see #getMethodArgumentValues
 * @see #doInvoke
 */
@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
  Object... providedArgs) throws Exception {

 //Get parameters
 Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
 if (logger.isTraceEnabled()) {
  logger.trace("Arguments: " + Arrays.toString(args));
 }
 //Implementation
 return doInvoke(args);
}
Copy code

The real implementation is nothing more than reflection invoke, so the more important thing is how the parameters are bound. The details are in the getmethodargumentvalues method

getMethodArgumentValues

The getmethodargumentvalues method is used to get the real parameters from the request and return the object array. The definition of this method is as follows

/**
 * Get the method argument values for the current request, checking the provided
 * argument values and falling back to the configured argument resolvers.
 * <p>The resulting array will be passed into {@link #doInvoke}.
 * @since 5.1.2
 */
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
  Object... providedArgs) throws Exception {

 //Get all the parameters on the method
 MethodParameter[] parameters = getMethodParameters();
 if (ObjectUtils.isEmpty(parameters)) {
  return EMPTY_ARGS;
 }

 Object[] args = new Object[parameters.length];
 for (int i = 0; i < parameters.length; i++) {
  MethodParameter parameter = parameters[i];
  parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
  args[i] = findProvidedArgument(parameter, providedArgs);
  if (args[i] != null) {
   continue;
  }
  if (!this.resolvers.supportsParameter(parameter)) {
   throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
  }
  try {
   
   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;
}
Copy code

Spring MVC request processing. Your answer is guaranteed to pass the interview!

According to the debugging information, we can see that the class used to process the request parameters is handlermethodargumentresolvercomposite, which is the implementation class of the handlermethodargumentresolver interface. At this time, the parameter being processed is a student object, and the value note has been bound. That is to say, the method resolveargument is actually binding

resolveArgument

Resolveargument is the method that actually performs binding

/**
 * Iterate over registered
 * {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers}
 * and invoke the one that supports it.
 * @throws IllegalArgumentException if no suitable argument resolver is found
 */
@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
  NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

 //Get the appropriate parameter parser
 HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
 if (resolver == null) {
  throw new IllegalArgumentException("Unsupported parameter type [" +
    parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
 }
 //Perform parameter binding
 return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
Copy code

getArgumentResolver

Getargumentresolver this method is used to perform the binding of parameters, which is defined as follows

/**
 * Find a registered {@link HandlerMethodArgumentResolver} that supports
 * the given method parameter.
 */
@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
 HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
 if (result == null) {
  for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
   if (resolver.supportsParameter(parameter)) {
    result = resolver;
    this.argumentResolverCache.put(parameter, result);
    break;
   }
  }
 }
 return result;
}
Copy code

The logic of this method is to find the handlermethodargumentresolver that can perform parameter binding from the argumentresolver cache. If not, find it from the handlermethodargumentresolver. Spring MVC supports 26 kinds of handlermethodargumentresolvers, which are used to parse various types of parameters

Spring MVC request processing. Your answer is guaranteed to pass the interview!

According to the debugging of the blogger, we can know

  • Requestparammethodargumentresolver: handles common parameters (basic type, packing type, string) with or without @ requestparam annotation
  • Servletmodelattributemethodprocessor: handles POJO type parameters, such as custom student objects
  • Requestresponsebodymethodprocessor: handles parameters of the @ requestbody annotation type

resolveArgument

Since different types of parameters have different handlermethodargumentresolver to process, the POJO type parameter injection implementation is selected here, and the corresponding parameter parsing class is modelattributemethodprocessor. The resolveargument method is used to parse (bind) parameters. The method definition is as follows

/**
 * Resolve the argument from the model or if not found instantiate it with
 * its default if it is available. The model attribute is then populated
 * with request values via data binding and optionally validated
 * if {@code @java.validation.Valid} is present on the argument.
 * @throws BindException if data binding and validation result in an error
 * and the next method parameter is not of type {@link Errors}
 * @throws Exception if WebDataBinder initialization fails
 */
@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");

 //Get parameter name
 String name = ModelFactory.getNameForParameter(parameter);
 //Gets the modelattribute annotation on the 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 {
   //To create an instance of a parameter type (without injecting a value), the underlying layer is to call the construction method through reflection
   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();
   }
   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)) {
    //Methods that actually perform binding (value injection)
    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;
}
Copy code

Spring MVC request processing. Your answer is guaranteed to pass the interview!

According to the debugging information, you can also see that after bindrequestparameters (binder, webrequest) is executed, POJO type parameters have been bound.

bindRequestParameters

/**
 * This implementation downcasts {@link WebDataBinder} to
 * {@link ServletRequestDataBinder} before binding.
 * @see ServletRequestDataBinderFactory
 */
@Override
protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
 ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);
 Assert.state(servletRequest != null, "No ServletRequest");
 ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder;
 //Method to perform binding
 servletBinder.bind(servletRequest);
}
Copy code

bind

Continue to explore the bind method

public void bind(ServletRequest request) {
 //Get the key value pairs of all parameters
 MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);
 //Processing file upload request
 MultipartRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartRequest.class);
 if (multipartRequest != null) {
  bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
 }
 //Add the parameters carried in the URL to mutablepropertyvalues
 addBindValues(mpvs, request);
 //Execute binding (inject value)
 doBind(mpvs);
}
Copy code

Because the calling level is too deep, the following steps can’t be listed step by step. The principle of dobind method is to set the value by calling the setter method in POJO object to view the final debugging information

Spring MVC request processing. Your answer is guaranteed to pass the interview!

According to the debugging information, it can be seen that the setter method of POJO object will be executed in the end, and the specific class to be executed is beanwrapperimpl.

After understanding the binding of parameters, let’s look at the processing of return values.

Return value processing

invokeAndHandle

Back to the source invokeandhandle method (in the servletinvocablehandlermethod class), the method is defined as follows

/**
 * Invoke the method and handle the return value through one of the
 * configured {@link HandlerMethodReturnValueHandler HandlerMethodReturnValueHandlers}.
 * @param webRequest the current request
 * @param mavContainer the ModelAndViewContainer for this request
 * @param providedArgs "given" arguments matched by type (not resolved)
 */
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
  Object... providedArgs) throws Exception {

 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 {
  //How to deal with different types of return values
  this.returnValueHandlers.handleReturnValue(
    returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
 }
 catch (Exception ex) {
  if (logger.isTraceEnabled()) {
   logger.trace(formatErrorForReturnValue(returnValue), ex);
  }
  throw ex;
 }
}
Copy code

The real way to handle different types of return values is the handlereturnvalue method

handleReturnValue

/**
 * Iterate over registered {@link HandlerMethodReturnValueHandler HandlerMethodReturnValueHandlers} and invoke the one that supports it.
 * @throws IllegalStateException if no suitable {@link HandlerMethodReturnValueHandler} is found.
 */
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
  ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

 //Select the appropriate handlermethodreturnvaluehandler according to the return value type
 HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
 if (handler == null) {
  throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
 }
 //The real processing return value
 handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
Copy code

selectHandler

@Nullable
private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
 boolean isAsyncValue = isAsyncReturnValue(value, returnType);
 for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
  if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
   continue;
  }
  if (handler.supportsReturnType(returnType)) {
   return handler;
  }
 }
 return null;
}
Copy code

Spring MVC request processing. Your answer is guaranteed to pass the interview!

According to the debugging information, spring MVC provides 15 implementations of handlermethodreturnvaluehandler for return values to handle different types of return values.

In fact, the requestresponsebody methodprocessor is used to handle the @ ResponseBody type.

If you have any impression of parameter binding, you will find that the @ requestbody type parameter binding also uses this class.

Continue to follow up the handlereturnvalue method of the requestresponsebodymethodprocessor class

handleReturnValue

The handlereturnvalue method of the requestresponsebodymethodprocessor class is defined as follows

Here, a very important property, requesthandled, is set, which is related to whether the modelandview object needs to be returned

@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
  ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
  throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

 //Set whether the request has been fully processed in the handler. For example, the @ ResponseBody method does not need a view parser, so it can be set to true here.
 //This flag can also be set to true when the controller method declares a parameter of type servletresponse or OutputStream. 
 //When this property is set to true, the upper layer getmodelandview will return null when getting modelandview, because the view is not required.
 //The default value is false
 mavContainer.setRequestHandled(true);
 ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
 ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);

 // Try even with null return value. ResponseBodyAdvice could get involved.
 //The bottom layer is to use Java. Io. Outputstreamwriter class to write the return value to network io
 writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
Copy code

Continue to go deep into the writewithmessage converters method, and debug step by step to the end. The bottom layer is to use the java.io.outputstreamwriter class to write the return value to the network io

Spring MVC request processing. Your answer is guaranteed to pass the interview!

Because handlereturnvalue sets the requesthandled to true, the upper layer will return null when calling the getmodelandview method, indicating that the request does not need a view. If you are interested in it, you can adjust it by yourself.

summary

This paper mainly from the perspective of source code reading and debugging, explains the whole process of spring MVC processing request, and explains the binding of parameters and the processing of return value. I believe you will have a more in-depth understanding of the request processing process of spring MVC with your own debugging information.

Link:https://juejin.cn/post/690348…