面试官:你画的SpringMVC请求处理过程是从网上抄的吧?

Spring Boot 学习之路 -- Service 层 在实际开发中,Service 层主要负责业务模块的逻辑应用设计。在设计 Service 层的过程中,首先设计接口,然后设计接口的实现类。通常情况下,Service 层用于封装项目中一些通用的业务逻辑,这么做的好处是有利于业务逻辑的独立性和重复利用性。因此,为了处理一个 Spring Boot 项目中的业务逻辑,Service 层是不可或缺的。 阅读详情

作者:Sicimike

原文:blog.youkuaiyun.com/Baisitao_/article/details/107471719

前言

SpringMVC请求处理相信大家都很熟悉了,本篇主要是基于SpringMVC处理请求的流程来阅读并调试源码,以及解决几个仅靠流程图无法解释的问题。

“

本篇使用的Spring版本为5.2.2.RELEASE

”

九大组件

SpringMVC几乎所有的功能都由九大组件来完成,所以明白九大组件的作用,对于学习SpringMVC来说非常重要。

/** 文件上传解析器 */
private MultipartResolver multipartResolver;

/** 区域解析器,用于国际化 */
private LocaleResolver localeResolver;

/** 主题解析器 */
private ThemeResolver themeResolver;

/** Handler映射信息 */
private List<HandlerMapping> handlerMappings;

/** Handler适配器*/
private List<HandlerAdapter> handlerAdapters;

/** Handler执行异常解析器 */
private List<HandlerExceptionResolver> handlerExceptionResolvers;

/** 请求到视图的转换器 */
private RequestToViewNameTranslator viewNameTranslator;

/** SpringMVC允许重定向时携带参数,存在session中,用完就销毁,所以叫FlashMap */
private FlashMapManager flashMapManager;

/** 视图解析器 */
private List<ViewResolver> viewResolvers;
1234567891011121314151617181920212223242526
  • HandlerMapping:Handler映射信息,根据请求携带的url信息查找处理器(Handler)。每个请求都需要对应的Handler处理。

  • HandlerAdapter:Handler适配器,SpringMVC没有直接调用处理器(Handler),而是通过HandlerAdapter来调用,主要是为了统一Handler的调用方式

  • ViewResolver:视图解析器,用来将字符串类型的视图名称解析为View类型的视图。ViewResolver需要找到渲染所用的模板和所用的技术(也就是视图的类型)进行渲染,具体的渲染过程则交由不同的视图自己完成。

  • MultipartResolver:文件上传解析器,主要用来处理文件上传请求

  • HandlerExceptionResolver:Handler执行异常解析器,用来对异常进行统一处理

  • RequestToViewNameTranslator:请求到视图的转换器

  • LocaleResolver:区域解析器,用于支持国际化

  • FlashMapManager:SpringMVC允许重定向时携带参数,存在session中,用完就销毁,所以叫FlashMap

  • ThemeResolver:主题解析器,用于支持不同的主题九大组件中最重的的前三个,HandlerMapping、HandlerAdapter和ViewResolver,因为这是阅读源码时,避不开的三个组件。

调试准备

搭建一个基本的Spring web项目即可

Controller部分

@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";
    }
}

Entity部分

public class Student {

    private String name;
    private Integer gender;

   // getter、setter
}

还是那句话,Spring源码非常庞大,不能只见树木不见森林,需要有针对性的阅读,所以本篇只需要关注主体流程即可。

核心方法

我们都知道,SpringMVC有一个用来分发请求的前端控制器DispatcherServlet,其中用来处理请求的方法就是doService,该方法定义如下

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 {
  // 真正执行的方法
  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);
   }
  }
 }
}

doDispatch

doDispatch是doService中真正用来处理请求的方法

/**
 * 实际处理请求的方法
 */
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
 HttpServletRequest processedRequest = request;
 HandlerExecutionChain mappedHandler = null;
 boolean multipartRequestParsed = false;

 WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

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

  try {
   // 校验是否是文件上传请求
   processedRequest = checkMultipart(request);
   multipartRequestParsed = (processedRequest != request);

   // Determine handler for the current request.
   // 为当前请求找到一个合适的处理器(Handler)
   // 返回值是一个HandlerExecutionChain,也就是处理器执行链
   mappedHandler = getHandler(processedRequest);
   if (mappedHandler == null) {
    noHandlerFound(processedRequest, response);
    return;
   }

   // Determine handler adapter for the current request.
   // 根据HandlerExecutionChain携带的Handler找到合适的HandlerAdapter
   HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

   // Process last-modified header, if supported by the handler.
   // 处理GET请求的缓存
   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方法
   if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    return;
   }

   // Actually invoke the handler.
   // 利用HandlerAdapter来执行Handler里对应的处理方法
   mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

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

   // 如果没有设置视图,则应用默认的视图名
   applyDefaultViewName(processedRequest, mv);
   // 执行拦截器的postHandle方法
   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);
  }
  // 根据ModelAndView对象解析视图
  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);
   }
  }
 }
}

该方法就是SpringMVC处理请求的整体流程,其中涉及到几个重要的方法。

getHandler

该方法定义如下

/**
 * Return the HandlerExecutionChain for this request.
 * 为这个request返回一个HandlerExecutionChain
 */
@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;
}

调试信息如下

根据调试信息可以看出,getHandler方法主要是从ListhandlerMappings集合中遍历查找一个合适的处理器(Handler),返回的结果是一个HandlerExecutionChain。然后再根据HandlerExecutionChain里携带的Handler去获取HandlerAdapter。

getHandlerAdapter

getHandlerAdapter方法定义如下

