溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Spring怎么找到對(duì)應(yīng)轉(zhuǎn)換器使用枚舉參數(shù)

發(fā)布時(shí)間:2021-08-21 10:51:37 來(lái)源:億速云 閱讀:129 作者:小新 欄目:開(kāi)發(fā)技術(shù)

這篇文章將為大家詳細(xì)講解有關(guān)Spring怎么找到對(duì)應(yīng)轉(zhuǎn)換器使用枚舉參數(shù),小編覺(jué)得挺實(shí)用的,因此分享給大家做個(gè)參考,希望大家閱讀完這篇文章后可以有所收獲。

找入口

請(qǐng)求入口是DispatcherServlet

所有的請(qǐng)求最終都會(huì)落到doDispatch方法中的

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

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

跟著代碼深入,我們會(huì)找到

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

可以看出,這里面通過(guò)getMethodArgumentValues方法處理參數(shù),然后調(diào)用doInvoke方法獲取返回值。

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

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

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

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

  1. 輸入?yún)?shù)

  2. 找到目標(biāo)參數(shù)

  3. 檢查是否需要特殊轉(zhuǎn)換邏輯

  4. 如果需要,進(jìn)行轉(zhuǎn)換

  5. 如果不需要,直接返回

Spring怎么找到對(duì)應(yīng)轉(zhuǎn)換器使用枚舉參數(shù)

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

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

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

核心代碼如下:

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

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

  • 然后 Spring 把它們轉(zhuǎn)換為我們期望的類(lèi)型。

  • 找到目標(biāo)參數(shù)的邏輯在DispatcherServlet中,根據(jù) uri 找到對(duì)應(yīng)的 Controller 處理方法

  • 找到方法就找到了目標(biāo)參數(shù)類(lèi)型。

  • 接下來(lái)就是檢查是否需要轉(zhuǎn)換邏輯,也就是

  • org.springframework.validation.DataBinder#convertIfNecessary

  • 顧名思義,如果需要就轉(zhuǎn)換,將字符串類(lèi)型轉(zhuǎn)換為目標(biāo)類(lèi)型。

在我們的例子中,就是將 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)換,將會(huì)執(zhí)行類(lèi)型轉(zhuǎn)換邏輯。

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

  • 如果能找到,就用找到的轉(zhuǎn)換器開(kāi)始轉(zhuǎn)換邏輯

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

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

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

可以對(duì)我們自己寫(xiě)代碼有一些啟發(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ù)類(lèi)型和目標(biāo)類(lèi)型,構(gòu)造緩存 key

  2. 根據(jù)緩存 key從緩存中查詢轉(zhuǎn)換器

  3. 如果能找到且不是 NO_MATCH,返回轉(zhuǎn)換器;如果是 NO_MATCH,返回 null;如果未找到,繼續(xù)

  4. 通過(guò)org.springframework.core.convert.support.GenericConversionService.Converters#find查詢轉(zhuǎn)換器

  5. 如果未找到,檢查源類(lèi)型和目標(biāo)類(lèi)型是否可以強(qiáng)轉(zhuǎn),也就是類(lèi)型一致。如果是,返回 NoOpConverter,如果否,返回 null。

  6. 檢查找到的轉(zhuǎn)換器是否為 null,如果不是,將轉(zhuǎn)換器加入到緩存中,返回該轉(zhuǎn)換器

  7. 如果否,在緩存中添加 NO_MATCH 標(biāo)識(shí),返回 null

Spring怎么找到對(duì)應(yīng)轉(zhuǎn)換器使用枚舉參數(shù)

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

  • 我們還可以看到,轉(zhuǎn)換器緩存用的是ConcurrentReferenceHashMap,這個(gè)類(lèi)是線程安全的

  • 可以保證并發(fā)情況下,不會(huì)出現(xiàn)異常存儲(chǔ)。但是getConverter方法沒(méi)有使用同步邏輯。

  • 換句話說(shuō),并發(fā)請(qǐng)求時(shí),可能存在性能損耗。

不過(guò),對(duì)于 web 請(qǐng)求場(chǎng)景,并發(fā)損耗好過(guò)阻塞等待。

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

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

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

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

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

也就是用這個(gè)工廠方法,創(chuàng)建了指定類(lèi)型的轉(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;
}

類(lèi)型轉(zhuǎn)換

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

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

其核心邏輯就是已經(jīng)找到對(duì)應(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)換的時(shí)候又調(diào)用一次這是參數(shù)轉(zhuǎn)換邏輯

  • 我們?cè)撛趺磧?yōu)化這種同一請(qǐng)求內(nèi)多次調(diào)用相同邏輯或者請(qǐng)求相同參數(shù)呢?
    那就是使用緩存。為了保持一次請(qǐng)求中前后兩次數(shù)據(jù)的一致性和請(qǐng)求的高效,推薦使用內(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工廠類(lèi)構(gòu)建轉(zhuǎn)換器(即IdCodeToEnumConverterFactory類(lèi)的getConverter方法)

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

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

至此,我們把整個(gè)路程通了下來(lái)。

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

無(wú)論是GET請(qǐng)求,還是傳參式的POST請(qǐng)求(即Form模式)

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

  • 沒(méi)有包裝成對(duì)象。這樣的話,Spring 是通過(guò)RequestParamMethodArgumentResolver處理參數(shù)。

  • 如果是包裝成對(duì)象,會(huì)使用ModelAttributeMethodProcessor處理參數(shù)。這兩個(gè)處理類(lèi)中查找類(lèi)型轉(zhuǎn)換器邏輯都是相同的。

  • 都可以使用上面這種方式,實(shí)現(xiàn)枚舉參數(shù)的類(lèi)型轉(zhuǎn)換。

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

  • Spring 對(duì)于 body 參數(shù)是通過(guò)RequestResponseBodyMethodProcessor處理的

  • 其內(nèi)部使用了MappingJackson2HttpMessageConverter轉(zhuǎn)換器,邏輯完全不同。

關(guān)于“Spring怎么找到對(duì)應(yīng)轉(zhuǎn)換器使用枚舉參數(shù)”這篇文章就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,使各位可以學(xué)到更多知識(shí),如果覺(jué)得文章不錯(cuò),請(qǐng)把它分享出去讓更多的人看到。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI