最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot實戰(zhàn):Spring如何找到對應(yīng)轉(zhuǎn)換器優(yōu)雅使用枚舉參數(shù)

 更新時間:2021年08月20日 17:33:54   作者:看山  
這篇文章主要介紹了SpringBoot實戰(zhàn)中Spring是如何找到對應(yīng)轉(zhuǎn)換器優(yōu)雅的使用枚舉參數(shù),文中附有詳細的實例代碼有需要的朋友可以參考下,希望可以有所幫助

找入口

請求入口是DispatcherServlet

所有的請求最終都會落到doDispatch方法中的

ha.handle(processedRequest, response, mappedHandler.getHandler())邏輯。

我們從這里出發(fā),一層一層向里扒。

跟著代碼深入,我們會找到

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方法處理參數(shù),然后調(diào)用doInvoke方法獲取返回值。

繼續(xù)深入,能夠找到

org.springframework.web.method.annotation.RequestParamMethodArgumentResolver#resolveArgument方法

這個方法就是解析參數(shù)的邏輯。

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

  1. 輸入?yún)?shù)
  2. 找到目標(biāo)參數(shù)
  3. 檢查是否需要特殊轉(zhuǎn)換邏輯
  4. 如果需要,進行轉(zhuǎn)換
  5. 如果不需要,直接返回

解析參數(shù)

獲取輸入?yún)?shù)的邏輯在

org.springframework.web.method.annotation.RequestParamMethodArgumentResolver#resolveName

單參數(shù)返回的是 String 類型,多參數(shù)返回 String 數(shù)組。

核心代碼如下:

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

所以說,無論我們的目標(biāo)參數(shù)是什么,輸入?yún)?shù)都是 String 類型或 String 數(shù)組

  • 然后 Spring 把它們轉(zhuǎn)換為我們期望的類型。
  • 找到目標(biāo)參數(shù)的邏輯在DispatcherServlet中,根據(jù) uri 找到對應(yīng)的 Controller 處理方法
  • 找到方法就找到了目標(biāo)參數(shù)類型。
  • 接下來就是檢查是否需要轉(zhuǎn)換邏輯,也就是
  • org.springframework.validation.DataBinder#convertIfNecessary
  • 顧名思義,如果需要就轉(zhuǎn)換,將字符串類型轉(zhuǎn)換為目標(biāo)類型。

在我們的例子中,就是將 String 轉(zhuǎn)換為枚舉值。

查找轉(zhuǎn)換器

org.springframework.beans.TypeConverterDelegate#convertIfNecessary方法中

繼續(xù)深扒找到這么一段邏輯:

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

這段邏輯中,調(diào)用了

 org.springframework.core.convert.support.GenericConversionService#canConvert方法

檢查是否可轉(zhuǎn)換,如果可以轉(zhuǎn)換,將會執(zhí)行類型轉(zhuǎn)換邏輯。

檢查是否可轉(zhuǎn)換的本質(zhì)就是檢查是否能夠找到對應(yīng)的轉(zhuǎn)換器。

  • 如果能找到,就用找到的轉(zhuǎn)換器開始轉(zhuǎn)換邏輯
  • 如果找不到,那就是不能轉(zhuǎn)換,走其他邏輯。

我們可以看看查找轉(zhuǎn)換器的代碼

org.springframework.core.convert.support.GenericConversionService#getConverter

可以對我們自己寫代碼有一些啟發(fā):

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

轉(zhuǎn)換為偽代碼就是:

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

查找轉(zhuǎn)換器

Spring 內(nèi)部使用Map作為緩存,用來存儲通用轉(zhuǎn)換器接口GenericConverter,這個接口會是我們自定義轉(zhuǎn)換器的包裝類。

  • 我們還可以看到,轉(zhuǎn)換器緩存用的是ConcurrentReferenceHashMap,這個類是線程安全的
  • 可以保證并發(fā)情況下,不會出現(xiàn)異常存儲。但是getConverter方法沒有使用同步邏輯。
  • 換句話說,并發(fā)請求時,可能存在性能損耗。

不過,對于 web 請求場景,并發(fā)損耗好過阻塞等待。

Spring 如何查找轉(zhuǎn)換器

org.springframework.core.convert.support.GenericConversionService.Converters#find

就是找到對應(yīng)轉(zhuǎn)換器的核心邏輯:

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 是通過源類型和目標(biāo)類型組合起來,查找對應(yīng)的轉(zhuǎn)換器。

而且,Spring 還通過getClassHierarchy方法,將源類型和目標(biāo)類型的家族族譜全部列出來,用雙層 for 循環(huán)遍歷查找。

上面的代碼中,還有一個matches方法,在這個方法里面,調(diào)用了ConverterFactory#getConverter方法

也就是用這個工廠方法,創(chuàng)建了指定類型的轉(zhuǎn)換器。

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

類型轉(zhuǎn)換

經(jīng)過上面的邏輯,已經(jīng)找到判斷可以進行轉(zhuǎn)換。

org.springframework.core.convert.support.GenericConversionService#convert

其核心邏輯就是已經(jīng)找到對應(yīng)的轉(zhuǎn)換器了,下面就是轉(zhuǎn)換邏輯

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)就是前文中g(shù)etConverter方法。

  • 此處還是可以給我們編碼上的一些借鑒的:getConverter方法在canConvert中調(diào)用了一次
    然后在后續(xù)真正轉(zhuǎn)換的時候又調(diào)用一次這是參數(shù)轉(zhuǎn)換邏輯
  • 我們該怎么優(yōu)化這種同一請求內(nèi)多次調(diào)用相同邏輯或者請求相同參數(shù)呢?
    那就是使用緩存。為了保持一次請求中前后兩次數(shù)據(jù)的一致性和請求的高效,推薦使用內(nèi)存緩存。

執(zhí)行到這里,直接調(diào)用

ConversionUtils.invokeConverter(converter, source, sourceType, targetType)轉(zhuǎn)換

其內(nèi)部是使用

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

這里就是調(diào)用ConverterFactory工廠類構(gòu)建轉(zhuǎn)換器(即IdCodeToEnumConverterFactory類的getConverter方法)

然后調(diào)用轉(zhuǎn)換器的conver方法(即IdCodeToEnumConverter類的convert方法),將輸入?yún)?shù)轉(zhuǎn)換為目標(biāo)類型。

具體實現(xiàn)可以看一下實戰(zhàn)篇中的代碼,這里不做贅述。

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

跟隨源碼找到自定義轉(zhuǎn)換器工廠類和轉(zhuǎn)換器類的實現(xiàn)邏輯

無論是GET請求,還是傳參式的POST請求(即Form模式)

這里需要強調(diào)一下的是,由于實戰(zhàn)篇中我們用到的例子是簡單參數(shù)的方式,也就是Controller的方法參數(shù)都是直接參數(shù)

  • 沒有包裝成對象。這樣的話,Spring 是通過RequestParamMethodArgumentResolver處理參數(shù)。
  • 如果是包裝成對象,會使用ModelAttributeMethodProcessor處理參數(shù)。這兩個處理類中查找類型轉(zhuǎn)換器邏輯都是相同的。
  • 都可以使用上面這種方式,實現(xiàn)枚舉參數(shù)的類型轉(zhuǎn)換。

但是 HTTP Body 方式卻不行,為什么呢?

  • Spring 對于 body 參數(shù)是通過RequestResponseBodyMethodProcessor處理的
  • 其內(nèi)部使用了MappingJackson2HttpMessageConverter轉(zhuǎn)換器,邏輯完全不同。

所以,想要實現(xiàn) body 的類型轉(zhuǎn)換,還需要走另外一種方式,更多關(guān)于Spring對應(yīng)轉(zhuǎn)換器的枚舉參數(shù)的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • img 加載網(wǎng)絡(luò)圖片失敗 顯示默認圖片的方法

    img 加載網(wǎng)絡(luò)圖片失敗 顯示默認圖片的方法

    下面小編就為大家?guī)硪黄猧mg 加載網(wǎng)絡(luò)圖片失敗 顯示默認圖片的方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-05-05
  • java右下角彈窗示例分享

    java右下角彈窗示例分享

    這篇文章主要介紹了java右下角彈窗示例,需要的朋友可以參考下
    2014-04-04
  • Java如何實現(xiàn)保證線程安全

    Java如何實現(xiàn)保證線程安全

    這篇文章主要介紹了Java如何實現(xiàn)保證線程安全問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2025-04-04
  • Maven使用方法詳及方式詳細介紹

    Maven使用方法詳及方式詳細介紹

    使用maven倉庫的話需要從網(wǎng)上下載maven的包,比如“apache-maven-3.5.4-bin.tar”,下載完成之后解壓,在解壓的文件夾中的conf目錄下的settings.xml文件夾下就可以配置maven遠程倉庫和本地倉庫的地址
    2022-11-11
  • 養(yǎng)成良好java代碼編碼規(guī)范

    養(yǎng)成良好java代碼編碼規(guī)范

    這篇文章主要介紹了如何養(yǎng)成良好java代碼編碼規(guī)范,規(guī)范需要平時編碼過程中注意,是一個慢慢養(yǎng)成的好習(xí)慣,下面小編就帶大家來一起詳細了解一下吧
    2019-06-06
  • 深入解析Java編程中的抽象類

    深入解析Java編程中的抽象類

    這篇文章主要介紹了Java編程中的抽象類,抽象類體現(xiàn)了Java面向?qū)ο缶幊痰奶匦?需要的朋友可以參考下
    2015-10-10
  • 基于JavaMail的常用類詳細介紹

    基于JavaMail的常用類詳細介紹

    以下是對JavaMail的常用類進行了詳細分析的介紹,需要的朋友可以過來參考下
    2013-09-09
  • Java創(chuàng)建多線程的8種方式集合

    Java創(chuàng)建多線程的8種方式集合

    這篇文章主要介紹了Java創(chuàng)建多線程的8種方式集合,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • SpringBoot集成Redis及SpringCache緩存管理示例詳解

    SpringBoot集成Redis及SpringCache緩存管理示例詳解

    本文介紹了如何在SpringBoot中集成Redis并使用SpringCache進行緩存管理,詳解了Redis的配置、使用以及SpringCache的注解,還闡述了SpringCache的工作原理,包括其AOP實現(xiàn)和與各種緩存框架的集成,使得開發(fā)者可以輕松實現(xiàn)緩存功能,以提高應(yīng)用性能
    2024-09-09
  • Java的異常類型總結(jié)

    Java的異常類型總結(jié)

    在本篇文章里小編給大家分享了關(guān)于Java的異常類型的相關(guān)知識點內(nèi)容,有需要的朋友們跟著學(xué)習(xí)下。
    2019-01-01

最新評論

五华县| 漳州市| 城口县| 九龙坡区| 海原县| 临洮县| 永川市| 中山市| 闸北区| 钦州市| 德化县| 荆州市| 科技| 忻城县| 锦州市| 祁门县| 吴川市| 塘沽区| 巍山| 长子县| 萨嘎县| 通江县| 灵武市| 延寿县| 义乌市| 科技| 施甸县| 清远市| 磐石市| 清苑县| 剑河县| 石泉县| 武胜县| 东阿县| 包头市| 冷水江市| 常熟市| 都江堰市| 清徐县| 定远县| 兴业县|