/**
  * 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");
 }

调试信息如下

同样getHandlerAdapter方法主要是从ListhandlerAdapters集合中遍历查找一个合适的处理器适配器(HandlerAdapter),返回的结果是一个HandlerAdapter。

可以看到此处HandlerAdapter真正的实现类是RequestMappingHandlerAdapter。

processDispatchResultprocessDispatchResult方法主要根据方法执行完成后封装的ModelAndView,转发到对应页面,定义如下

/**
 * 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()) {
  // 主要调用该方法渲染视图
  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);
 }
}

render

render方法定义如下

/**
 * 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.
  // 根据给定的视图名称,解析获取View对象
  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;
 }
}

resolveViewName

resolveViewName方法定义如下

@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;
}

调试信息如下

根据调试信息可以看到真正解析视图的ViewResolver的是InternalResourceViewResolver类,也就是我们经常配置的一项类型

<!-- 定义视图文件解析 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
 <property name="prefix" value="/WEB-INF/views/" />
 <property name="suffix" value=".html" />
</bean>

至此我们就得到了SpringMVC处理请求的完整逻辑SpringMVC处理请求的整个流程已经梳理清楚了。

但是,有两个重要的问题没有解决,那就是:参数绑定和返回值处理。

“

因为在编写Controller里面的方法的时候,各种类型的参数都有,SpringMVC是怎么处理不同类型的参数的呢?SpringMVC处理请求完成后,一定会返回ModelAndView吗,如果加了@ResponseBody注解呢?

”

参数绑定

在整个流程中,还有一个最重要的方法,那就是真正执行handler的方法,参数的绑定和返回值的处理都在这个方法里,也就是

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

handle

handle方法的作用是根据请求参数,执行真正的处理方法,并且返回合适的ModelAndView对象,也有可能返回null。该方法定义如下在AbstractHandlerMethodAdapter类中

/**
 * 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);
}

可以看到这个方法实现只有一行代码

handleInternal

继续深入handleInternal方法

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

 ModelAndView mav;
 // 校验指定的请求以获取受支持的方法类型(GET、POST等)和所需的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...
  // 真正执行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;
}

invokeHandlerMethod

继续深入invokeHandlerMethod方法

/**
 * Invoke the {@link RequestMapping} handler method preparing a {@link ModelAndView}
 * if view resolution is required.
 * 执行@RequestMapping标注的handler方法,如果需要解析视图就准备一个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);

  // HandlerMethod接口封装执行方法的信息,提供对方法参数,方法返回值,方法注释等的便捷访问。
  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可以看做ModelAndView的上下文容器,关联着Model和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);
  }

  // 真正执行Handler的方法
  invocableMethod.invokeAndHandle(webRequest, mavContainer);
  if (asyncManager.isConcurrentHandlingStarted()) {
   return null;
  }

  // 获取ModelAndeView对象
  return getModelAndView(mavContainer, modelFactory, webRequest);
 }
 finally {
  webRequest.requestCompleted();
 }
}

invokeAndHandle

invokeAndHandle方法的作用是执行并处理真正响应请求的方法,该方法定义如下

/**
 * 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 {

 // 执行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;
 }
}

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 {

 // 获取参数
 Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
 if (logger.isTraceEnabled()) {
  logger.trace("Arguments: " + Arrays.toString(args));
 }
 // 执行
 return doInvoke(args);
}

真正的执行无非就是通过反射invoke,所以更重要的是参数是如何绑定的,详情就在getMethodArgumentValues方法

getMethodArgumentValues

getMethodArgumentValues方法用于从request请求中获取真正的参数,返回的是Object数组,该方法定义如下

/**
 * 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 {

 // 获取方法上所有的参数
 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;
}

根据调试信息可以看到,用来处理请求参数的类是HandlerMethodArgumentResolver接口的实现类HandlerMethodArgumentResolverComposite,此时正在处理的参数是一个Student对象,并且已经把值注绑定了,也就是说真正执行绑定的是方法resolveArgumentresolveArgument

resolveArgument是真正执行绑定的的方法

根据调试信息可以看到,用来处理请求参数的类是HandlerMethodArgumentResolver接口的实现类HandlerMethodArgumentResolverComposite,此时正在处理的参数是一个Student对象,并且已经把值注绑定了,也就是说真正执行绑定的是方法resolveArgument

resolveArgument
resolveArgument是真正执行绑定的的方法

getArgumentResolvergetArgumentResolver该方法用于执行参数的绑定,定义如下

/**
 * 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;
}

该方法的逻辑就是先从argumentResolver缓存中找到能够执行参数绑定的HandlerMethodArgumentResolver,如果找不到就从HandlerMethodArgumentResolver找,SpringMVC支持的HandlerMethodArgumentResolver一共有26种,用来解析各种类型的参数

                                    根据博主的调试可以知道

RequestParamMethodArgumentResolver:处理普通参数(基本类型、包装类型、String),不管加不加@RequestParam注解ServletModelAttributeMethodProcessor:处理POJO类型的参数,比如自定义的Student对象RequestResponseBodyMethodProcessor:处理@RequestBody注解类型的参数有兴趣的同学可以试试更多不同形式的参数

resolveArgument

由于不同类型的参数有不同的HandlerMethodArgumentResolver来处理,此处选取POJO类型参数的注入实现,对应的参数解析类是ModelAttributeMethodProcessor,其中resolveArgument方法用来解析(绑定)参数方法定义如下

/**
 * 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");

 // 获取参数名
 String name = ModelFactory.getNameForParameter(parameter);
 // 获取参数上的ModelAttribute注解
 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 {
   // 创建参数类型的实例(未注入值),底层就是通过反射调用构造方法
   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)) {
    // 真正执行绑定(值注入)的方法
    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;
}

根据调试信息也可以看到bindRequestParameters(binder, webRequest)执行完成之后,POJO类型的参数已经完成了绑定。

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;
 // 执行绑定的方法
 servletBinder.bind(servletRequest);
}

bind

继续深入bind方法

public void bind(ServletRequest request) {
 // 获取所有参数的键值对
 MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);
 // 处理文件上传请求
 MultipartRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartRequest.class);
 if (multipartRequest != null) {
  bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
 }
 // 把url中携带的参数也加入到MutablePropertyValues
 addBindValues(mpvs, request);
 // 执行绑定(注入值)
 doBind(mpvs);
}

由于调用层次过深,所以无法一步步列出下面的步骤,doBind方法的原理还是通过调用POJO对象里的setter方法设置值,可以查看最终的调试信息

根据调试信息可以看到,最终执行的还是POJO对象的setter方法,具体执行的类是BeanWrapperImpl。

了解了参数的绑定,再来看返回值的处理。

返回值处理

invokeAndHandle回到源码invokeAndHandle方法处(ServletInvocableHandlerMethod类中),该方法定义如下

/**
 * 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 {
  // 真正处理不同类型返回值的方法
  this.returnValueHandlers.handleReturnValue(
    returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
 }
 catch (Exception ex) {
  if (logger.isTraceEnabled()) {
   logger.trace(formatErrorForReturnValue(returnValue), ex);
  }
  throw ex;
 }
}

真正处理不同类型的返回值的方法是handleReturnValue方法

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 {

 // 根据返回值个返回值类型选取合适的HandlerMethodReturnValueHandler
 HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
 if (handler == null) {
  throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
 }
 // 真正的处理返回值
 handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}

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;
}

根据调试信息可以看到,SpringMVC为返回值提供了15个HandlerMethodReturnValueHandler的实现了来处理不同类型的返回值。

事实上,用来处理@ResponseBody类型的是RequestResponseBodyMethodProcessor。

如果对前文参数绑定还有印象的话,会发现@RequestBody类型参数绑定也是用的这个类。

继续跟进RequestResponseBodyMethodProcessor类的handleReturnValue方法handleReturnValueRequestResponseBodyMethodProcessor类的handleReturnValue方法定义如下

这里设置了一个非常重要的属性requestHandled,这个属性关系到是否需要返回ModelAndView对象

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

 // 设置该请求是否已在处理程序中完全处理,例如@ResponseBody方法不需要视图解析器,此处就可以设置为true。
 // 当控制器方法声明类型为ServletResponse或OutputStream的参数时,也可以设置此标志为true。 
 // 这个属性设置成true之后,上层getModelAndView获取ModelAndView时会返回Null,因为不需要视图。
 // 默认值为false
 mavContainer.setRequestHandled(true);
 ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
 ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);

 // Try even with null return value. ResponseBodyAdvice could get involved.
 // 底层就是利用java.io.OutputStreamWriter类把返回值写到网络IO
 writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}

继续深入writeWithMessageConverters方法,一步步调试到最后,底层就是利用java.io.OutputStreamWriter类把返回值写到网络IO由于handleReturnValue把requestHandled设置成了true,上层在调用getModelAndView方法时会返回null,表示该请求不需要视图。感兴趣的同学自己调试一下便知。

总结

本文主要从源码的阅读和调试的角度,整体的讲解了SpringMVC处理请求的整个流程,并且讲解了参数的绑定以及返回值的处理。相信大家看完后,结合自己的调试信息,会对SpringMVC的请求处理过程有一个更深入的理解。

避坑指南:英飞凌TC3XX的MCMCAN初始化常见错误及解决方法 本文针对英飞凌TC3XX系列MCU的MCMCAN模块初始化难题,深入剖析了寄存器配置时序、总线监控模式误用、中断映射错误及Message RAM地址冲突四大常见陷阱。通过实战代码示例和诊断流程,提供了清晰的避坑指南和解决方案,帮助嵌入式开发者高效解决CAN FD通信故障,确保汽车电子与工业控制项目的稳定运行。 阅读详情

相关推荐

Unity 升级 xLua 版本,编译不同平台链接库

Unity 升级 xLua 详解

LymanCao的专栏 1803

06、SpringBoot —— Service层

大多数的SpringBoot项目采用接口模式实现Service层。按照@Service的value属性映射服务类的对象。@Service和@Repository的联系与区别。Service层与@Service注解。按照实现类的服务名称映射服务类对象。Service层的实现过程。同时存在多个实现类的情况。不采用接口模式的服务类。

weixin_73389510的博客 2357

【读代码】TradingAgents:基于多智能体LLM的金融交易框架深度解析

TradingAgents是由Tauric Research团队开源的创新型金融交易框架,其核心思想是通过多智能体协作系统模拟专业交易机构的决策流程。项目采用模块化设计,整合了LLM、实时数据分析、风险控制等组件,实现了从市场分析到交易执行的完整闭环。

kakaZhui的博客 2005

springMVC-doService()分析

1、request到dispaterServlet -> doservice( request,response){     1、保存请求快照     2、共享上下文信息,本地解析,主题解析等对象给 handler和view对象     3、          } -->doDispatch( request,reponse){     1、判断request是否是文件上传请求

飞雪迎春 1595

【Java】利用反射执行Spring容器Bean指定的方法,支持多种参数自动调用

目录 使用情景 目的 实现方式 前提: 思路 核心类 测试方法 使用情景 将定时任务录入数据库(这样做的好处是定时任务可视化,也可以动态修改各个任务的执行时间),通过反射执行对应的方法; 配合Netty实现简单的HTTP请求处理框架 其他需要使用反射执行Spring方法的业务亦可 目的 很多文章都提到了反射,但是对于方法参数处理这一块都是明确了类型,不支持按照实际参数动态转换,而本篇文章提供了一个思路怎么做到方法参数的动态调用。 大家也可以...

ITjianshuzhai的博客 2335

亚马逊SP-API授权实战:从OAuth流程到Refresh Token获取

本文详细解析了亚马逊SP-API授权流程,从OAuth认证到Refresh Token获取的全过程。通过实战经验分享,帮助开发者快速掌握Marketplace Appstore、Website Workflow和自行授权三种方式,解决常见错误如MD5100和MD9999,并提供了安全存储Refresh Token的最佳实践。

weixin_30379531的博客 519

Spring MVC源码分析(三):请求分发器DispatcherServlet的设计与实现

一、概述 DispatcherServlet是SpringMVC的一个前端控制器,是MVC架构中的C,即controller的实现,用于拦截这个web应用的所有请求,具体为在web.xml中配置这个servlet,对应的url-pattern设置为“/”,或者使用servlet3.0之后的WebApplicationInitializer来配置,在web容器启动这个应用时,会创建和初始化这个DispatcherServlet对象实例。 DispatcherServlet在接收到请求之后,会根据请求的ur

半生苦涩半生酸的博客 1023

用miniconda建立PyTorch、Keras、TensorFlow三个环境

如果还没有安装Miniconda,请从[Miniconda官网](https://docs.conda.io/en/latest/miniconda.html)下载合适的版本并进行安装。如果遇到问题,可以通过编辑`~/.condarc`文件来临时禁用某个源,或者通过`conda config --remove channels`命令来移除配置的镜像源。要切换到不同的环境,先使用`conda deactivate`命令退出当前环境,再用`conda activate 环境名称`命令激活另一个环境。

数字人生 2464

面试官:小伙汁,你画的SpringMVC请求处理过程是从网上抄的吧?

Handler参数是怎么绑定的? @ResponseBody注解为什么没有返回视图?

Sicimike的博客 3689

AI Agent Harness Engineering 开发效率工具:Prompt工程、调试与监控平台

随着AI Agent技术从原型探索走向企业级落地,开发者普遍面临Prompt迭代无版本管控、Agent执行黑盒无法定位错误、上线后无监控导致故障频发、多团队协作效率低下四大核心痛点,据2024年LLMOps行业报告显示,没有配套工具链的Agent开发团队平均迭代周期是有工具链团队的6倍,线上故障率高达23%,平均资源浪费超过60%。

小白菜的博客 427

Windows原生环境配置Tensorflow使用GPU

本文讲解如何在Windows原生环境搭建可调用GPU设备进行计算的Tensorflow2.x环境。 区别于WSL,使用Windows原始环境作为开发环境。

天然玩家的博客 1502

springmvc重定向到另一个项目_面试官:你画的SpringMVC请求处理过程是从网上抄的吧?...

# 前言SpringMVC请求处理相信大家都很熟悉了,本篇主要是基于SpringMVC处理请求的流程来阅读并调试源码,以及解决几个仅靠流程图无法解释的问题。说明:本篇使用的Spring版本为5.2.2.RELEASE# 九大组件SpringMVC几乎所有的功能都由九大组件来完成,所以明白九大组件的作用,对于学习SpringMVC来说非常重要。/** 文件上传解析器 */private M...

