一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

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

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

服務器之家 - 編程語言 - Java教程 - SpringBoot實戰之高效使用枚舉參數(原理篇)案例詳解

SpringBoot實戰之高效使用枚舉參數(原理篇)案例詳解

2021-12-16 11:05沉潛飛動 Java教程

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

找入口

對 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方法獲取返回值。

繼續深入,能夠找到org.springframework.web.method.annotation.RequestParamMethodArgumentResolver#resolveArgument方法,這個方法就是解析參數的邏輯。

試想一下,如果是我們自己實現這段邏輯,會怎么做呢?

  1. 獲取輸入參數
  2. 找到目標參數
  3. 檢查是否需要特殊轉換邏輯
  4. 如果需要,進行轉換
  5. 如果不需要,直接返回

SpringBoot實戰之高效使用枚舉參數(原理篇)案例詳解

獲取輸入參數的邏輯在org.springframework.web.method.annotation.RequestParamMethodArgumentResolver#resolveName,單參數返回的是 String 類型,多參數返回 String 數組。核心代碼如下:

String[] paramValues = request.getParameterValues(name);
if (paramValues != null) {
  arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
}

所以說,無論我們的目標參數是什么,輸入參數都是 String 類型或 String 數組,然后 Spring 把它們轉換為我們期望的類型。

找到目標參數的邏輯在DispatcherServlet中,根據 uri 找到對應的 Controller 處理方法,找到方法就找到了目標參數類型。

接下來就是檢查是否需要轉換邏輯,也就是org.springframework.validation.DataBinder#convertIfNecessary,顧名思義,如果需要就轉換,將字符串類型轉換為目標類型。在我們的例子中,就是將 String 轉換為枚舉值。

查找轉換器

繼續深扒,會在org.springframework.beans.TypeConverterDelegate#convertIfNecessary方法中找到這么一段邏輯:

if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
  try {
      return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
  }
  catch (ConversionFailedException ex) {
      // fallback to default conversion logic below
      conversionAttemptEx = ex;
  }
}

這段邏輯中,調用了org.springframework.core.convert.support.GenericConversionService#canConvert方法,檢查是否可轉換,如果可以轉換,將會執行類型轉換邏輯。

檢查是否可轉換的本質就是檢查是否能夠找到對應的轉換器。如果能找到,就用找到的轉換器開始轉換邏輯,如果找不到,那就是不能轉換,走其他邏輯。

我們可以看看查找轉換器的代碼org.springframework.core.convert.support.GenericConversionService#getConverter,可以對我們自己寫代碼有一些啟發:

private final Map<ConverterCacheKey, GenericConverter> converterCache = new ConcurrentReferenceHashMap<>(64);

protected GenericConverter getConverter(TypeDescriptor sourceType, TypeDescriptor targetType) {
  ConverterCacheKey key = new ConverterCacheKey(sourceType, targetType);
  GenericConverter converter = this.converterCache.get(key);
  if (converter != null) {
      return (converter != NO_MATCH ? converter : null);
  }

  converter = this.converters.find(sourceType, targetType);
  if (converter == null) {
      converter = getDefaultConverter(sourceType, targetType);
  }

  if (converter != null) {
      this.converterCache.put(key, converter);
      return converter;
  }

  this.converterCache.put(key, NO_MATCH);
  return null;
}

轉換為偽代碼就是:

  1. 根據參數類型和目標類型,構造緩存 key
  2. 根據緩存 key,從緩存中查詢轉換器
  3. 如果能找到且不是 NO_MATCH,返回轉換器;如果是 NO_MATCH,返回 null;如果未找到,繼續
  4. 通過org.springframework.core.convert.support.GenericConversionService.Converters#find查詢轉換器
  5. 如果未找到,檢查源類型和目標類型是否可以強轉,也就是類型一致。如果是,返回 NoOpConverter,如果否,返回 null。
  6. 檢查找到的轉換器是否為 null,如果不是,將轉換器加入到緩存中,返回該轉換器
  7. 如果否,在緩存中添加 NO_MATCH 標識,返回 null

SpringBoot實戰之高效使用枚舉參數(原理篇)案例詳解

