• SpringMVC 接收参数(二)接收数组和集合参数&转换器TypeConverter的使用&属性编辑器PropertyEditor使用


      简单研究下接收集合参的使用以及接收原理。

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

    继续调用 (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;
            }
    View Code

      可以看到核心的逻辑是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;
            }
    View Code

      实际是根据原类型和目的类型创建一个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));
            }
        }
    View Code

    - 启动过程中调用到: 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;
        }
    View Code

    (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)
    View Code

      可以看出,在调用 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;
        }
    View Code

    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。

  • 相关阅读:
    《深入理解Android内核设计思想》已陆续在全国各大书店及网上书店上市,感谢大家一直以来的支持~~
    PKUSC2019 D2T2
    Java面试(二)
    Java面试(一)
    MFC学习(七) 单文档程序
    MFC学习(六)计算器
    C++ 面试题(转载)
    MFC学习(五)常见面试题
    MFC学习(四) 消息机制
    MFC学习(三)程序入口和执行流程
  • 原文地址:https://www.cnblogs.com/qlqwjy/p/16254810.html
Copyright © 2020-2023  润新知