简单研究下接收集合参的使用以及接收原理。
1. 简单使用
1. 前置
原始类型HttpServletRequest 获取String 类型的数据, 可以获取多个参数的,也可以获取数组类型的
1. 接口
@GetMapping("/test3") public JSONResultUtil<String> test3(HttpServletRequest request) { String[] ids = request.getParameterValues("ids"); String id = request.getParameter("ids"); System.out.println("======"); System.out.println(Arrays.toString(ids)); System.out.println(id); return new JSONResultUtil<>(true, "ok", "test1"); }
测试:http://localhost:8088/test/test3?ids=1&ids=2&ids=3
结果:
[1, 2, 3]
1
可以看出getParameterValues 是返回数组类型;getParameter 是返回单个元素,如果有多个name 相同的会返回第一个元素。
2. SpringMVC 接收数组和集合
1》 映射集合
接口如下:
@GetMapping("/test1") public JSONResultUtil<String> test1(@RequestParam List<Integer> ids) { System.out.println(ids); return new JSONResultUtil<>(true, "ok", "test1"); }
下面两种方法都可以自动映射为List<Integer> 类型:
(1) 第一种方法:
http://localhost:8088/test/test1?ids=1&ids=2&ids=3
(2) 第二种方法:
http://localhost:8088/test/test1?ids=1,2,3
2》 数组同理, 也可以用上面方法进行接收
2. 接收集合原理
用如下数组类型的接口做分析:
@GetMapping("/test1") public JSONResultUtil<String> test1(@RequestParam Integer[] ids) { System.out.println(Arrays.toString(ids)); return new JSONResultUtil<>(true, "ok", "test1"); }
访问接口: http://localhost:8088/test/test1?ids=1,2
原理跟踪:
1. 解析函数参数入口:
org.springframework.web.method.support.InvocableHandlerMethod#getMethodArgumentValues
2. 接着调用到参数解析器
然后调用org.springframework.web.method.support.HandlerMethodArgumentResolverComposite#resolveArgument, HandlerMethodArgumentResolverComposite 相当于一个统一的入口,这里面会进行缓存和其他操作.
3. 继续获取参数以及转换
上面获取到真实的参数解析器是 RequestParamMethodArgumentResolver, 然后会调用resolver.resolveArgument, 实际会调用到父类方法: org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver#resolveArgument 所以核心的操作(获取参数以及对参数进行转换)都是在这里进行的。源码如下:
@Override public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { NamedValueInfo namedValueInfo = getNamedValueInfo(parameter); MethodParameter nestedParameter = parameter.nestedIfOptional(); Object resolvedName = resolveStringValue(namedValueInfo.name); if (resolvedName == null) { throw new IllegalArgumentException( "Specified name must not resolve to null: [" + namedValueInfo.name + "]"); } Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest); if (arg == null) { if (namedValueInfo.defaultValue != null) { arg = resolveStringValue(namedValueInfo.defaultValue); } else if (namedValueInfo.required && !nestedParameter.isOptional()) { handleMissingValue(namedValueInfo.name, nestedParameter, webRequest); } arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType()); } else if ("".equals(arg) && namedValueInfo.defaultValue != null) { arg = resolveStringValue(namedValueInfo.defaultValue); } if (binderFactory != null) { WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name); try { arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter); } catch (ConversionNotSupportedException ex) { throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(), namedValueInfo.name, parameter, ex.getCause()); } catch (TypeMismatchException ex) { throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(), namedValueInfo.name, parameter, ex.getCause()); } } handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest); return arg; }
核心过程如下:
1. org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver#getNamedValueInfo 获取注解上相关的信息并封装成下面对象: org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver.NamedValueInfo
/** * Represents the information about a named value, including name, whether it's required and a default value. */ protected static class NamedValueInfo { private final String name; private final boolean required; private final String defaultValue; public NamedValueInfo(String name, boolean required, String defaultValue) { this.name = name; this.required = required; this.defaultValue = defaultValue; } }
获取到的信息如下:
实际上就是解析我们@RequestParam 上面的注解的信息,包括默认值、是否必须、别名等,然后拿别名去request 找相关东西,找不到就根据默认值和必传信息进行后面处理。
2. 调用org.springframework.web.method.annotation.RequestParamMethodArgumentResolver#resolveName 解析字符串参数
@Override protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception { HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class); MultipartHttpServletRequest multipartRequest = WebUtils.getNativeRequest(servletRequest, MultipartHttpServletRequest.class); Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest); if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) { return mpArg; } Object arg = null; if (multipartRequest != null) { List<MultipartFile> files = multipartRequest.getFiles(name); if (!files.isEmpty()) { arg = (files.size() == 1 ? files.get(0) : files); } } if (arg == null) { String[] paramValues = request.getParameterValues(name); if (paramValues != null) { arg = (paramValues.length == 1 ? paramValues[0] : paramValues); } } return arg; }
可以看到是调用request 获取参数。 这里获取到的参数如下:
3. 接下来处理参数。判断获取到的参数,如果为空就取默认值或者走空值处理逻辑;接下来走 if (binderFactory != null) 里面的逻辑进行参数类型的转换,也就是将String[] 转换为Integer[]。 接下来转换的核心逻辑是在这里
继续调用到org.springframework.validation.DataBinder#convertIfNecessary(java.lang.Object, java.lang.Class<T>, org.springframework.core.MethodParameter)
@Override public <T> T convertIfNecessary(Object value, Class<T> requiredType, MethodParameter methodParam) throws TypeMismatchException { return getTypeConverter().convertIfNecessary(value, requiredType, methodParam); }
... 经过一系列调用调用到: org.springframework.beans.TypeConverterDelegate#convertIfNecessary(java.lang.String, java.lang.Object, java.lang.Object, java.lang.Class<T>, org.springframework.core.convert.TypeDescriptor): 下面有个逻辑是,先用自己的PropertyEditor(自定义的属性编辑器),如果没有就用默认的转换器,如果默认的转换器也不匹配就用默认的属性编辑器
public <T> T convertIfNecessary(String propertyName, Object oldValue, Object newValue, Class<T> requiredType, TypeDescriptor typeDescriptor) throws IllegalArgumentException { // Custom editor for this type? PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName); ConversionFailedException conversionAttemptEx = null; // No custom editor but custom ConversionService specified? ConversionService conversionService = this.propertyEditorRegistry.getConversionService(); if (editor == null && conversionService != null && newValue != null && typeDescriptor != null) { TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue); if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) { try { return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor); } catch (ConversionFailedException ex) { // fallback to default conversion logic below conversionAttemptEx = ex; } } } Object convertedValue = newValue; // Value not of required type? if (editor != null || (requiredType != null && !ClassUtils.isAssignableValue(requiredType, convertedValue))) { if (typeDescriptor != null && requiredType != null && Collection.class.isAssignableFrom(requiredType) && convertedValue instanceof String) { TypeDescriptor elementTypeDesc = typeDescriptor.getElementTypeDescriptor(); if (elementTypeDesc != null) { Class<?> elementType = elementTypeDesc.getType(); if (Class.class == elementType || Enum.class.isAssignableFrom(elementType)) { convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue); } } } if (editor == null) { editor = findDefaultEditor(requiredType); } convertedValue = doConvertValue(oldValue, convertedValue, requiredType, editor); } boolean standardConversion = false; if (requiredType != null) { // Try to apply some standard type conversion rules if appropriate. if (convertedValue != null) { if (Object.class == requiredType) { return (T) convertedValue; } else if (requiredType.isArray()) { // Array required -> apply appropriate conversion of elements. if (convertedValue instanceof String && Enum.class.isAssignableFrom(requiredType.getComponentType())) { convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue); } return (T) convertToTypedArray(convertedValue, propertyName, requiredType.getComponentType()); } else if (convertedValue instanceof Collection) { // Convert elements to target type, if determined. convertedValue = convertToTypedCollection( (Collection<?>) convertedValue, propertyName, requiredType, typeDescriptor); standardConversion = true; } else if (convertedValue instanceof Map) { // Convert keys and values to respective target type, if determined. convertedValue = convertToTypedMap( (Map<?, ?>) convertedValue, propertyName, requiredType, typeDescriptor); standardConversion = true; } if (convertedValue.getClass().isArray() && Array.getLength(convertedValue) == 1) { convertedValue = Array.get(convertedValue, 0); standardConversion = true; } if (String.class == requiredType && ClassUtils.isPrimitiveOrWrapper(convertedValue.getClass())) { // We can stringify any primitive value... return (T) convertedValue.toString(); } else if (convertedValue instanceof String && !requiredType.isInstance(convertedValue)) { if (conversionAttemptEx == null && !requiredType.isInterface() && !requiredType.isEnum()) { try { Constructor<T> strCtor = requiredType.getConstructor(String.class); return BeanUtils.instantiateClass(strCtor, convertedValue); } catch (NoSuchMethodException ex) { // proceed with field lookup if (logger.isTraceEnabled()) { logger.trace("No String constructor found on type [" + requiredType.getName() + "]", ex); } } catch (Exception ex) { if (logger.isDebugEnabled()) { logger.debug("Construction via String failed for type [" + requiredType.getName() + "]", ex); } } } String trimmedValue = ((String) convertedValue).trim(); if (requiredType.isEnum() && "".equals(trimmedValue)) { // It's an empty enum identifier: reset the enum value to null. return null; } convertedValue = attemptToConvertStringToEnum(requiredType, trimmedValue, convertedValue); standardConversion = true; } else if (convertedValue instanceof Number && Number.class.isAssignableFrom(requiredType)) { convertedValue = NumberUtils.convertNumberToTargetClass( (Number) convertedValue, (Class<Number>) requiredType); standardConversion = true; } } else { // convertedValue == null if (javaUtilOptionalEmpty != null && requiredType == javaUtilOptionalEmpty.getClass()) { convertedValue = javaUtilOptionalEmpty; } } if (!ClassUtils.isAssignableValue(requiredType, convertedValue)) { if (conversionAttemptEx != null) { // Original exception from former ConversionService call above... throw conversionAttemptEx; } else if (conversionService != null) { // ConversionService not tried before, probably custom editor found // but editor couldn't produce the required type... TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue); if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) { return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor); } } // Definitely doesn't match: throw IllegalArgumentException/IllegalStateException StringBuilder msg = new StringBuilder(); msg.append("Cannot convert value of type '").append(ClassUtils.getDescriptiveType(newValue)); msg.append("' to required type '").append(ClassUtils.getQualifiedName(requiredType)).append("'"); if (propertyName != null) { msg.append(" for property '").append(propertyName).append("'"); } if (editor != null) { msg.append(": PropertyEditor [").append(editor.getClass().getName()).append( "] returned inappropriate value of type '").append( ClassUtils.getDescriptiveType(convertedValue)).append("'"); throw new IllegalArgumentException(msg.toString()); } else { msg.append(": no matching editors or conversion strategy found"); throw new IllegalStateException(msg.toString()); } } } if (conversionAttemptEx != null) { if (editor == null && !standardConversion && requiredType != null && Object.class != requiredType) { throw conversionAttemptEx; } logger.debug("Original ConversionService attempt failed - ignored since " + "PropertyEditor based conversion eventually succeeded", conversionAttemptEx); } return (T) convertedValue; }
继续调用 (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
然后调用到: org.springframework.core.convert.support.GenericConversionService#convert(java.lang.Object, org.springframework.core.convert.TypeDescriptor, org.springframework.core.convert.TypeDescriptor) 核心找转换器以及转换逻辑是在这里
public Object convert(Object source, 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); }
(1) 找转换器
org.springframework.core.convert.support.GenericConversionService#getConverter
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; } 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; } 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; }
可以看到核心的逻辑是org.springframework.core.convert.support.GenericConversionService.Converters#find 方法区匹配(调用org.springframework.core.convert.support.GenericConversionService.Converters#getClassHierarchy 获取类的继承关系,然后进行匹配),获取到之后加入缓存。
org.springframework.core.convert.support.GenericConversionService.Converters#find找转换器逻辑:
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; } 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; }
实际是根据原类型和目的类型创建一个ConvertiblePair 对象,然后从 org.springframework.core.convert.support.GenericConversionService.Converters#converters 缓存拿。这里获取到的converter 是: org.springframework.core.convert.support.ArrayToArrayConverter。
org.springframework.core.convert.support.GenericConversionService.Converters#converters 缓存时机
- 项目启动过程中: 调用到org.springframework.core.convert.support.DefaultConversionService#addDefaultConverters
public static void addDefaultConverters(ConverterRegistry converterRegistry) { addScalarConverters(converterRegistry); addCollectionConverters(converterRegistry); converterRegistry.addConverter(new ByteBufferConverter((ConversionService) converterRegistry)); if (jsr310Available) { Jsr310ConverterRegistrar.registerJsr310Converters(converterRegistry); } converterRegistry.addConverter(new ObjectToObjectConverter()); converterRegistry.addConverter(new IdToEntityConverter((ConversionService) converterRegistry)); converterRegistry.addConverter(new FallbackObjectToStringConverter()); if (javaUtilOptionalClassAvailable) { converterRegistry.addConverter(new ObjectToOptionalConverter((ConversionService) converterRegistry)); } } private static void addScalarConverters(ConverterRegistry converterRegistry) { converterRegistry.addConverterFactory(new NumberToNumberConverterFactory()); converterRegistry.addConverterFactory(new StringToNumberConverterFactory()); converterRegistry.addConverter(Number.class, String.class, new ObjectToStringConverter()); converterRegistry.addConverter(new StringToCharacterConverter()); converterRegistry.addConverter(Character.class, String.class, new ObjectToStringConverter()); converterRegistry.addConverter(new NumberToCharacterConverter()); converterRegistry.addConverterFactory(new CharacterToNumberFactory()); converterRegistry.addConverter(new StringToBooleanConverter()); converterRegistry.addConverter(Boolean.class, String.class, new ObjectToStringConverter()); converterRegistry.addConverterFactory(new StringToEnumConverterFactory()); converterRegistry.addConverter(new EnumToStringConverter((ConversionService) converterRegistry)); converterRegistry.addConverterFactory(new IntegerToEnumConverterFactory()); converterRegistry.addConverter(new EnumToIntegerConverter((ConversionService) converterRegistry)); converterRegistry.addConverter(new StringToLocaleConverter()); converterRegistry.addConverter(Locale.class, String.class, new ObjectToStringConverter()); converterRegistry.addConverter(new StringToCharsetConverter()); converterRegistry.addConverter(Charset.class, String.class, new ObjectToStringConverter()); converterRegistry.addConverter(new StringToCurrencyConverter()); converterRegistry.addConverter(Currency.class, String.class, new ObjectToStringConverter()); converterRegistry.addConverter(new StringToPropertiesConverter()); converterRegistry.addConverter(new PropertiesToStringConverter()); converterRegistry.addConverter(new StringToUUIDConverter()); converterRegistry.addConverter(UUID.class, String.class, new ObjectToStringConverter()); } public static void addCollectionConverters(ConverterRegistry converterRegistry) { ConversionService conversionService = (ConversionService) converterRegistry; converterRegistry.addConverter(new ArrayToCollectionConverter(conversionService)); converterRegistry.addConverter(new CollectionToArrayConverter(conversionService)); converterRegistry.addConverter(new ArrayToArrayConverter(conversionService)); converterRegistry.addConverter(new CollectionToCollectionConverter(conversionService)); converterRegistry.addConverter(new MapToMapConverter(conversionService)); converterRegistry.addConverter(new ArrayToStringConverter(conversionService)); converterRegistry.addConverter(new StringToArrayConverter(conversionService)); converterRegistry.addConverter(new ArrayToObjectConverter(conversionService)); converterRegistry.addConverter(new ObjectToArrayConverter(conversionService)); converterRegistry.addConverter(new CollectionToStringConverter(conversionService)); converterRegistry.addConverter(new StringToCollectionConverter(conversionService)); converterRegistry.addConverter(new CollectionToObjectConverter(conversionService)); converterRegistry.addConverter(new ObjectToCollectionConverter(conversionService)); if (streamAvailable) { converterRegistry.addConverter(new StreamConverter(conversionService)); } }
- 启动过程中调用到: org.springframework.format.datetime.standard.DateTimeConverters#registerConverters 注册日期相关:
public static void registerConverters(ConverterRegistry registry) { DateFormatterRegistrar.addDateConverters(registry); registry.addConverter(new LocalDateTimeToLocalDateConverter()); registry.addConverter(new LocalDateTimeToLocalTimeConverter()); registry.addConverter(new ZonedDateTimeToLocalDateConverter()); registry.addConverter(new ZonedDateTimeToLocalTimeConverter()); registry.addConverter(new ZonedDateTimeToLocalDateTimeConverter()); registry.addConverter(new ZonedDateTimeToOffsetDateTimeConverter()); registry.addConverter(new ZonedDateTimeToInstantConverter()); registry.addConverter(new OffsetDateTimeToLocalDateConverter()); registry.addConverter(new OffsetDateTimeToLocalTimeConverter()); registry.addConverter(new OffsetDateTimeToLocalDateTimeConverter()); registry.addConverter(new OffsetDateTimeToZonedDateTimeConverter()); registry.addConverter(new OffsetDateTimeToInstantConverter()); registry.addConverter(new CalendarToZonedDateTimeConverter()); registry.addConverter(new CalendarToOffsetDateTimeConverter()); registry.addConverter(new CalendarToLocalDateConverter()); registry.addConverter(new CalendarToLocalTimeConverter()); registry.addConverter(new CalendarToLocalDateTimeConverter()); registry.addConverter(new CalendarToInstantConverter()); registry.addConverter(new LongToInstantConverter()); registry.addConverter(new InstantToLongConverter()); }
org.springframework.format.datetime.DateFormatterRegistrar#addDateConverters
public static void addDateConverters(ConverterRegistry converterRegistry) { converterRegistry.addConverter(new DateToLongConverter()); converterRegistry.addConverter(new DateToCalendarConverter()); converterRegistry.addConverter(new CalendarToDateConverter()); converterRegistry.addConverter(new CalendarToLongConverter()); converterRegistry.addConverter(new LongToDateConverter()); converterRegistry.addConverter(new LongToCalendarConverter()); }
- org.springframework.core.convert.support.GenericConversionService#addConverter(org.springframework.core.convert.converter.Converter<?,?>) 获取Spring容器中对象相关对象然后进行注册
@Override public void addFormatters(FormatterRegistry registry) { for (Converter<?, ?> converter : getBeansOfType(Converter.class)) { registry.addConverter(converter); } for (GenericConverter converter : getBeansOfType(GenericConverter.class)) { registry.addConverter(converter); } for (Formatter<?> formatter : getBeansOfType(Formatter.class)) { registry.addFormatter(formatter); } } private <T> Collection<T> getBeansOfType(Class<T> type) { return this.beanFactory.getBeansOfType(type).values(); } @Override public void addConverter(Converter<?, ?> converter) { ResolvableType[] typeInfo = getRequiredTypeInfo(converter.getClass(), Converter.class); if (typeInfo == null && converter instanceof DecoratingProxy) { typeInfo = getRequiredTypeInfo(((DecoratingProxy) converter).getDecoratedClass(), Converter.class); } if (typeInfo == null) { throw new IllegalArgumentException("Unable to determine source type <S> and target type <T> for your " + "Converter [" + converter.getClass().getName() + "]; does the class parameterize those types?"); } addConverter(new ConverterAdapter(converter, typeInfo[0], typeInfo[1])); } private ResolvableType[] getRequiredTypeInfo(Class<?> converterClass, Class<?> genericIfc) { ResolvableType resolvableType = ResolvableType.forClass(converterClass).as(genericIfc); ResolvableType[] generics = resolvableType.getGenerics(); if (generics.length < 2) { return null; } Class<?> sourceType = generics[0].resolve(); Class<?> targetType = generics[1].resolve(); if (sourceType == null || targetType == null) { return null; } return generics; }
(2) 进行转换
1》 org.springframework.core.convert.support.ConversionUtils#invokeConverter 进行转换
public static Object invokeConverter(GenericConverter converter, Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { try { return converter.convert(source, sourceType, targetType); } catch (ConversionFailedException ex) { throw ex; } catch (Throwable ex) { throw new ConversionFailedException(sourceType, targetType, source, ex); } }
2》 继续调用到org.springframework.core.convert.support.ArrayToArrayConverter#convert
final class ArrayToArrayConverter implements ConditionalGenericConverter { private final CollectionToArrayConverter helperConverter; private final ConversionService conversionService; public ArrayToArrayConverter(ConversionService conversionService) { this.helperConverter = new CollectionToArrayConverter(conversionService); this.conversionService = conversionService; } @Override public Set<ConvertiblePair> getConvertibleTypes() { return Collections.singleton(new ConvertiblePair(Object[].class, Object[].class)); } @Override public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { return this.helperConverter.matches(sourceType, targetType); } @Override public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { if (this.conversionService instanceof GenericConversionService && ((GenericConversionService) this.conversionService).canBypassConvert( sourceType.getElementTypeDescriptor(), targetType.getElementTypeDescriptor())) { return source; } List<Object> sourceList = Arrays.asList(ObjectUtils.toObjectArray(source)); return this.helperConverter.convert(sourceList, sourceType, targetType); } }
这里先构造了一个Arrays.ArrayList 内部类,然后调用 org.springframework.core.convert.support.CollectionToArrayConverter#convert 将集合转换为数组:
@Override public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { if (source == null) { return null; } Collection<?> sourceCollection = (Collection<?>) source; Object array = Array.newInstance(targetType.getElementTypeDescriptor().getType(), sourceCollection.size()); int i = 0; for (Object sourceElement : sourceCollection) { Object targetElement = this.conversionService.convert(sourceElement, sourceType.elementTypeDescriptor(sourceElement), targetType.getElementTypeDescriptor()); Array.set(array, i++, targetElement); } return array; }
首先调用 Array.newInstance 创建一个数组
然后调用 this.conversionService.convert 再次将String 转为Integer, 实际是又走一遍上面获取转换器以及转换的流程。最后调用Array.set 设置元素。 转换实际会调用到: org.springframework.core.convert.support.StringToNumberConverterFactory.StringToNumber
private static final class StringToNumber<T extends Number> implements Converter<String, T> { private final Class<T> targetType; public StringToNumber(Class<T> targetType) { this.targetType = targetType; } @Override public T convert(String source) { if (source.isEmpty()) { return null; } return NumberUtils.parseNumber(source, this.targetType); } }
3》 最近转换完成之后的数据结果如下:
4. 然后结束这个参数的解析,将参数给方法 org.springframework.web.method.support.InvocableHandlerMethod#getMethodArgumentValues, 该方法内部再将结果记录到 args[i], 开始解析后面的参数
补充: 测试对于类型转换失败的类型的处理
还是上面接口,访问:
http://localhost:8088/test/test1?ids=1&ids=2xx
报错如下:
org.springframework.web.method.annotation.MethodArgumentTypeMismatchException: Failed to convert value of type 'java.lang.String[]' to required type 'java.lang.Integer[]'; nested exception is java.lang.NumberFormatException: For input string: "2xx" at org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver.resolveArgument(AbstractNamedValueMethodArgumentResolver.java:128) at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121) at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:158) at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:128) at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:116) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738) at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85) at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:963) at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897) at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970) at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861) at javax.servlet.http.HttpServlet.service(HttpServlet.java:622) at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846) at javax.servlet.http.HttpServlet.service(HttpServlet.java:729) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:230) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:105) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:81) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:474) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:349) at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:783) at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:798) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1434) at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:748) Caused by: java.lang.NumberFormatException: For input string: "2xx" at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) at java.lang.Integer.parseInt(Integer.java:580) at java.lang.Integer.valueOf(Integer.java:766) at org.springframework.util.NumberUtils.parseNumber(NumberUtils.java:208)
可以看出,在调用 org.springframework.util.NumberUtils#parseNumber(java.lang.String, java.lang.Class<T>) 转换数字的时候报错。
补充: 对于ids=1,2 这种参数的转换
对于这种参数的传递,在org.springframework.core.convert.support.GenericConversionService#convert(java.lang.Object, org.springframework.core.convert.TypeDescriptor, org.springframework.core.convert.TypeDescriptor) 方法中,实参 source是 字符串 "1,2", 获取到的转换器converter 是org.springframework.core.convert.support.StringToArrayConverter, 因此调用该类,源码如下:
final class StringToArrayConverter implements ConditionalGenericConverter { private final ConversionService conversionService; public StringToArrayConverter(ConversionService conversionService) { this.conversionService = conversionService; } @Override public Set<ConvertiblePair> getConvertibleTypes() { return Collections.singleton(new ConvertiblePair(String.class, Object[].class)); } @Override public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { return this.conversionService.canConvert(sourceType, targetType.getElementTypeDescriptor()); } @Override public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { if (source == null) { return null; } String string = (String) source; String[] fields = StringUtils.commaDelimitedListToStringArray(string); Object target = Array.newInstance(targetType.getElementTypeDescriptor().getType(), fields.length); for (int i = 0; i < fields.length; i++) { String sourceElement = fields[i]; Object targetElement = this.conversionService.convert(sourceElement.trim(), sourceType, targetType.getElementTypeDescriptor()); Array.set(target, i, targetElement); } return target; } }
可以看到是先将source 调用org.springframework.util.StringUtils#commaDelimitedListToStringArray 按逗号分割为数组,然后同上面一样调用Array 相关方法创建数组、转换对象、然后放到数组对应位置。org.springframework.util.StringUtils#commaDelimitedListToStringArray 如下:
public static String[] commaDelimitedListToStringArray(String str) { return delimitedListToStringArray(str, ","); }
补充: List 不写实际类型参数默认是String 类型
1. 比如接口:
@GetMapping("/test2") public JSONResultUtil<String> test2(@RequestParam List ids) { System.out.println(ids); return new JSONResultUtil<>(true, "ok", "test1"); }
2. 调用 http://localhost:8088/test/test2?ids=1,2
3. 结果:
4. 源码跟踪:
在调用 org.springframework.core.convert.support.StringToCollectionConverter#convert 获取到的elementDesc 为空,所以直接将字符串添加到集合中:
@Override public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { if (source == null) { return null; } String string = (String) source; String[] fields = StringUtils.commaDelimitedListToStringArray(string); TypeDescriptor elementDesc = targetType.getElementTypeDescriptor(); Collection<Object> target = CollectionFactory.createCollection(targetType.getType(), (elementDesc != null ? elementDesc.getType() : null), fields.length); if (elementDesc == null) { for (String field : fields) { target.add(field.trim()); } } else { for (String field : fields) { Object targetElement = this.conversionService.convert(field.trim(), sourceType, elementDesc); target.add(targetElement); } } return target; }
3. 注册自己的converter
1. 编写converter
package cn.xm.config; import org.springframework.core.convert.converter.Converter; import org.springframework.stereotype.Component; import java.text.SimpleDateFormat; import java.util.Date; @Component public class DateConverter implements Converter<String, Date> { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @Override public Date convert(String source) { try { return sdf.parse(source); } catch (Exception e) { e.printStackTrace(); } return null; } }
2. 测试接口:
@GetMapping("/test3") public JSONResultUtil<String> test3(@RequestParam Date date) { System.out.println(date); return new JSONResultUtil<>(true, "ok", "test1"); }
3. 测试: 访问接口 http://localhost:8088/test/test3?date=2022-02-02%2015:15:14 即可
4. 使用PropertyEditor 解决以及原理查看
PropertyEditor 是jdk 提供的属性编辑器,用于将字符串转换为其他类型的对象。
1. 简单使用
1. 定义转换器
package cn.xm.config; import org.apache.commons.lang3.time.DateUtils; import java.beans.PropertyEditorSupport; import java.text.ParseException; public class MyEditor extends PropertyEditorSupport { @Override public void setAsText(String text) throws IllegalArgumentException { try { System.out.println(text); this.setValue(DateUtils.parseDate(text + " 22:22:22", "yyyy-MM-dd HH:mm:ss")); } catch (ParseException e) { } } }
2. controller 中使用@InitBinder 注册自定义的属性编辑器
@GetMapping("/test4") public JSONResultUtil<String> test4(@RequestParam Date id) { System.out.println(id); return new JSONResultUtil<>(true, "ok", "test1"); } @InitBinder public void initBinder(WebDataBinder dataBinder) { dataBinder.registerCustomEditor(Date.class, new MyEditor()); }
2. 源码解读
1. org.springframework.beans.TypeConverterDelegate#convertIfNecessary(java.lang.String, java.lang.Object, java.lang.Object, java.lang.Class<T>, org.springframework.core.convert.TypeDescriptor) 进行转换的时候,第一行代码获取到的ecitor 是 MyEditor 实例。
2. 接着调用org.springframework.beans.TypeConverterDelegate#doConvertValue 进行转换
private Object doConvertValue(Object oldValue, Object newValue, Class<?> requiredType, PropertyEditor editor) { Object convertedValue = newValue; if (editor != null && !(convertedValue instanceof String)) { // Not a String -> use PropertyEditor's setValue. // With standard PropertyEditors, this will return the very same object; // we just want to allow special PropertyEditors to override setValue // for type conversion from non-String values to the required type. try { editor.setValue(convertedValue); Object newConvertedValue = editor.getValue(); if (newConvertedValue != convertedValue) { convertedValue = newConvertedValue; // Reset PropertyEditor: It already did a proper conversion. // Don't use it again for a setAsText call. editor = null; } } catch (Exception ex) { if (logger.isDebugEnabled()) { logger.debug("PropertyEditor [" + editor.getClass().getName() + "] does not support setValue call", ex); } // Swallow and proceed. } } Object returnValue = convertedValue; if (requiredType != null && !requiredType.isArray() && convertedValue instanceof String[]) { // Convert String array to a comma-separated String. // Only applies if no PropertyEditor converted the String array before. // The CSV String will be passed into a PropertyEditor's setAsText method, if any. if (logger.isTraceEnabled()) { logger.trace("Converting String array to comma-delimited String [" + convertedValue + "]"); } convertedValue = StringUtils.arrayToCommaDelimitedString((String[]) convertedValue); } if (convertedValue instanceof String) { if (editor != null) { // Use PropertyEditor's setAsText in case of a String value. if (logger.isTraceEnabled()) { logger.trace("Converting String to [" + requiredType + "] using property editor [" + editor + "]"); } String newTextValue = (String) convertedValue; return doConvertTextValue(oldValue, newTextValue, editor); } else if (String.class == requiredType) { returnValue = convertedValue; } } return returnValue; }
3. 接着调用:org.springframework.beans.TypeConverterDelegate#doConvertTextValue
private Object doConvertTextValue(Object oldValue, String newTextValue, PropertyEditor editor) { try { editor.setValue(oldValue); } catch (Exception ex) { if (logger.isDebugEnabled()) { logger.debug("PropertyEditor [" + editor.getClass().getName() + "] does not support setValue call", ex); } // Swallow and proceed. } editor.setAsText(newTextValue); return editor.getValue(); }
这里就调用到我们自己的类: cn.xm.config.MyEditor#setAsText, 然后调用 editor.getValue() 获取对象,这就是jdk 自带的PropertyEditor 的用法。
简单的说:
Spring有两种自动类型转换器,一种是Converter(适用于全局转换),一种是PropertyEditor(对单个controller 生效)。
Converter是类型转换成类型,Editor:从string类型转换为其他类型。
从某种程度上,Converter包含Editor。如果出现需要从string转换到其他类型。首选Editor。