목차
머리말" >머리말
디버깅 준비" >디버깅 준비
핵심 메서드" >핵심 메서드
参数绑定" >参数绑定
返回值处理" >返回值处理
Java java지도 시간 인터뷰어: 말씀하신 SpringMVC 요청 처리 프로세스는 인터넷에서 복사한 것 맞죠?

인터뷰어: 말씀하신 SpringMVC 요청 처리 프로세스는 인터넷에서 복사한 것 맞죠?

Jul 26, 2023 pm 04:34 PM
springmvc

머리말

이 글은 주로 SpringMVC 요청 처리 프로세스를 기반으로 소스 코드를 읽고 디버그하며, 순서도만으로는 설명할 수 없는 몇 가지 문제를 해결합니다.

"

이 글에서 사용한 Spring 버전은 5.2.2입니다.RELEASE

"

9가지 주요 구성 요소

SpringMVC의 거의 모든 기능은 9가지 주요 구성 요소로 완성되므로 9가지를 이해하세요. 주요 컴포넌트 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: 핸들러 매핑 정보, 요청에 포함된 URL 정보에 따라 핸들러(Handler)를 찾습니다. 각 요청에는 해당 핸들러 처리가 필요합니다.
  • HandlerAdapter: 핸들러 어댑터, SpringMVC는 프로세서(Handler)를 직접 호출하지 않고 HandlerAdapter를 통해 호출하는데 주로 Handler의 호출 방식을 통일하기 위해
  • ViewResolver: 뷰 파서, 문자열 유형을 변환하는 데 사용되는 뷰 이름은 View 유형의 뷰로 확인됩니다. ViewResolver는 렌더링에 사용되는 템플릿과 렌더링에 사용되는 기술(즉, 뷰 유형)을 찾아야 하며 특정 렌더링 프로세스는 다양한 뷰 자체에서 완료됩니다.
  • MultipartResolver: 주로 파일 업로드 요청을 처리하는 데 사용되는 파일 업로드 파서
  • HandlerExceptionResolver: 예외를 균일하게 처리하는 데 사용되는 핸들러 실행 예외 파서
  • RequestToViewNameTranslator: 변환기 보기 요청
  • LocaleResolver: 지역 국제화를 지원하는 데 사용되는 리졸버
  • FlashMapManager: SpringMVC는 리디렉션 중에 매개변수를 전달하고, 세션에 저장하고, 사용 후 제거할 수 있으므로 FlashMap
  • ThemeResolver라고 합니다. 테마 리졸버, 처음 세 가지 다양한 테마를 지원하는 데 사용되는 9가지 주요 구성 요소는 HandlerMapping, HandlerAdapter 및 ViewResolver입니다. 이는 소스 코드를 읽을 때 피할 수 없는 세 가지 구성 요소이기 때문입니다.

디버깅 준비

기본 Spring 웹 프로젝트만 빌드하세요

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