weixin_27374911的博客 92

中小企业办公网络配置综合实验华为设备(NAT+策略路由+OSPF+VLAN)

组网需求: 1、PC1、2、3、4均可以访问internet的lo0:8.8.8.8 2、PC1、PC4通过电信ISP访问internet;PC2、PC3通过联通ISP访问internet; 需求分析: 1、internet路由器模拟公网,8.8.8.8模拟公网IP;电信ISP、联通ISP路由器模拟电信和联通运营商;出口路由为企业出口路由器;这四个路由器直连链路之间运行ospf,配置动态路由,实现互通; 2、出口路由与三层交换直连链路运行ospf,动态同步路由信息; 3、出口路由器内网端口配..

A soul tortured by desire 7125

springmvc如何使用视图解析器_面试官:小伙汁,你画的SpringMVC请求处理过程是从网上抄的吧?

前言SpringMVC请求处理相信大家都很熟悉了,本篇主要是基于SpringMVC处理请求的流程来阅读并调试源码,以及解决几个仅靠流程图无法解释的问题。本篇使用的Spring版本为5.2.2.RELEASE九大组件SpringMVC几乎所有的功能都由九大组件来完成,所以明白九大组件的作用,对于学习SpringMVC来说非常重要。/** 文件上传解析器 */ private MultipartRes...

