主頁 > 後端開發 > Spring MVC請求處理程序。你這樣回答保證通過面試!

Spring MVC請求處理程序。你這樣回答保證通過面試!

2020-12-08 06:56:02 後端開發

前言

SpringMVC請求處理相信大家都很熟悉了,本篇主要是基于SpringMVC處理請求的流程來閱讀并除錯原始碼,以及解決幾個僅靠流程圖無法解釋的問題,

關于Spring MVC的流程思維導圖分享給大家:

Spring系列的學習筆記和面試題,包含spring面試題、spring cloud面試題、spring boot面試題、spring教程筆記、spring boot教程筆記、最新阿里巴巴開發手冊(63頁PDF總結)、2020年Java面試手冊,一共整理了1184頁PDF檔案,

關注公眾號:程式員白楠楠,即可獲取這份1184頁PDF檔案的spring全家桶資料,

本篇使用的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;

  • 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 MVC 相關的技術文章整理成了 PDF,老規矩,關注微信公眾號 Java后端 回復 666 下載,

除錯準備

搭建一個基本的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方法主要是從List handlerMappings集合中遍歷查找一個合適的處理器(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方法主要是從List handlerAdapters集合中遍歷查找一個合適的處理器配接器(HandlerAdapter),回傳的結果是一個HandlerAdapter,

可以看到此處HandlerAdapter真正的實作類是RequestMappingHandlerAdapter,

processDispatchResult

processDispatchResult方法主要根據方法執行完成后封裝的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 >
 <property name="prefix" value="https://www.cnblogs.com/WEB-INF/views/" />
 <property name="suffix" value="https://www.cnblogs.com/bainannan/p/.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 = https://www.cnblogs.com/bainannan/p/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物件,并且已經把值注系結了,也就是說真正執行系結的是方法resolveArgument

resolveArgument

resolveArgument是真正執行系結的的方法

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

 // 獲取合適的引數決議器
 HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
 if (resolver == null) {
  throw new IllegalArgumentException("Unsupported parameter type [" +
    parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
 }
 // 執行引數系結
 return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}

getArgumentResolver

getArgumentResolver該方法用于執行引數的系結,定義如下

/**
 * 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 = https://www.cnblogs.com/bainannan/p/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 = https://www.cnblogs.com/bainannan/p/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方法

handleReturnValue

RequestResponseBodyMethodProcessor類的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的請求處理程序有一個更深入的理解,

Spring系列的學習筆記和面試題,包含spring面試題、spring cloud面試題、spring boot面試題、spring教程筆記、spring boot教程筆記、最新阿里巴巴開發手冊(63頁PDF總結)、2020年Java面試手冊,一共整理了1184頁PDF檔案,

關注公眾號:程式員白楠楠,即可獲取這份1184頁PDF檔案的spring全家桶資料,



轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/231291.html

標籤:Java

上一篇:阿里終面:怎么用 UDP 實作 TCP?

下一篇:有沒有大哥系統里面整合過flowable

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more