您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關Spring怎么找到對應轉換器使用枚舉參數,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。
所有的請求最終都會落到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方法
這個方法就是解析參數的邏輯。
試想一下,如果是我們自己實現這段邏輯,會怎么做呢?
輸入參數
找到目標參數
檢查是否需要特殊轉換邏輯
如果需要,進行轉換
如果不需要,直接返回
獲取輸入參數的邏輯在
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; }
轉換為偽代碼就是:
根據參數類型和目標類型,構造緩存 key
根據緩存 key從緩存中查詢轉換器
如果能找到且不是 NO_MATCH,返回轉換器;如果是 NO_MATCH,返回 null;如果未找到,繼續
通過org.springframework.core.convert.support.GenericConversionService.Converters#find查詢轉換器
如果未找到,檢查源類型和目標類型是否可以強轉,也就是類型一致。如果是,返回 NoOpConverter,如果否,返回 null。
檢查找到的轉換器是否為 null,如果不是,將轉換器加入到緩存中,返回該轉換器
如果否,在緩存中添加 NO_MATCH 標識,返回 null
Spring 內部使用Map作為緩存,用來存儲通用轉換器接口GenericConverter,這個接口會是我們自定義轉換器的包裝類。
我們還可以看到,轉換器緩存用的是ConcurrentReferenceHashMap,這個類是線程安全的
可以保證并發情況下,不會出現異常存儲。但是getConverter方法沒有使用同步邏輯。
換句話說,并發請求時,可能存在性能損耗。
不過,對于 web 請求場景,并發損耗好過阻塞等待。
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處理參數。這兩個處理類中查找類型轉換器邏輯都是相同的。
都可以使用上面這種方式,實現枚舉參數的類型轉換。
但是 HTTP Body 方式卻不行,為什么呢?
Spring 對于 body 參數是通過RequestResponseBodyMethodProcessor處理的
其內部使用了MappingJackson2HttpMessageConverter轉換器,邏輯完全不同。
關于“Spring怎么找到對應轉換器使用枚舉參數”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,使各位可以學到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。