您好,登錄后才能下訂單哦!
這篇文章將為大家詳細(xì)講解有關(guān)Spring怎么找到對(duì)應(yīng)轉(zhuǎn)換器使用枚舉參數(shù),小編覺(jué)得挺實(shí)用的,因此分享給大家做個(gè)參考,希望大家閱讀完這篇文章后可以有所收獲。
所有的請(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ì)怎么做呢?
輸入?yún)?shù)
找到目標(biāo)參數(shù)
檢查是否需要特殊轉(zhuǎn)換邏輯
如果需要,進(jìn)行轉(zhuǎn)換
如果不需要,直接返回
獲取輸入?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)換為枚舉值。
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)換為偽代碼就是:
根據(jù)參數(shù)類(lèi)型和目標(biāo)類(lèi)型,構(gòu)造緩存 key
根據(jù)緩存 key從緩存中查詢轉(zhuǎn)換器
如果能找到且不是 NO_MATCH,返回轉(zhuǎn)換器;如果是 NO_MATCH,返回 null;如果未找到,繼續(xù)
通過(guò)org.springframework.core.convert.support.GenericConversionService.Converters#find查詢轉(zhuǎn)換器
如果未找到,檢查源類(lèi)型和目標(biāo)類(lèi)型是否可以強(qiáng)轉(zhuǎn),也就是類(lèi)型一致。如果是,返回 NoOpConverter,如果否,返回 null。
檢查找到的轉(zhuǎn)換器是否為 null,如果不是,將轉(zhuǎn)換器加入到緩存中,返回該轉(zhuǎn)換器
如果否,在緩存中添加 NO_MATCH 標(biāo)識(shí),返回 null
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ò)阻塞等待。
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; }
經(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)。
這里需要強(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)把它分享出去讓更多的人看到。
免責(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)容。