激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - Java SpringBoot在RequestBody中高效的使用枚舉參數原理案例詳解

Java SpringBoot在RequestBody中高效的使用枚舉參數原理案例詳解

2021-12-16 11:01看山 Java教程

這篇文章主要介紹了Java SpringBoot在RequestBody中高效的使用枚舉參數原理案例詳解,本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內容,需要的朋友可以參考下

優雅的使用枚舉參數(原理篇)中我們聊過,Spring對于不同的參數形式,會采用不同的處理類處理參數,這種形式,有些類似于策略模式。將針對不同參數形式的處理邏輯,拆分到不同處理類中,減少耦合和各種if-else邏輯。本文就來扒一扒,RequestBody參數中使用枚舉參數的原理。

 

找入口

對 Spring 有一定基礎的同學一定知道,請求入口是DispatcherServlet,所有的請求最終都會落到doDispatch方法中的ha.handle(processedRequest, response, mappedHandler.getHandler())邏輯。我們從這里出發,一層一層向里扒。

跟著代碼深入,我們會找到org.springframework.web.method.support.InvocableHandlerMethod#invokeForRequest的邏輯:

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

可以看出,這里面通過getMethodArgumentValues方法處理參數,然后調用doInvoke方法獲取返回值。getMethodArgumentValues方法內部又是通過HandlerMethodArgumentResolverComposite實例處理參數。這個類內部是一個HandlerMethodArgumentResolver實例列表,列表中是Spring處理參數邏輯的集合,跟隨代碼Debug,可以看到有27個元素。這些類也是可以定制擴展,實現自己的參數解析邏輯,這部分內容后續再做介紹。

 

選擇Resolver

這個Resolver列表中,包含我們常用的幾個處理類。Get請求的普通參數是通過RequestParamMethodArgumentResolver處理參數,包裝類通過ModelAttributeMethodProcessor處理參數,RequestBody形式的參數,則是通過RequestResponseBodyMethodProcessor處理參數。這段就是Spring中策略模式的使用,通過實現org.springframework.web.method.support.HandlerMethodArgumentResolver#supportsParameter方法,判斷輸入參數是否可以解析。下面貼上RequestResponseBodyMethodProcessor的實現:

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

可以看到,RequestResponseBodyMethodProcessor是通過判斷參數是否帶有RequestBody注解來判斷,當前參數是否可以解析。

 

解析參數

RequestResponseBodyMethodProcessor繼承自AbstractMessageConverterMethodArgumentResolver,真正解析RequestBody參數的邏輯在org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver#readWithMessageConverters方法中。我們看下源碼(因為源碼比較長,文中僅留下核心邏輯。):

protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
      Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
  MediaType contentType = inputMessage.getHeaders().getContentType();// 1
  Class<?> contextClass = parameter.getContainingClass();// 2
  Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);// 3

  Object body = NO_VALUE;

  EmptyBodyCheckingHttpInputMessage message = new EmptyBodyCheckingHttpInputMessage(inputMessage);// 4
  for (HttpMessageConverter<?> converter : this.messageConverters) {// 5
      Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
      GenericHttpMessageConverter<?> genericConverter =
              (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
      if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
              (targetClass != null && converter.canRead(targetClass, contentType))) {
          if (message.hasBody()) {
              HttpInputMessage msgToUse =
                      getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
              body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
                      ((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));// 6
              body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
          }
          else {
              body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
          }
          break;
      }
  }
  return body;
}

跟著代碼說明一下各部分用途:

  1. 獲取請求content-type
  2. 獲取參數容器類
  3. 獲取目標參數類型
  4. 將請求參數轉換為EmptyBodyCheckingHttpInputMessage類型
  5. 循環各種RequestBody參數解析器,這些解析器都是HttpMessageConverter接口的實現類。Spring對各種情況做了全量覆蓋,總有一款適合的。文末給出HttpMessageConverter各個擴展類的類圖。
  6. for循環體中就是選擇一款適合的,進行解析
    • 首先調用canRead方法判斷是否可用
    • 判斷請求請求參數是否為空,為空則通過AOP的advice處理一下空請求體,然后返回
    • 不為空,先通過AOP的advice做前置處理,然后調用read方法轉換對象,在通過advice做后置處理

Spring的AOP不在本文范圍內,所以一筆帶過。后續有專題說明。

本例中,HttpMessageConverter使用的是MappingJackson2HttpMessageConverter,該類繼承自AbstractJackson2HttpMessageConverter。看名稱就知道,這個類是使用Jackson處理請求參數。其中read方法之后,會調用內部私有方法readJavaType,下面給出該方法的核心邏輯:

private Object readJavaType(JavaType javaType, HttpInputMessage inputMessage) throws IOException {
  MediaType contentType = inputMessage.getHeaders().getContentType();// 1
  Charset charset = getCharset(contentType);

  ObjectMapper objectMapper = selectObjectMapper(javaType.getRawClass(), contentType);// 2
  Assert.state(objectMapper != null, "No ObjectMapper for " + javaType);

  boolean isUnicode = ENCODINGS.containsKey(charset.name()) ||
          "UTF-16".equals(charset.name()) ||
          "UTF-32".equals(charset.name());// 3
  try {
      if (isUnicode) {
          return objectMapper.readValue(inputMessage.getBody(), javaType);// 4
      } else {
          Reader reader = new InputStreamReader(inputMessage.getBody(), charset);
          return objectMapper.readValue(reader, javaType);
      }
  }
  catch (InvalidDefinitionException ex) {
      throw new HttpMessageConversionException("Type definition error: " + ex.getType(), ex);
  }
  catch (JsonProcessingException ex) {
      throw new HttpMessageNotReadableException("JSON parse error: " + ex.getOriginalMessage(), ex, inputMessage);
  }
}

跟著代碼說明一下各部分用途:

  1. 獲取請求的content-type,這個是Spring實現的擴展邏輯,根據不同的content-type可以選擇不同的ObjectMapper實例。也就是第2步的邏輯
  2. 根據content-type和目標類型,選擇ObjectMapper實例。本例中直接返回的是默認的,也就是通過Jackson2ObjectMapperBuilder.cbor().build()方法創建的。
  3. 檢查請求是否是unicode字符,目前來說,大家用的都是UTF-8的
  4. 通過ObjectMapper將請求json轉換為對象。其實這部分還有一段判斷inputMessage是否是MappingJacksonInputMessage實例的,考慮到大家使用的版本,這部分就不說了。

至此,Spring的邏輯全部結束,似乎還是沒有找到我們使用的JsonCreator注解或者JsonDeserialize的邏輯。不過也能想到,這兩個都是Jackson的類,那必然應該是Jackson的邏輯。接下來,就扒一扒Jackson的轉換邏輯了。

 

深入Jackson的ObjectMapper邏輯

牽扯Jackson的邏輯主要分布在AbstractMessageConverterMethodArgumentResolver#readWithMessageConverters和ObjectMapper#readValue這兩個方法中。先說一下ObjectMapper#readValue方法的邏輯,這里面會調用GenderIdCodeEnum#create方法,完成類型轉換。

ObjectMapper#readValue方法直接調用了當前類中的_readMapAndClose方法,這個方法里面比較關鍵的是ctxt.readRootValue(p, valueType, _findRootDeserializer(ctxt, valueType), null),這個方法就是將輸入json轉換為對象。咱們再繼續深入,可以找到Jackson內部是通過BeanDeserializer這個類轉換對象的,比較重要的是deserializeFromObject方法,源碼如下(刪除一下不太重要的代碼):

public Object deserializeFromObject(JsonParser p, DeserializationContext ctxt) throws IOException
{
  // 這里根據上下文中目標類型,創建實例對象,其中 _valueInstantiator 是 StdValueInstantiator 實例。
  final Object bean = _valueInstantiator.createUsingDefault(ctxt);
  // [databind#631]: Assign current value, to be accessible by custom deserializers
  p.setCurrentValue(bean);

  if (p.hasTokenId(JsonTokenId.ID_FIELD_NAME)) {
      String propName = p.currentName();
      do {
          p.nextToken();

          // 根據字段名找到 屬性對象,對于gender字段,類型是 MethodProperty。
          SettableBeanProperty prop = _beanProperties.find(propName);
          if (prop != null) { // normal case
              try {
                  // 開始進行解碼操作,并將解碼結果寫入到對象中
                  prop.deserializeAndSet(p, ctxt, bean);
              } catch (Exception e) {
                  wrapAndThrow(e, bean, propName, ctxt);
              }
              continue;
          }
          handleUnknownVanilla(p, ctxt, bean, propName);
      } while ((propName = p.nextFieldName()) != null);
  }
  return bean;
}

咱們看一下MethodProperty#deserializeAndSet的邏輯(只保留關鍵代碼):

public void deserializeAndSet(JsonParser p, DeserializationContext ctxt,
      Object instance) throws IOException
{
  Object value;
  // 調用 FactoryBasedEnumDeserializer 實例的解碼方法
  value = _valueDeserializer.deserialize(p, ctxt);
  // 通過反射將值寫入對象中
  _setter.invoke(instance, value);
}

其中_valueDeserializer是FactoryBasedEnumDeserializer實例,快要接近目標了,看下這段邏輯:

public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
  // 獲取json中的值
  Object value = _deser.deserialize(p, ctxt);
  // 調用 GenderIdCodeEnum#create 方法
  return _factory.callOnWith(_valueClass, value);
}

_factory是AnnotatedMethod實例,主要是對JsonCreator注解定義的方法的包裝,然后callOnWith中調用java.lang.reflect.Method#invoke反射方法,執行GenderIdCodeEnum#create。

至此,我們終于串起來所有邏輯。

 

文末總結