Spring 內部使用Map作為緩存,用來存儲通用轉換器接口GenericConverter,這個接口會是我們自定義轉換器的包裝類。我們還可以看到,轉換器緩存用的是ConcurrentReferenceHashMap,這個類是線程安全的,可以保證并發情況下,不會出現異常存儲。但是getConverter方法沒有使用同步邏輯。換句話說,并發請求時,可能存在性能損耗。不過,對于 web 請求場景,并發損耗好過阻塞等待。

我們在看下 Spring 是如何查找轉換器的,在org.springframework.core.convert.support.GenericConversionService.Converters#find中就是找到對應轉換器的核心邏輯:

private final Map<ConvertiblePair, ConvertersForPair> converters = new ConcurrentHashMap<>(256);

@Nullable
public GenericConverter find(TypeDescriptor sourceType, TypeDescriptor targetType) {
  // Search the full type hierarchy
  List<Class<?>> sourceCandidates = getClassHierarchy(sourceType.getType());
  List<Class<?>> targetCandidates = getClassHierarchy(targetType.getType());
  for (Class<?> sourceCandidate : sourceCandidates) {
      for (Class<?> targetCandidate : targetCandidates) {
          ConvertiblePair convertiblePair = new ConvertiblePair(sourceCandidate, targetCandidate);
          GenericConverter converter = getRegisteredConverter(sourceType, targetType, convertiblePair);
          if (converter != null) {
              return converter;
          }
      }
  }
  return null;
}

@Nullable
private GenericConverter getRegisteredConverter(TypeDescriptor sourceType,
      TypeDescriptor targetType, ConvertiblePair convertiblePair) {

  // Check specifically registered converters
  ConvertersForPair convertersForPair = this.converters.get(convertiblePair);
  if (convertersForPair != null) {
      GenericConverter converter = convertersForPair.getConverter(sourceType, targetType);
      if (converter != null) {
          return converter;
      }
  }
  // Check ConditionalConverters for a dynamic match
  for (GenericConverter globalConverter : this.globalConverters) {
      if (((ConditionalConverter) globalConverter).matches(sourceType, targetType)) {
          return globalConverter;
      }
  }
  return null;
}

我們可以看到,Spring 是通過源類型和目標類型組合起來,查找對應的轉換器。而且,Spring 還通過getClassHierarchy方法,將源類型和目標類型的家族族譜全部列出來,用雙層 for 循環遍歷查找。

上面的代碼中,還有一個matches方法,在這個方法里面,調用了ConverterFactory#getConverter方法,也就是用這個工廠方法,創建了指定類型的轉換器。

private final ConverterFactory<Object, Object> converterFactory;

public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
  boolean matches = true;
  if (this.converterFactory instanceof ConditionalConverter) {
      matches = ((ConditionalConverter) this.converterFactory).matches(sourceType, targetType);
  }
  if (matches) {
      Converter<?, ?> converter = this.converterFactory.getConverter(targetType.getType());
      if (converter instanceof ConditionalConverter) {
          matches = ((ConditionalConverter) converter).matches(sourceType, targetType);
      }
  }
  return matches;
}

類型轉換

經過上面的邏輯,已經找到判斷可以進行轉換。其核心邏輯就是已經找到對應的轉換器了,下面就是轉換邏輯,在org.springframework.core.convert.support.GenericConversionService#convert中:

public Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType) {
  Assert.notNull(targetType, "Target type to convert to cannot be null");
  if (sourceType == null) {
      Assert.isTrue(source == null, "Source must be [null] if source type == [null]");
      return handleResult(null, targetType, convertNullSource(null, targetType));
  }
  if (source != null && !sourceType.getObjectType().isInstance(source)) {
      throw new IllegalArgumentException("Source to convert from must be an instance of [" +
              sourceType + "]; instead it was a [" + source.getClass().getName() + "]");
  }
  GenericConverter converter = getConverter(sourceType, targetType);
  if (converter != null) {
      Object result = ConversionUtils.invokeConverter(converter, source, sourceType, targetType);
      return handleResult(sourceType, targetType, result);
  }
  return handleConverterNotFound(source, sourceType, targetType);
}

其中的GenericConverter converter = getConverter(sourceType, targetType)就是前文中getConverter方法。此處還是可以給我們編碼上的一些借鑒的:getConverter方法在canConvert中調用了一次,然后在后續真正轉換的時候又調用一次,這是參數轉換邏輯,我們該怎么優化這種同一請求內多次調用相同邏輯或者請求相同參數呢?那就是使用緩存。為了保持一次請求中前后兩次數據的一致性和請求的高效,推薦使用內存緩存。

