RequestMappingHandlerAdapter詳解

来源:https://www.cnblogs.com/Xianhuii/archive/2023/01/01/17018699.html
-Advertisement-
Play Games

RequestMappingHandlerAdapter是Spring Web MVC中針對@Controller和@RequestMapping體系的處理器適配器,本文對RequestMappingHandlerAdapter的組成、初始化以及同步請求處理流程進行詳細梳理和總結。 ...


RequestMappingHandlerAdapter是日常項目中使用最多的HandlerAdapter實現類。

它還有一個抽象父類AbstractHandlerMethodAdapter,顧名思義,是專門用來處理HandlerMethod類型的handler。具體可以看AbstractHandlerMethodAdapter#supports方法:

public final boolean supports(Object handler) {  
   return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));  
}

通過之前的學習可以知道,RequestMappingHandlerMapping獲取的handler就是HandlerMethod類型的。

RequestMappingHandlerMappingRequestMappingHandlerAdapter就像一對孿生兄弟:

  1. RequestMappingHandlerMapping負責根據request找到映射的handler
  2. RequestMappingHandlerAdapter負責根據handler執行對應的方法

我們先總結RequestMappingHandlerAdapter處理handler的核心流程:

  1. requestresponse封裝成ServletWebRequest對象。
  2. handler封裝成ServletInvocableHandlerMethod對象invocableMethod
  3. invocableMethod設置argumentResolversreturnValueHandlersdataBinderFactoryparameterNameDiscoverer等工具。
  4. 解析請求參數。
  5. 執行方法。
  6. 處理返回值。

實際上,RequestMappingHandlerAdapter處理handler過程中還有許多細節,比如前後端不分離項目的視圖相關處理(沒有必要花費時間深入學習),非同步請求的相關處理(會另外寫文章)。

0 預備知識

RequestMappingHandlerAdapter中有許多成員變數,在請求處理過程中起著重要的作用。

0.1 argumentResolvers

argumentResolvers是參數解析器,RequestMappingHandlerAdapter使用argumentResolvers進行參數解析。

簡單來說,就是將HTTP請求中的數據,轉換成handler方法中的形參對象。

argumentResolvers使用了組合模式,它的類型是HandlerMethodArgumentResolverComposite,其內部緩存HandlerMethodArgumentResolver對象,用來進行參數解析。

HandlerMethodArgumentResolverComposite中包含argumentResolversargumentResolverCache兩個成員變數。在初始化時,會將所有配置的參數解析器緩存到argumentResolvers中。第一次解析參數時,會遍歷argumentResolvers獲取對應參數解析器,並緩存到argumentResolverCache中,後續再次解析該參數可直接從鍵值對中獲取,提高效率。

實際進行參數解析的是HandlerMethodArgumentResolver實現類。它們使用了策略模式,通過supportsParameter()方法獲取支持的參數解析器,通過resolveArgument()方法進行參數解析。

0.2 customArgumentResolvers

customArgumentResolvers是用於緩存開發人員自定義的參數解析器,即通過WebMvcConfigurer#addArgumentResolvers()方法添加的解析器。

RequestMappingHandlerAdapter初始化時,會將customArgumentResolvers中的自定義參數解析器添加到argumentResolvers中。

0.3 returnValueHandlers

returnValueHandlers是返回值處理器,它可以對控制層業務返回值進行處理。

例如,對@ResponseBody標註的返回值進行JSON格式化,並寫到輸出流。

returnValueHandlers使用了組合模式,它的類型是HandlerMethodReturnValueHandlerComposite,其內部緩存HandlerMethodReturnValueHandler對象,用來進行返回值處理。

0.4 customReturnValueHandlers

customReturnValueHandlers是用於緩存開發人員自定義的參數解析器,即通過WebMvcConfigurer#addReturnValueHandlers()方法添加的解析器。

RequestMappingHandlerAdapter初始化時,會將customReturnValueHandlers中的自定義參數解析器添加到returnValueHandlers中。

1 初始化流程

RequestMappingHandlerAdapter內部,有兩個方法用於初始化。一個是構造函數,另一個是實現org.springframework.beans.factory.InitializingBeanafterPropertiesSet()方法。