本文通過一個示例串起來@JsonCreator注解起作用的邏輯,JsonDeserializer接口的邏輯與之類型,可以耐心debug一番。下面給出主要類的類圖:

Java SpringBoot在RequestBody中高效的使用枚舉參數原理案例詳解

Java SpringBoot在RequestBody中高效的使用枚舉參數原理案例詳解

Java SpringBoot在RequestBody中高效的使用枚舉參數原理案例詳解

 

推薦閱讀

  • SpringBoot 實戰:一招實現結果的優雅響應
  • SpringBoot 實戰:如何優雅的處理異常
  • SpringBoot 實戰:通過 BeanPostProcessor 動態注入 ID 生成器
  • SpringBoot 實戰:自定義 Filter 優雅獲取請求參數和響應結果
  • SpringBoot 實戰:優雅的使用枚舉參數
  • SpringBoot 實戰:優雅的使用枚舉參數(原理篇)
  • SpringBoot 實戰:在 RequestBody 中優雅的使用枚舉參數
  • SpringBoot 實戰:在 RequestBody 中優雅的使用枚舉參數(原理篇)

到此這篇關于Java SpringBoot在RequestBody中高效的使用枚舉參數原理案例詳解的文章就介紹到這了,更多相關Java SpringBoot在RequestBody中高效的使用枚舉參數內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!

原文鏈接:https://blog.csdn.net/liuxinghao/article/details/120052842

延伸 · 閱讀

精彩推薦
  • Java教程Java8中Stream使用的一個注意事項

    Java8中Stream使用的一個注意事項

    最近在工作中發現了對于集合操作轉換的神器,java8新特性 stream,但在使用中遇到了一個非常重要的注意點,所以這篇文章主要給大家介紹了關于Java8中S...

    阿杜7482021-02-04
  • Java教程Java使用SAX解析xml的示例

    Java使用SAX解析xml的示例

    這篇文章主要介紹了Java使用SAX解析xml的示例,幫助大家更好的理解和學習使用Java,感興趣的朋友可以了解下...

    大行者10067412021-08-30
  • Java教程小米推送Java代碼

    小米推送Java代碼

    今天小編就為大家分享一篇關于小米推送Java代碼,小編覺得內容挺不錯的,現在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧...

    富貴穩中求8032021-07-12
  • Java教程Java實現搶紅包功能

    Java實現搶紅包功能

    這篇文章主要為大家詳細介紹了Java實現搶紅包功能,采用多線程模擬多人同時搶紅包,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙...

    littleschemer13532021-05-16
  • Java教程20個非常實用的Java程序代碼片段

    20個非常實用的Java程序代碼片段

    這篇文章主要為大家分享了20個非常實用的Java程序片段,對java開發項目有所幫助,感興趣的小伙伴們可以參考一下 ...

    lijiao5352020-04-06
  • Java教程升級IDEA后Lombok不能使用的解決方法

    升級IDEA后Lombok不能使用的解決方法

    最近看到提示IDEA提示升級,尋思已經有好久沒有升過級了。升級完畢重啟之后,突然發現好多錯誤,本文就來介紹一下如何解決,感興趣的可以了解一下...

    程序猿DD9332021-10-08
  • Java教程Java BufferWriter寫文件寫不進去或缺失數據的解決

    Java BufferWriter寫文件寫不進去或缺失數據的解決

    這篇文章主要介紹了Java BufferWriter寫文件寫不進去或缺失數據的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望...

    spcoder14552021-10-18
  • Java教程xml與Java對象的轉換詳解

    xml與Java對象的轉換詳解

    這篇文章主要介紹了xml與Java對象的轉換詳解的相關資料,需要的朋友可以參考下...

    Java教程網2942020-09-17
主站蜘蛛池模板: 国产成人高清成人av片在线看 | 最新中文在线视频 | 91在线色| 91在线色视频 | 性大片免费看 | 日韩黄在线 | 制服丝袜日日夜夜 | 久久亚洲精品久久国产一区二区 | 国产一区二区精品91 | 9999久久久久久 | 国产精品久久久久久久av | 美女污污视频在线观看 | 久久精品视频在线免费观看 | 密室逃脱第一季免费观看完整在线 | 成人h精品动漫一区二区三区 | 日韩午夜一区二区三区 | 亚洲精品永久视频 | 欧美一级黄色录像片 | 国产精品久久久久无码av | 久草在线播放视频 | 国产精品一区二区在线 | 久久国产精品电影 | 日本网站在线播放 | 天天夜夜草 | 免费观看的毛片手机视频 | 成人免费观看49www在线观看 | 日本中文一级片 | 中国美女一级黄色大片 | 看91 | 日本欧美视频 | 国产一级一国产一级毛片 | 欧美日韩大片在线观看 | 多男操一女视频 | 午夜国产福利 | 国产在线地址 | 午夜精品福利影院 | 特黄一区二区三区 | 国产正在播放 | 成人在线精品视频 | av电影在线网 | 亚洲免费看片网站 |