/**
 * 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&#39;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;
}
로그인 후 복사

调试信息如下

인터뷰어: 말씀하신 SpringMVC 요청 처리 프로세스는 인터넷에서 복사한 것 맞죠?

根据调试信息可以看出,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");
 }
로그인 후 복사

调试信息如下

인터뷰어: 말씀하신 SpringMVC 요청 처리 프로세스는 인터넷에서 복사한 것 맞죠?

同样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&#39;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 &#39;" + mv.getViewName() +
     "&#39; in servlet with name &#39;" + getServletName() + "&#39;");
  }
 }
 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 &#39;" + getServletName() + "&#39;");
  }
 }

 // 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;
}
로그인 후 복사

调试信息如下

인터뷰어: 말씀하신 SpringMVC 요청 처리 프로세스는 인터넷에서 복사한 것 맞죠?

根据调试信息可以看到真正解析视图的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 요청 처리 프로세스는 인터넷에서 복사한 것 맞죠?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;
}
로그인 후 복사
인터뷰어: 말씀하신 SpringMVC 요청 처리 프로세스는 인터넷에서 복사한 것 맞죠?

根据调试信息可以看到,用来处理请求参数的类是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种,用来解析各种类型的参数
인터뷰어: 말씀하신 SpringMVC 요청 처리 프로세스는 인터넷에서 복사한 것 맞죠?

                                    根据博主的调试可以知道
로그인 후 복사

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;
}
로그인 후 복사
인터뷰어: 말씀하신 SpringMVC 요청 처리 프로세스는 인터넷에서 복사한 것 맞죠?

根据调试信息也可以看到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方法设置值,可以查看最终的调试信息

인터뷰어: 말씀하신 SpringMVC 요청 처리 프로세스는 인터넷에서 복사한 것 맞죠?

根据调试信息可以看到,最终执行的还是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 요청 처리 프로세스는 인터넷에서 복사한 것 맞죠?

根据调试信息可以看到,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인터뷰어: 말씀하신 SpringMVC 요청 처리 프로세스는 인터넷에서 복사한 것 맞죠?由于handleReturnValue把requestHandled设置成了true,上层在调用getModelAndView方法时会返回null,表示该请求不需要视图。感兴趣的同学自己调试一下便知。

总结

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

위 내용은 인터뷰어: 말씀하신 SpringMVC 요청 처리 프로세스는 인터넷에서 복사한 것 맞죠?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.

핫 AI 도구

Undresser.AI Undress

Undresser.AI Undress

사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover

AI Clothes Remover

사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool

Undress AI Tool

무료로 이미지를 벗다

Clothoff.io

Clothoff.io

AI 옷 제거제

Video Face Swap

Video Face Swap

완전히 무료인 AI 얼굴 교환 도구를 사용하여 모든 비디오의 얼굴을 쉽게 바꾸세요!

뜨거운 도구

메모장++7.3.1

메모장++7.3.1

사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전

SublimeText3 중국어 버전

중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기

스튜디오 13.0.1 보내기

강력한 PHP 통합 개발 환경

드림위버 CS6

드림위버 CS6

시각적 웹 개발 도구

SublimeText3 Mac 버전

SublimeText3 Mac 버전

신 수준의 코드 편집 소프트웨어(SublimeText3)

SpringBoot와 SpringMVC의 비교 및 ​​차이점 분석 SpringBoot와 SpringMVC의 비교 및 ​​차이점 분석 Dec 29, 2023 am 11:02 AM

SpringBoot와 SpringMVC는 모두 Java 개발에서 일반적으로 사용되는 프레임워크이지만 둘 사이에는 몇 가지 분명한 차이점이 있습니다. 이 기사에서는 이 두 프레임워크의 기능과 용도를 살펴보고 차이점을 비교할 것입니다. 먼저 SpringBoot에 대해 알아봅시다. SpringBoot는 Spring 프레임워크를 기반으로 하는 애플리케이션의 생성 및 배포를 단순화하기 위해 Pivotal 팀에서 개발되었습니다. 독립 실행형 실행 파일을 구축하는 빠르고 가벼운 방법을 제공합니다.

SpringBoot와 SpringMVC의 차이점은 무엇입니까? SpringBoot와 SpringMVC의 차이점은 무엇입니까? Dec 29, 2023 am 10:46 AM

SpringBoot와 SpringMVC의 차이점은 무엇입니까? SpringBoot와 SpringMVC는 웹 애플리케이션 구축을 위한 매우 인기 있는 두 가지 Java 개발 프레임워크입니다. 별도로 사용되는 경우가 많지만 차이점은 분명합니다. 우선, SpringBoot는 Spring 프레임워크의 확장 또는 향상된 버전으로 간주될 수 있습니다. 개발자를 돕기 위해 Spring 애플리케이션의 초기화 및 구성 프로세스를 단순화하도록 설계되었습니다.

spring과 springmvc의 차이점은 무엇입니까? spring과 springmvc의 차이점은 무엇입니까? Dec 29, 2023 pm 05:02 PM

spring과 springmvc의 차이점: 1. 위치 지정 및 기능 2. 핵심 기능 4. 확장성 세부 소개: 1. 포지셔닝 및 기능 Spring은 종속성 주입, 관점 지향 프로그래밍, 트랜잭션 관리 및 기타 기능을 제공하는 포괄적인 애플리케이션 개발 프레임워크이며, Spring MVC는 엔터프라이즈 수준 애플리케이션의 개발을 단순화하도록 설계되었습니다. Spring 프레임워크는 웹 애플리케이션 개발에 사용되며 MVC 패턴을 구현합니다. 2. 핵심 기능 등.

SpringBoot와 SpringMVC의 차이점은 무엇입니까? SpringBoot와 SpringMVC의 차이점은 무엇입니까? Dec 29, 2023 pm 05:19 PM

SpringBoot와 SpringMVC는 Java 개발에 일반적으로 사용되는 두 가지 프레임워크입니다. 둘 다 Spring 프레임워크에서 제공되지만 기능과 사용법에 약간의 차이가 있습니다. 이번 글에서는 SpringBoot와 SpringMVC의 특징과 차이점을 각각 소개하겠습니다. 1. SpringBoot의 특징: 단순화된 구성: SpringBoot는 구성보다 관례의 원칙을 통해 프로젝트 구성 프로세스를 크게 단순화합니다. 프로젝트와 개발자가 요구하는 매개변수를 자동으로 구성할 수 있습니다.

springboot와 springmvc의 차이점은 무엇입니까? springboot와 springmvc의 차이점은 무엇입니까? Jun 07, 2023 am 10:10 AM

springboot와 springmvc의 차이점은 다음과 같습니다. 1. 다양한 구성 3. 다양한 개발 시간 5. 다양한 JAR 패키징 기능 구현 여부 제공되는 기능 8. 다양한 기능 9. 다양한 커뮤니티 및 문서 지원 10. 배포 설명이 필요한지 여부

Java API 개발에서 웹 서비스 처리를 위해 SpringMVC 사용 Java API 개발에서 웹 서비스 처리를 위해 SpringMVC 사용 Jun 17, 2023 pm 11:38 PM

인터넷의 발달로 웹 서비스가 점점 더 보편화되고 있습니다. 애플리케이션 프로그래밍 인터페이스인 JavaAPI는 다양한 애플리케이션 시나리오에 적응하기 위해 지속적으로 새 버전을 출시하고 있습니다. 널리 사용되는 오픈 소스 프레임워크인 SpringMVC는 웹 애플리케이션을 쉽게 구축하는 데 도움을 줍니다. 이 기사에서는 SpringMVC 구성, 컨트롤러 작성 및 사용을 포함하여 JavaAPI 개발에서 웹 서비스 처리에 SpringMVC를 사용하는 방법을 자세히 설명합니다.

Java의 SpringMVC 인터셉터를 사용하는 방법 Java의 SpringMVC 인터셉터를 사용하는 방법 May 13, 2023 pm 02:55 PM

인터셉터의 역할 SpringMVC의 인터셉터는 프로세서를 전처리하고 후처리하는 데 사용되는 서블릿 개발의 필터와 유사합니다. 인터셉터들은 일정한 순서로 체인으로 연결되는데, 이 체인을 인터셉터 체인(InterceptorChain)이라고 합니다. 인터셉트된 메서드나 필드에 액세스하면 인터셉터 체인의 인터셉터가 이전에 정의된 순서대로 호출됩니다. 인터셉터는 AOP 아이디어의 구체적인 구현이기도 합니다. 인터셉터와 필터의 차이점: 필터(Filter) 인터셉터(Intercepter)의 사용 범위는 서블릿 사양의 일부이며 모든 JavaWeb 프로젝트에서 사용할 수 있습니다.

SpringBoot와 SpringMVC의 유사점과 차이점 비교 SpringBoot와 SpringMVC의 유사점과 차이점 비교 Dec 29, 2023 am 08:30 AM

SpringBoot와 SpringMVC의 유사점과 차이점 분석 SpringBoot와 SpringMVC는 Java 분야에서 매우 중요한 개발 프레임워크입니다. 둘 다 Spring 프레임워크의 일부이지만 사용법과 기능에는 몇 가지 명백한 차이점이 있습니다. 이 기사에서는 SpringBoot와 SpringMVC를 비교하고 이들 간의 유사점과 차이점을 분석합니다. 먼저 SpringBoot에 대해 알아봅시다. 스프링보

See all articles