Spring-重复请求参数分析

实现结果

  • 当出现重复参数名时,所有参数值均会保存在 map 的 相同 key(即参数名)中。
  • 若参数格式为 String,则所有参数值用逗号连接,一次性输出。
  • 若参数格式为 Integer,则只输出第一个参数值。

实现分析

参数均用 @RequestParam 注解修饰,则应当是与之相关的参数绑定环节中实现了这样特殊的参数处理。

参数存储

  1. 使用 RequestParamMethodArgumentResolver 解析 @RequestParam 注释的方法参数,将相同参数名下的所有参数值均存储在一个 String 数组中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver
    implements UriComponentsContributor {

    protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
    ...
    Object arg = null;
    if (arg == null) {
    String[] paramValues = request.getParameterValues(name);
    if (paramValues != null) {
    arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
    }
    }
    ...
    }
    }
  2. 通过 WebDataBinderconvertIfNecessary 方法,实现参数转换处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver
implements UriComponentsContributor {

public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
...
Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
...
WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
try {
arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
}
...
}
}

转换处理

  1. 通过 WebDataBinderDataBinderTypeConverterSupport 层层转包至 TypeConverterDelegateconvertIfNecessary 方法处理。
  2. 通过判断所需格式与当前格式,进行参数转换处理。

String[] To String

  1. 确认所需类型为 String,有合适的 PropertyEditor,但转换值相对于所需类型不可赋值,且当前值的类型不为 String。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public <T> T convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue,
    @Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws IllegalArgumentException {

    PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);
    Object convertedValue = newValue;

    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);
    }
    ...
    }
  2. 确认当前转换值的类型为 String[],但所需类型不为数组。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    private Object doConvertValue(@Nullable Object oldValue, @Nullable Object newValue,
    @Nullable Class<?> requiredType, @Nullable PropertyEditor editor) {
    ...
    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);
    }
    ...
    }
  3. 默认使用逗号作为分隔符,将所有数组的所有元素拼接为一个字符串。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public abstract class StringUtils {

    public static String arrayToCommaDelimitedString(@Nullable Object[] arr) {
    return arrayToDelimitedString(arr, ",");
    }

    public static String arrayToDelimitedString(@Nullable Object[] arr, String delim) {
    if (ObjectUtils.isEmpty(arr)) {
    return "";
    }
    if (arr.length == 1) {
    return ObjectUtils.nullSafeToString(arr[0]);
    }

    StringJoiner sj = new StringJoiner(delim);
    for (Object elem : arr) {
    sj.add(String.valueOf(elem));
    }
    return sj.toString();
    }
    }

String[] To Integer

  1. 确认当前转换值的类型为 Integer,没有合适的 PropertyEditor,使用 ConversionService 实现。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public <T> T convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue,
    @Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws IllegalArgumentException {

    PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);

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

  2. 根据源数据类型与目标数据类型,选择合适的 GenericConverter,通过 ConversionUtilsinvokeConverter 方法实现。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    public class GenericConversionService implements ConfigurableConversionService {

    public Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType) {
    ...
    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);

    }
    }

    abstract class ConversionUtils {

    public static Object invokeConverter(GenericConverter converter, @Nullable 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);
    }
    }
    }
  3. 选择数组的第一个元素,作为参数值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    final class ArrayToObjectConverter implements ConditionalGenericConverter {

    public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
    if (source == null) {
    return null;
    }
    if (sourceType.isAssignableTo(targetType)) {
    return source;
    }
    if (Array.getLength(source) == 0) {
    return null;
    }
    Object firstElement = Array.get(source, 0);
    return this.conversionService.convert(firstElement, sourceType.elementTypeDescriptor(firstElement), targetType);
    }
    }

参考资料