在Spring Boot中,會在WebMvcConfigurationSupport中進行完整的初始化。

1.1 構造函數

構造函數中主要是對messageConverters進行初始化,添加一些必備的消息轉換器。實際上,WebMvcConfigurationSupport中會進行覆蓋,因此不過多描述:

public RequestMappingHandlerAdapter() {  
   this.messageConverters = new ArrayList<>(4);  
   this.messageConverters.add(new ByteArrayHttpMessageConverter());  
   this.messageConverters.add(new StringHttpMessageConverter());  
   if (!shouldIgnoreXml) {  
      try {  
         this.messageConverters.add(new SourceHttpMessageConverter<>());  
      }  
      catch (Error err) {  
         // Ignore when no TransformerFactory implementation is available  
      }  
   }  
   this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());  
}

1.2 afterPropertiesSet()

RequestMappingHandlerAdapter#afterPropertiesSet()方法中,會對argumentResolversinitBinderArgumentResolversreturnValueHandlers等進行初始化:

public void afterPropertiesSet() {  
   // Do this first, it may add ResponseBody advice beans  
   initControllerAdviceCache();  
  
   if (this.argumentResolvers == null) {  
      List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();  
      this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);  
   }  
   if (this.initBinderArgumentResolvers == null) {  
      List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();  
      this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);  
   }  
   if (this.returnValueHandlers == null) {  
      List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();  
      this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);  
   }  
}

1.2.1 initControllerAdviceCache

RequestMappingHandlerAdapter#initControllerAdviceCache()方法中,會從容器中獲取所有@ControllerAdvice標註的bean。然後緩存這些bean中標註@RequestMapping&@ModelAttributemodelAttributeAdviceCache)和@InitBinderinitBinderAdviceChache)等註解的方法,並且直接緩存實現RequestBodyAdviceResponseBodyAdvicebeanrequestResponseBodyAdvice)。

private void initControllerAdviceCache() {  
   if (getApplicationContext() == null) {  
      return;  
   }  
  
   List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());  
  
   List<Object> requestResponseBodyAdviceBeans = new ArrayList<>();  
  
   for (ControllerAdviceBean adviceBean : adviceBeans) {  
      Class<?> beanType = adviceBean.getBeanType();  
      if (beanType == null) {  
         throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);  
      }  
      Set<Method> attrMethods = MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS);  
      if (!attrMethods.isEmpty()) {  
         this.modelAttributeAdviceCache.put(adviceBean, attrMethods);  
      }  
      Set<Method> binderMethods = MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS);  
      if (!binderMethods.isEmpty()) {  
         this.initBinderAdviceCache.put(adviceBean, binderMethods);  
      }  
      if (RequestBodyAdvice.class.isAssignableFrom(beanType) || ResponseBodyAdvice.class.isAssignableFrom(beanType)) {  
         requestResponseBodyAdviceBeans.add(adviceBean);  
      }  
   }  
  
   if (!requestResponseBodyAdviceBeans.isEmpty()) {  
      this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans);  
   }  
}

1.2.2 getDefaultXxx()方法

通過getDefaultArgumentResolvers()getDefaultInitBinderArgumentResolvers()getDefaultResurnValueHandlers()方法分別對argumentResolversinitBinderArgumentResolversreturnValueHandlers進行初始化。

在這些getDefaultXxx()方法中,一方面會按一定順序添加一系列預設的處理器對象,另一方面會通過getCustomXxx()方法獲取開發人員自定義的處理器對象(可通過WevMvcConfigurer添加)。

例如,RequestMappingHandlerAdapter#getDefaultArgumentResolvers()方法會添加一系列預設的參數解析器,並且通過getCustomArgumentResolvers()方法獲取開發人員自定義的參數解析器:

private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {  
   List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(30);  
  
   // Annotation-based argument resolution  
   resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));  
   resolvers.add(new RequestParamMapMethodArgumentResolver());  
   resolvers.add(new PathVariableMethodArgumentResolver());  
   resolvers.add(new PathVariableMapMethodArgumentResolver());  
   resolvers.add(new MatrixVariableMethodArgumentResolver());  
   resolvers.add(new MatrixVariableMapMethodArgumentResolver());  
   resolvers.add(new ServletModelAttributeMethodProcessor(false));  
   resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));  
   resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));  
   resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));  
   resolvers.add(new RequestHeaderMapMethodArgumentResolver());  
   resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));  
   resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));  
   resolvers.add(new SessionAttributeMethodArgumentResolver());  
   resolvers.add(new RequestAttributeMethodArgumentResolver());  
  
   // Type-based argument resolution  
   resolvers.add(new ServletRequestMethodArgumentResolver());  
   resolvers.add(new ServletResponseMethodArgumentResolver());  
   resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));  
   resolvers.add(new RedirectAttributesMethodArgumentResolver());  
   resolvers.add(new ModelMethodProcessor());  
   resolvers.add(new MapMethodProcessor());  
   resolvers.add(new ErrorsMethodArgumentResolver());  
   resolvers.add(new SessionStatusMethodArgumentResolver());  
   resolvers.add(new UriComponentsBuilderMethodArgumentResolver());  
   if (KotlinDetector.isKotlinPresent()) {  
      resolvers.add(new ContinuationHandlerMethodArgumentResolver());  
   }  
  
   // Custom arguments  
   if (getCustomArgumentResolvers() != null) {  
      resolvers.addAll(getCustomArgumentResolvers());  
   }  
  
   // Catch-all  
   resolvers.add(new PrincipalMethodArgumentResolver());  
   resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));  
   resolvers.add(new ServletModelAttributeMethodProcessor(true));  
  
   return resolvers;

1.3 WebMvcConfigurationSupport

WebMvcConfigurationSupport#requestMappingHandlerAdapter()中,會完成requestMappingHandlerAdapterbean的創建,對contentNegotiationManagermessageConverterswebBindingInitializercustomArgumentResolverscustomReturnValueHandlers等基礎成員變數,以及非同步請求的taskExecutorasyncRequestTimeoutcallableInterceptorsdeferredResultInterceptors等成員變數進行初始化:

public RequestMappingHandlerAdapter requestMappingHandlerAdapter(  
      @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,  
      @Qualifier("mvcConversionService") FormattingConversionService conversionService,  
      @Qualifier("mvcValidator") Validator validator) {  
  
   RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();  
   adapter.setContentNegotiationManager(contentNegotiationManager);  
   adapter.setMessageConverters(getMessageConverters());  
   adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer(conversionService, validator));  
   adapter.setCustomArgumentResolvers(getArgumentResolvers());  
   adapter.setCustomReturnValueHandlers(getReturnValueHandlers());  
  
   if (jackson2Present) {  
      adapter.setRequestBodyAdvice(Collections.singletonList(new JsonViewRequestBodyAdvice()));  
      adapter.setResponseBodyAdvice(Collections.singletonList(new JsonViewResponseBodyAdvice()));  
   }  
  
   AsyncSupportConfigurer configurer = getAsyncSupportConfigurer();  
   if (configurer.getTaskExecutor() != null) {  
      adapter.setTaskExecutor(configurer.getTaskExecutor());  
   }  
   if (configurer.getTimeout() != null) {  
      adapter.setAsyncRequestTimeout(configurer.getTimeout());  
   }  
   adapter.setCallableInterceptors(configurer.getCallableInterceptors());  
   adapter.setDeferredResultInterceptors(configurer.getDeferredResultInterceptors());  
  
   return adapter;  
}

在初始化過程中,一方面會為這些成員添加一系列預設對象,另一方面會從WebMvcConfigurer中獲取開發人員自定義的對象。

2 同步請求處理流程

首先,DispatcherServlet會調用HandlerAdapter介面的handle()方法。

AbstractHandlerMethodAdapterhandle()方法的實現只是做了一個類型轉換:

public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)  
      throws Exception {  
   return handleInternal(request, response, (HandlerMethod) handler);  
}

AbstractHandlerMethodAdapter#handleInternal()是一個抽象方法,會由子類具體去實現。

RequestMappingHandlerAdapter#handlerInternal()方法中會進行一些請求判斷和緩存處理(省略),它的核心是在invokeHandlerMethod()方法:

protected ModelAndView handleInternal(HttpServletRequest request,  
      HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {  
  
   ModelAndView mav;  
   
   mav = invokeHandlerMethod(request, response, handlerMethod);
  
   return mav;  
}

2.1 預處理:添加處理器

RequestMappingHandlerAdapter#invokeHandlerMethod()方法中,會進行如下處理:

  1. requestresponse封裝成ServletWebRequest對象,便於後續處理。
  2. handler封裝成ServletInvocableHandlerMethod對象invocableMethod
  3. invocableMethod設置argumentResolvers(參數解析)、returnValueHandlers(返回值處理)、dataBinderFactory(數據綁定和校驗)和parameterNameDiscoverer(形參名字解析)等組件,用作後續方法處理的工具。這些組件都來自RequestMappingHandlerAdapter的成員變數。
  4. 最後會調用invocableMethodinvokeAndHandle()方法進行實際處理。

RequestMappingHandlerAdapter#invokeHandlerMethod()具體源碼如下:

protected ModelAndView invokeHandlerMethod(HttpServletRequest request,  
      HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { 
   // 1、將`request`和`response`封裝成`ServletWebRequest`對象
   ServletWebRequest webRequest = new ServletWebRequest(request, response);  
   try {  
      WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);  
      ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory); 
      // 2、將`handler`封裝成`ServletInvocableHandlerMethod`對象`invocableMethod` 
      ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);  
      // 3、為`invocableMethod`設置`argumentResolvers`、`returnValueHandlers`、`dataBinderFactory`和`parameterNameDiscoverer`等工具
      if (this.argumentResolvers != null) {  
         invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);  
      }  
      if (this.returnValueHandlers != null) {  
         invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);  
      }  
      invocableMethod.setDataBinderFactory(binderFactory);  
      invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);  
      // 4、處理請求
      invocableMethod.invokeAndHandle(webRequest, mavContainer);    
      return getModelAndView(mavContainer, modelFactory, webRequest);  
   }  
   finally {  
      webRequest.requestCompleted();  
   }  
}

ServletInvocableHandlerMethod#invokeAndHandle()方法會調用請求,並且對返回值進行處理:

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {  
   // 1、調用請求
   Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);  
   // 省略相關代碼
   // 2、返回值處理
   try {  
      this.returnValueHandlers.handleReturnValue(  
            returnValue, getReturnValueType(returnValue), mavContainer, webRequest);  
   }  
   catch (Exception ex) {  
      if (logger.isTraceEnabled()) {  
         logger.trace(formatErrorForReturnValue(returnValue), ex);  
      }  
      throw ex;  
   }  
}

2.2 形參對象解析

InvocableHandlerMethod#invokeForRequest()方法中,會進行參數解析(將request中的數據解析成handler方法的形參對象),然後通過反射調用對應方法,獲取返回值:

public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {  
   // 1、參數解析
   Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);  
   // 2、調用方法
   return doInvoke(args);  
}

InvocableHandlerMethod#getMethodArgumentValues()方法中,會通過反射獲取handler方法的形參,然後使用resolvers對一個個形參進行解析。

根據形參的類型不同(HttpServletRequest等),形參上標註的註解不同(@RequestBody等),會調用不同的解析器實現類進行處理。

根據解析器實現類的不同,在解析過程中,會進行數據綁定、消息轉換和參數校驗:

protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {  
   // 1、獲取方法的形參信息
   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 {  
         // 2、形參解析
         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;  
}

2.3 執行方法

回到InvocableHandlerMethod#invokeForRequest()方法,解析方法形參後,會調用InvocableHandlerMethod#doInvoke()方法,通過反射調用方法,並傳入handler對應的控制層bean作為觸發對象,以及上述形參對象:

protected Object doInvoke(Object... args) throws Exception {  
   Method method = getBridgedMethod();  
   try {  
      return method.invoke(getBean(), args);  
   }  
   catch (IllegalArgumentException ex) {  
      // 省略相關代碼
   }  
}

2.4 返回值處理

回到ServletInvocableHandlerMethod#invokeAndHandle()方法,此時獲取了handler方法執行完成的返回值,會調用HandlerMethodReturnValueHandlerComposite#handleReturnValue()方法對返回值進行處理。首先會根據返回值信息MethodParameter對象查找支持的返回值處理器HandlerMethodReturnValueHandler,然後使用該處理器對返回值進行處理:

public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,  
      ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {  
   // 1、查找返回值處理器
   HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);  

   // 2、返回值處理
   handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);  
}