執行到這里,直接調用ConversionUtils.invokeConverter(converter, source, sourceType, targetType)轉換,其內部是使用org.springframework.core.convert.support.GenericConversionService.ConverterFactoryAdapter#convert方法,代碼如下:

public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
  if (source == null) {
      return convertNullSource(sourceType, targetType);
  }
  return this.converterFactory.getConverter(targetType.getObjectType()).convert(source);
}

這里就是調用ConverterFactory工廠類構建轉換器(即IdCodeToEnumConverterFactory類的getConverter方法),然后調用轉換器的conver方法(即IdCodeToEnumConverter類的convert方法),將輸入參數轉換為目標類型。具體實現可以看一下實戰篇中的代碼,這里不做贅述。

至此,我們把整個路程通了下來。

文末總結

在本文中,我們跟隨源碼找到自定義轉換器工廠類和轉換器類的實現邏輯。這里需要強調一下的是,由于實戰篇中我們用到的例子是簡單參數的方式,也就是Controller的方法參數都是直接參數,沒有包裝成對象。這樣的話,Spring 是通過RequestParamMethodArgumentResolver處理參數。如果是包裝成對象,會使用ModelAttributeMethodProcessor處理參數。這兩個處理類中查找類型轉換器邏輯都是相同的。

無論是GET請求,還是傳參式的POST請求(即Form模式),都可以使用上面這種方式,實現枚舉參數的類型轉換。但是是 HTTP Body 方式卻不行,為什么呢?

Spring 對于 body 參數是通過RequestResponseBodyMethodProcessor處理的,其內部使用了MappingJackson2HttpMessageConverter轉換器,邏輯完全不同。所以,想要實現 body 的類型轉換,還需要走另外一種方式。將在下一篇中給出。

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

原文鏈接:https://www.howardliu.cn/springboot-enum-params-principle/

延伸 · 閱讀

精彩推薦
  • Java教程Java使用SAX解析xml的示例

    Java使用SAX解析xml的示例

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

    大行者10067412021-08-30
  • Java教程Java8中Stream使用的一個注意事項

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

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

    阿杜7482021-02-04
  • Java教程Java實現搶紅包功能

    Java實現搶紅包功能

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

    littleschemer13532021-05-16
  • Java教程xml與Java對象的轉換詳解

    xml與Java對象的轉換詳解

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

    Java教程網2942020-09-17
  • Java教程升級IDEA后Lombok不能使用的解決方法

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

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

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

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

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

    spcoder14552021-10-18
  • Java教程20個非常實用的Java程序代碼片段

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

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

    lijiao5352020-04-06
  • Java教程小米推送Java代碼

    小米推送Java代碼

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

    富貴穩中求8032021-07-12
主站蜘蛛池模板: 男男playh片在线观看 | 99人中文字幕亚洲区 | 四虎影视永久免费视频观看 | 美国69xxxx59| 国产va免费精品高清在线观看 | 亚洲AV国产国产久青草 | 韩国三级2020 | 性色欲情网站IWWW九文堂 | 99视频一区| japanhd粗暴video| 毛片视频在线免费观看 | 亚洲国产精品嫩草影院久久 | 石原莉奈被店长侵犯免费 | 动漫美女人物被黄漫在线看 | 午夜一级影院 | 欧美3p大片在线观看完整版 | 第一次做m被调教经历 | 免费看a片毛片 | 性一交一无一伦一精一品 | 亚洲品质水蜜桃 | 色综色天天综合网 | 午夜精品久视频在线观看 | 我们日本在线观看免费动漫下载 | 色女的乖男人 | 免费标准高清看机机桶机机 | 欧美视频一区二区三区四区 | 欧美性野久久久久久久久 | 91精品国产色综合久久不卡蜜 | 香蕉视频久久 | 国产五月天在线 | 成年私人影院免费视频网站 | 好湿好紧太硬了我太爽了网站 | 国产在线精品一区二区高清不卡 | 亚洲成av人片在线观看天堂无码 | 亚洲va国产日韩欧美精品色婷婷 | 1024免费观看完整版在线播放 | 亚洲视频999 | 亚洲精品一二三四区 | 亚洲精品在看在线观看 | 精品视频在线观看免费 | 糖心在线观看网 |