weixin_40009207的博客 78

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

springmvc流程图_面试官:小伙子,你画的SpringMVC请求处理过程是从网上抄的吧?...

前言SpringMVC请求处理相信大家都很熟悉了,本篇主要是基于SpringMVC处理请求的流程来阅读并调试源码,以及解决几个仅靠流程图无法解释的问题。本篇使用的Spring版本为5.2.2.RELEASE九大组件SpringMVC几乎所有的功能都由九大组件来完成,所以明白九大组件的作用,对于学习SpringMVC来说非常重要。/** 文件上传解析器 */ private MultipartRes...

weixin_39819661的博客 73

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

陌陌面试官:说说Spring AOP 的原理、SpringMVC 的处理过程?

Spring AOP ,SpringMVC ,这两个应该是国内面试必问题,网上有很多答案,其实背背就可以。但今天笔者带大家一起深入浅出源码,看看他的原理。以期让印象更加深刻,面试的时候游刃有余。2021Java面试宝典 # Spring AOP 原理 简单说说 AOP 的设计: 每个 Bean 都会被 JDK 或者 Cglib 代理。取决于是否有接口。 每个 Bean 会有多个“方法拦截器”。注意:拦截器分为两层,外层由 Spring 内核控制流程,内层拦截器是用户设置,也就是 AOP。

程序员白楠楠的博客 252

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

面试官:今天要不来聊聊SpringMVC吧?

面试官:今天要不来聊聊SpringMVC吧? 候选者:我先简单说下我对SpringMVC的理解哈 候选者:SpringMVC我觉得它是对Servlet的封装,屏蔽掉Servlet很多的细节 候选者:举几个例子 候选者:可能我们刚学Servlet的时候,要获取参数需要不断的getParameter 候选者:现在只要在SpringMVC方法定义对应的JavaBean,只要属性名与参数名一致,SpringMVC就可以帮我们实现「将参数封装到JavaBean」上了 候选者:又比如,以前使用Servlet

分享Java技术知识,共同成长进步! 222
上一篇: 太赞了!LeetCode 电子书终于开放下载了,覆盖字节、蚂蚁、腾讯多家大厂真题(附5000页大厂试题)...
下一篇: 非常强悍的 RabbitMQ 总结,写得真好!
Java思维导图
博客等级 码龄10年 306粉丝 71原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值