HandlerMethodReturnValueHandlerComposite#selectHandler方法中,會遍歷returnValueHandlers,調用其HandlerMethodReturnValueHandler#supportsReturnType實現方法找到對應返回值處理器。:

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

找到返回值處理器後,就可以通過其handleReturnValue()方法對返回值進行處理。

舉個有實戰意義的例子,@ResponseBodyHandlerMethodReturnValueHandler實現類是RequestResponseBodyMethodProcessor

RequestResponseBodyMethodProcessorsupportsReturnType()方法會判斷返回值是否標有ResponseBody註解:

public boolean supportsReturnType(MethodParameter returnType) {  
   return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||  
         returnType.hasMethodAnnotation(ResponseBody.class));  
}

RequestResponseBodyMethodProcessorhandleReturnValue()方法會根據返回的Content-Type對返回值進行對應格式化,並寫入到輸出流中:

public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,  
      ModelAndViewContainer mavContainer, NativeWebRequest webRequest)  
      throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {  
  
   mavContainer.setRequestHandled(true);  
   ServletServerHttpRequest inputMessage = createInputMessage(webRequest);  
   ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);  
  
   // Try even with null return value. ResponseBodyAdvice could get involved.  
   writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);  
}

至此,我們走完了RequestMappingHandlerAdapter對同步請求的完整處理流程(前後端分離)。簡單來說,會經過一下主要步驟:

  1. 初始化請求處理的工具:argumentResolversreturnValueHandlersbinderFactoryparameterNameDiscoverer等。
  2. 解析形參對象
  3. 執行方法
  4. 返回值處理

實際上RequestMappingHandlerAdapter中還會對非同步請求進行處理,這部分我們會在之後的文章進行詳細介紹。

3 HandlerMethodArgumentResolver實現類

3.1 RequestResponseBodyMethodProcessor

RequestResponseBodyMethodProcessor是前後端分離項目中使用最多的HandlerMethodArgumentResolver實現類,它可以處理@RequestBody標註的形參。

3.1.1 supportsParameter()方法

RequestResponseBodyMethodProcessor#supportsParameter()方法會判斷形參上是否標註@RequestBody註解:

public boolean supportsParameter(MethodParameter parameter) {  
   return parameter.hasParameterAnnotation(RequestBody.class);  
}

3.1.2 resolveArgument()方法

RequestResponseBodyMethodProcessor#resolveArgument()方法會從輸入流中讀取數據,轉換成形參對象,並且對其進行數據校驗:

public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,  
      NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {  
  
   parameter = parameter.nestedIfOptional();  
   // 從輸入流中讀取數據,並構造成形參對象
   Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());  
   String name = Conventions.getVariableNameForParameter(parameter);  
  
   if (binderFactory != null) {  
      WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);  
      if (arg != null) {  
         // 數據校驗
         validateIfApplicable(binder, parameter);  
         if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {  
            throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());  
         }  
      }  
      if (mavContainer != null) {  
         mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());  
      }  
   }  
  
   return adaptArgumentIfNecessary(arg, parameter);  
}

AbstractMessageConverterMethodArgumentResolver#readWithMessageConverters()方法會根據Content-Type從輸入流讀取數據,並創建成形參對象:

protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,  
      Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {  
  
   // 獲取請求Content-Type
   MediaType contentType;  
   boolean noContentType = false;  
   try {  
      contentType = inputMessage.getHeaders().getContentType();  
   }  
   catch (InvalidMediaTypeException ex) {  
      throw new HttpMediaTypeNotSupportedException(ex.getMessage());  
   }  
   if (contentType == null) {  
      noContentType = true;  
      contentType = MediaType.APPLICATION_OCTET_STREAM;  
   }  
  
   // 獲取形參類型
   Class<?> contextClass = parameter.getContainingClass();  
   Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);  
   if (targetClass == null) {  
      ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);  
      targetClass = (Class<T>) resolvableType.resolve();  
   }  
  
   HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null);  
   Object body = NO_VALUE;  
  
   // 根據Content-Type使用對應messageConverter讀取並轉換數據
   EmptyBodyCheckingHttpInputMessage message = null;  
   try {  
      message = new EmptyBodyCheckingHttpInputMessage(inputMessage);  
  
      for (HttpMessageConverter<?> converter : this.messageConverters) {  
         Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();  
         GenericHttpMessageConverter<?> genericConverter =  
               (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);  
         // 根據Content-Type獲取對應的messageConverter
         if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :  
               (targetClass != null && converter.canRead(targetClass, contentType))) {  
            if (message.hasBody()) {  
               // RequestBodyAdvice#beforeBodyRead()處理
               HttpInputMessage msgToUse =  
                     getAdvice().beforeBodyRead(message, parameter, targetType, converterType);  
               // 讀取並轉換數據
               body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :  
                     ((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));  
               // RequestBodyAdvice#afterBodyRead()處理
               body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);  
            }  
            else {  
               body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);  
            }  
            break;  
         }  
      }  
   }  
   catch (IOException ex) {  
      throw new HttpMessageNotReadableException("I/O error while reading input message", ex, inputMessage);  
   }  
   finally {  
      if (message != null && message.hasBody()) {  
         closeStreamIfNecessary(message.getBody());  
      }  
   }  
  
   if (body == NO_VALUE) {  
      if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) ||  
            (noContentType && !message.hasBody())) {  
         return null;  
      }  
      throw new HttpMediaTypeNotSupportedException(contentType,  
            getSupportedMediaTypes(targetClass != null ? targetClass : Object.class));  
   }  
  
   MediaType selectedContentType = contentType;  
   Object theBody = body;  
   LogFormatUtils.traceDebug(logger, traceOn -> {  
      String formatted = LogFormatUtils.formatValue(theBody, !traceOn);  
      return "Read \"" + selectedContentType + "\" to [" + formatted + "]";  
   });  
  
   return body;  
}

AbstractMessageConverterMethodArgumentResolver#validateIfApplicable()方法會對標註javax.validation.Validorg.springframework.validation.annotation.Validated以及以Valid開頭的自定義註解進行參數校驗:

protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {  
   Annotation[] annotations = parameter.getParameterAnnotations();  
   for (Annotation ann : annotations) {  
      Object[] validationHints = ValidationAnnotationUtils.determineValidationHints(ann);  
      if (validationHints != null) {  
         binder.validate(validationHints);  
         break;  
      }  
   }  
}

4 HandlerMethodReturnValueHandler實現類

4.1 RequestResponseBodyMethodProcessor

RequestResponseBodyMethodProcessor是前後端分離項目中使用最多的HandlerMethodReturnValueHandler實現類,它可以處理@ResponseBody標註的返回值。

4.1.1 supportsReturnType()方法

RequestResponseBodyMethodProcessor#supportsReturnType()方法會判斷類或方法上是否標註@RequestBody註解:

public boolean supportsReturnType(MethodParameter returnType) {  
   return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||  
         returnType.hasMethodAnnotation(ResponseBody.class));  
}

4.1.2 handleReturnValue()方法

RequestResponseBodyMethodProcessor#handleReturnValue()方法會根據響應的Content-Type,將返回值格式化成對應數據格式,寫道輸出流進行響應:

public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,  
      ModelAndViewContainer mavContainer, NativeWebRequest webRequest)  
      throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {  
  
   mavContainer.setRequestHandled(true);  
   ServletServerHttpRequest inputMessage = createInputMessage(webRequest);  
   ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);  
  
   // Try even with null return value. ResponseBodyAdvice could get involved.  
   writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);  
}

實際業務在AbstractMessageConverterMethodProcessor#writeWithMessageConverters()方法,

protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,  
      ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)  
      throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {  
  
   Object body;  
   Class<?> valueType;  
   Type targetType;  
  
   // 如果返回值是CharSequence類型,valueType和targetType都設置成String類型
   if (value instanceof CharSequence) {  
      body = value.toString();  
      valueType = String.class;  
      targetType = String.class;  
   }  
   // 如果返回值不是CharSequence,valueType設置成對應返回值類型,targetType會設置成解析泛型後的返回值類型
   else {  
      body = value;  
      valueType = getReturnValueType(body, returnType);  
      targetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass());  
   }  
   // 如果是返回值繼承自Resource
   if (isResourceType(value, returnType)) {  
      outputMessage.getHeaders().set(HttpHeaders.ACCEPT_RANGES, "bytes");  
      if (value != null && inputMessage.getHeaders().getFirst(HttpHeaders.RANGE) != null &&  
            outputMessage.getServletResponse().getStatus() == 200) {  
         Resource resource = (Resource) value;  
         try {  
            List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();  
            outputMessage.getServletResponse().setStatus(HttpStatus.PARTIAL_CONTENT.value());  
            body = HttpRange.toResourceRegions(httpRanges, resource);  
            valueType = body.getClass();  
            targetType = RESOURCE_REGION_LIST_TYPE;  
         }  
         catch (IllegalArgumentException ex) {  
            outputMessage.getHeaders().set(HttpHeaders.CONTENT_RANGE, "bytes */" + resource.contentLength());  
            outputMessage.getServletResponse().setStatus(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE.value());  
         }  
      }  
   }  
  
   // 獲取響應的Content-Type
   MediaType selectedMediaType = null;  
   MediaType contentType = outputMessage.getHeaders().getContentType();  
   boolean isContentTypePreset = contentType != null && contentType.isConcrete();  
   if (isContentTypePreset) {  
      if (logger.isDebugEnabled()) {  
         logger.debug("Found 'Content-Type:" + contentType + "' in response");  
      }  
      selectedMediaType = contentType;  
   }  
   else {  
      HttpServletRequest request = inputMessage.getServletRequest();  
      List<MediaType> acceptableTypes;  
      try {  
         acceptableTypes = getAcceptableMediaTypes(request);  
      }  
      catch (HttpMediaTypeNotAcceptableException ex) {  
         int series = outputMessage.getServletResponse().getStatus() / 100;  
         if (body == null || series == 4 || series == 5) {  
            if (logger.isDebugEnabled()) {  
               logger.debug("Ignoring error response content (if any). " + ex);  
            }  
            return;  
         }  
         throw ex;  
      }  
      List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);  
  
      if (body != null && producibleTypes.isEmpty()) {  
         throw new HttpMessageNotWritableException(  
               "No converter found for return value of type: " + valueType);  
      }  
      List<MediaType> mediaTypesToUse = new ArrayList<>();  
      for (MediaType requestedType : acceptableTypes) {  
         for (MediaType producibleType : producibleTypes) {  
            if (requestedType.isCompatibleWith(producibleType)) {  
               mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));  
            }  
         }  
      }  
      if (mediaTypesToUse.isEmpty()) {  
         if (logger.isDebugEnabled()) {  
            logger.debug("No match for " + acceptableTypes + ", supported: " + producibleTypes);  
         }  
         if (body != null) {  
            throw new HttpMediaTypeNotAcceptableException(producibleTypes);  
         }  
         return;  
      }  
  
      MediaType.sortBySpecificityAndQuality(mediaTypesToUse);  
  
      for (MediaType mediaType : mediaTypesToUse) {  
         if (mediaType.isConcrete()) {  
            selectedMediaType = mediaType;  
            break;  
         }  
         else if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {  
            selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;  
            break;  
         }  
      }  
  
      if (logger.isDebugEnabled()) {  
         logger.debug("Using '" + selectedMediaType + "', given " +  
               acceptableTypes + " and supported " + producibleTypes);  
      }  
   }  
   // 根據響應Content-Type格式化返回值,並寫到輸出流
   if (selectedMediaType != null) {  
      selectedMediaType = selectedMediaType.removeQualityValue();  
      for (HttpMessageConverter<?> converter : this.messageConverters) {  
         GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?  
               (GenericHttpMessageConverter<?>) converter : null);  
         // 根據響應Content-Type獲取對應的messageConverter
         if (genericConverter != null ?  
               ((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :  
               converter.canWrite(valueType, selectedMediaType)) {  
            // ResponseBodyAdvice的beforeBodyWrite()處理
            body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,  
                  (Class<? extends HttpMessageConverter<?>>) converter.getClass(),  
                  inputMessage, outputMessage);  
            if (body != null) {  
               Object theBody = body;  
               LogFormatUtils.traceDebug(logger, traceOn ->  
                     "Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");  
               addContentDispositionHeader(inputMessage, outputMessage);  
               // 通過messageConverter格式化返回值,並寫到輸出流
               if (genericConverter != null) {  
                  genericConverter.write(body, targetType, selectedMediaType, outputMessage);  
               }  
               else {  
                  ((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);  
               }  
            }  
            else {  
               if (logger.isDebugEnabled()) {  
                  logger.debug("Nothing to write: null body");  
               }  
            }  
            return;  
         }  
      }  
   }  
}

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本文介紹 C++11 標準中新添加的 long long 超長整型和 nullptr 初始化空指針。 1. C++11:long long 超長整型 C++ 11 標準中,基於整數大小的考慮,共提供瞭如下表所示的這些數據類型。與此同時,標準中還明確限定了各個數據類型最少占用的位數。 | 整數類型 | ...
  • 今天在寫一個通訊錄實現程式的時候,遇到個讓我突然卡殼的問題,不知道怎麼進行兩個結構體之間的成員互換......結構體成員有“姓名”,“性別”,“年齡”,“地址”,“電話”,目的就是實現一個通過年齡進行sort排序的功能,作為一個努力學習的編程小白來說,有太多的東西需要學習了..........代碼如 ...
  • 動態鏈接庫(dynamic link library)介紹 代碼放到exe中,肯定會造成磁碟冗餘; 電腦ABCD四個軟體,lib加入到代碼中不是在編譯期進入的,而是在運行期 (A進程啟動,把dll加入到A進程中……),編譯的時候不需要這份代碼, 尾碼是.dll 如果要更新軟體,把dll換掉就可以了, ...
  • 卸載mysql教程: (261條消息) 如何徹底卸載 MySQL ,再可重新安裝 MySQL_Cavalier_01的博客-CSDN博客_如何卸載mysql重新安裝 安裝mysql5.7.19: 首先,去官網下載5.7.19壓縮包然後解壓; MySQL :: Download MySQL Commu ...
  • 一:背景 1. 講故事 上一篇寫完了之後,馬上就有朋友留言對記錄行的 8060byte 限制的疑惑,因為他的表記錄存儲了大量的文章,存儲文章的欄位類型用的是 nvarchar(max),長度很顯然是超過 8060byte 的,請問這個底層是怎麼破掉 8060byte 的限制的? 說實話這是一個好問題 ...
  • 為了應對大流量,現代應用/中間件通常採用分散式部署,此時不得不考慮 CAP 問題。ZooKeeper(後文簡稱 ZK)是面向 CP 設計的一個開源的分散式協調框架,將那些複雜且容易出錯的分散式一致性服務封裝起來,構成一個高效可靠的原語集,並以一系列簡單易用的介面提供給用戶使用,分散式應用程式可以基於... ...
  • 力扣104 求二叉樹的最大深度 題目: 給定一個二叉樹,找出其最大深度。 二叉樹的深度為根節點到最遠葉子節點的最長路徑上的節點數。 說明: 葉子節點是指沒有子節點的節點。 示例 給定二叉樹 [3,9,20,null,null,15,7], 3 / \ 9 20 / \ 15 7 返回它的最大深度 3 ...
  • 力扣100 相同的樹 題目: 給你兩棵二叉樹的根節點 p 和 q ,編寫一個函數來檢驗這兩棵樹是否相同。 如果兩個樹在結構上相同,並且節點具有相同的值,則認為它們是相同的。 示例 1: 輸入:p = [1,2,3], q = [1,2,3] 輸出:true 示例 2: 輸入:p = [1,2], q ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...