你的位置:首页 > Java教程

[Java教程]springmvc对同名参数处理


springmvc对同名参数处理

扯淡:

中断发博客几个月,其实蛮不爽的,可能最近太忙太劳累了些,很多总结也没时间写,今天刚好遇到个小问题,就阅读下源码找找乐。因为考虑到网上大多是提供解决问题的方案,没有实际去看spring源码流程,所以就发个博文记录下,万一以后有同学搜到我的文章能深入看些东西吧。

 

问题描述:

前端有多个相同name的input:

<input type="text" min="1" max="31" name="xName" value="1" /><input type="text" min="1" max="31" name="xName" value="2" /><input type="text" min="1" max="31" name="xName" value="3" />

 
提交时流程原理:
一般容器在HTTP协议的Request传递到ServletContainer中后,Container就会为该次请求生成一个HTTPServletRequest对象,在HTTPServletRequest对象中,参数和值,放入到了Map中。
tomcat源码中放入map时的代码:
  /**   * Put name and value pair in map. When name already exist, add value   * to array of values.   *   * @param map The map to populate   * @param name The parameter name   * @param value The parameter value   */  private static void putMapEntry( Map map, String name, String value) {    String[] newValues = null;    String[] oldValues = (String[]) map.get(name);    if (oldValues == null) {      newValues = new String[1];      newValues[0] = value;    } else {      newValues = new String[oldValues.length + 1];      System.arraycopy(oldValues, 0, newValues, 0, oldValues.length);      newValues[oldValues.length] = value;    }    map.put(name, newValues);  }

 

可见同名的name,都会被放入数组的value中,而在servlet中,获取value的两个方法:

request.getParameter request.getParameterValues

第一个获取的是数组的第一个值,第二个则获得整个数组。

springmvc框架在遇到这种同名参数提交,而参数中有逗号时,会出现问题:
比如我提交的值是 xname=123 和 xname=45,67
那么在进入action拿到参数时会变成string[] xname = ["123","45","67"]
就会影响业务逻辑正确执行。
 

解决方案:

就是在提交后台前,将参数进行转码,后台再进行解码就可以了。至于采用哪一种转码解码方式,其实没有什么要求,只需要注意采用方案会将逗号转码即可,知道这个关键点就好了。
 
实践方案:
前端代码:
$("input[name='ssidName']").each(function(){            $(this).val(encodeURIComponent($(this).val()));          })

 


后端java代码:

URLDecoder.decode(ssidNames [i], "UTF-8" )

 

灵活点的想法是:采用可逆转的转码解码也可,比如base64。有兴趣的可以尝试。
 

源码阅读:

 
虽然知道spring有这个自动组装的功能,那么我就找到这段代码:

关于springmvc原理分析这文章不错:http://www.cnblogs.com/heavenyes/p/3905844.html

 

我就在它后面再更进一步分析同名参数是如何处理的。

完成request中的参数和方法参数上数据的绑定:

private Object[] resolveHandlerArguments(Method handlerMethod, Object handler,      NativeWebRequest webRequest, ExtendedModelMap implicitModel) throws Exception {   // 1.获取方法参数类型的数组    Class[] paramTypes = handlerMethod.getParameterTypes();  // 声明数组,存参数的值    Object[] args = new Object[paramTypes.length];  //2.遍历参数数组,获取每个参数的值    for (int i = 0; i < args.length; i++) {      MethodParameter methodParam = new MethodParameter(handlerMethod, i);      methodParam.initParameterNameDiscovery(this.parameterNameDiscoverer);      GenericTypeResolver.resolveParameterType(methodParam, handler.getClass());      String paramName = null;      String headerName = null;      boolean requestBodyFound = false;      String cookieName = null;      String pathVarName = null;      String attrName = null;      boolean required = false;      String defaultValue = null;      boolean validate = false;      int annotationsFound = 0;      Annotation[] paramAnns = methodParam.getParameterAnnotations();    // 处理参数上的注解      for (Annotation paramAnn : paramAnns) {        if (RequestParam.class.isInstance(paramAnn)) {          RequestParam requestParam = (RequestParam) paramAnn;          paramName = requestParam.value();          required = requestParam.required();          defaultValue = parseDefaultValueAttribute(requestParam.defaultValue());          annotationsFound++;        }        else if (RequestHeader.class.isInstance(paramAnn)) {          RequestHeader requestHeader = (RequestHeader) paramAnn;          headerName = requestHeader.value();          required = requestHeader.required();          defaultValue = parseDefaultValueAttribute(requestHeader.defaultValue());          annotationsFound++;        }        else if (RequestBody.class.isInstance(paramAnn)) {          requestBodyFound = true;          annotationsFound++;        }        else if (CookieValue.class.isInstance(paramAnn)) {          CookieValue cookieValue = (CookieValue) paramAnn;          cookieName = cookieValue.value();          required = cookieValue.required();          defaultValue = parseDefaultValueAttribute(cookieValue.defaultValue());          annotationsFound++;        }        else if (PathVariable.class.isInstance(paramAnn)) {          PathVariable pathVar = (PathVariable) paramAnn;          pathVarName = pathVar.value();          annotationsFound++;        }        else if (ModelAttribute.class.isInstance(paramAnn)) {          ModelAttribute attr = (ModelAttribute) paramAnn;          attrName = attr.value();          annotationsFound++;        }        else if (Value.class.isInstance(paramAnn)) {          defaultValue = ((Value) paramAnn).value();        }        else if ("Valid".equals(paramAnn.annotationType().getSimpleName())) {          validate = true;        }      }       if (annotationsFound > 1) {        throw new IllegalStateException("Handler parameter annotations are exclusive choices - " +            "do not specify more than one such annotation on the same parameter: " + handlerMethod);      }      if (annotationsFound == 0) {// 如果没有注解        Object argValue = resolveCommonArgument(methodParam, webRequest);        if (argValue != WebArgumentResolver.UNRESOLVED) {          args[i] = argValue;        }        else if (defaultValue != null) {          args[i] = resolveDefaultValue(defaultValue);        }        else {          Class paramType = methodParam.getParameterType();      // 将方法声明中的Map和Model参数,放到request中,用于将数据放到request中带回页面          if (Model.class.isAssignableFrom(paramType) || Map.class.isAssignableFrom(paramType)) {            args[i] = implicitModel;          }          else if (SessionStatus.class.isAssignableFrom(paramType)) {            args[i] = this.sessionStatus;          }          else if (HttpEntity.class.isAssignableFrom(paramType)) {            args[i] = resolveHttpEntityRequest(methodParam, webRequest);          }          else if (Errors.class.isAssignableFrom(paramType)) {            throw new IllegalStateException("Errors/BindingResult argument declared " +                "without preceding model attribute. Check your handler method signature!");          }          else if (BeanUtils.isSimpleProperty(paramType)) {            paramName = "";          }          else {            attrName = "";          }        }      }    // 从request中取值,并进行赋值操作      if (paramName != null) {     // 根据paramName从request中取值,如果没有通过RequestParam注解指定paramName,则使用asm读取class文件来获取paramName        args[i] = resolveRequestParam(paramName, required, defaultValue, methodParam, webRequest, handler);      }      else if (headerName != null) {        args[i] = resolveRequestHeader(headerName, required, defaultValue, methodParam, webRequest, handler);      }      else if (requestBodyFound) {        args[i] = resolveRequestBody(methodParam, webRequest, handler);      }      else if (cookieName != null) {        args[i] = resolveCookieValue(cookieName, required, defaultValue, methodParam, webRequest, handler);      }      else if (pathVarName != null) {        args[i] = resolvePathVariable(pathVarName, methodParam, webRequest, handler);      }      else if (attrName != null) {        WebDataBinder binder =            resolveModelAttribute(attrName, methodParam, implicitModel, webRequest, handler);        boolean assignBindingResult = (args.length > i + 1 && Errors.class.isAssignableFrom(paramTypes[i + 1]));        if (binder.getTarget() != null) {          doBind(binder, webRequest, validate, !assignBindingResult);        }        args[i] = binder.getTarget();        if (assignBindingResult) {          args[i + 1] = binder.getBindingResult();          i++;        }        implicitModel.putAll(binder.getBindingResult().getModel());      }    }   // 返回参数值数组    return args;  }

 

resolveRequestParam方法将参数取出:

 

private Object resolveRequestParam (String paramName, boolean required, String defaultValue,        MethodParameter methodParam, NativeWebRequest webRequest, Object handlerForInitBinderCall)         throws Exception {      Class<?> paramType = methodParam.getParameterType();      if (Map. class.isAssignableFrom(paramType) && paramName.length() == 0) {         return resolveRequestParamMap((Class<? extends Map>) paramType, webRequest);      }      if (paramName.length() == 0) {        paramName = getRequiredParameterName(methodParam);      }      Object paramValue = null;      MultipartRequest multipartRequest = webRequest.getNativeRequest(MultipartRequest.class );      if (multipartRequest != null) {        List<MultipartFile> files = multipartRequest.getFiles(paramName);         if (!files.isEmpty()) {           paramValue = (files.size() == 1 ? files.get(0) : files);        }      }      if (paramValue == null) {        //数组参数执行这行代码,相当于执行了request.getParameterValues(name)        String[] paramValues = webRequest.getParameterValues(paramName);         if (paramValues != null) {          // 取出之后,如果是同名参数则赋值整个数组 paramValue此时是object           paramValue = (paramValues. length == 1 ? paramValues[0] : paramValues);        }      }      if (paramValue == null) {         if (defaultValue != null) {           paramValue = resolveDefaultValue(defaultValue);        }         else if (required) {           raiseMissingParameterException(paramName, paramType);        }        paramValue = checkValue(paramName, paramValue, paramType);      }      WebDataBinder binder = createBinder(webRequest, null, paramName);      initBinder(handlerForInitBinderCall, paramName, binder, webRequest);       // 将paramValue根据参数类型进行一次转换操作      return binder.convertIfNecessary(paramValue, paramType, methodParam);   }

 

最终会执行到TypeConverterDelegate的convertIfNecessary方法:

 

public <T> T convertIfNecessary(String propertyName, Object oldValue, Object newValue,        Class<T> requiredType, TypeDescriptor typeDescriptor) throws IllegalArgumentException {      Object convertedValue = newValue;      // Custom editor for this type?      PropertyEditor editor = this. propertyEditorRegistry.findCustomEditor(requiredType, propertyName);      ConversionFailedException firstAttemptEx = null;      // No custom editor but custom ConversionService specified?      ConversionService conversionService = this. propertyEditorRegistry.getConversionService();      if (editor == null && conversionService != null && convertedValue != null && typeDescriptor != null) {        TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);        TypeDescriptor targetTypeDesc = typeDescriptor;         if (conversionService.canConvert(sourceTypeDesc, targetTypeDesc)) {           try {              return (T) conversionService.convert(convertedValue, sourceTypeDesc, targetTypeDesc);           }           catch (ConversionFailedException ex) {              // fallback to default conversion logic below              firstAttemptEx = ex;           }        }      }      // Value not of required type?      if (editor != null || (requiredType != null && !ClassUtils.isAssignableValue(requiredType, convertedValue))) {         if (requiredType != null && Collection.class.isAssignableFrom(requiredType) && convertedValue instanceof String) {           TypeDescriptor elementType = typeDescriptor.getElementTypeDescriptor();           if (elementType != null && Enum.class.isAssignableFrom(elementType.getType())) {              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) {           // 因为我们的type是数组,所以执行这段逻辑,他会执行StringUtils.commaDelimitedListToStringArray           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.equals(requiredType) && ClassUtils.isPrimitiveOrWrapper(convertedValue.getClass())) {              // We can stringify any primitive value...              return (T) convertedValue.toString();           }           else if (convertedValue instanceof String && !requiredType.isInstance(convertedValue)) {              if (firstAttemptEx == null && !requiredType.isInterface() && !requiredType.isEnum()) {                 try {                   Constructor strCtor = requiredType.getConstructor(String.class );                   return (T) 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;           }        }         if (!ClassUtils. isAssignableValue(requiredType, convertedValue)) {           if (firstAttemptEx != null) {              throw firstAttemptEx;           }           // 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 (firstAttemptEx != null) {         if (editor == null && !standardConversion && requiredType != null && !Object.class.equals(requiredType)) {           throw firstAttemptEx;        }         logger.debug( "Original ConversionService attempt failed - ignored since " +              "PropertyEditor based conversion eventually succeeded", firstAttemptEx);      }      return (T) convertedValue;   }

 

StringUtils.commaDelimitedListToStringArray代码:

 

public static String[] commaDelimitedListToStringArray (String str) {      return delimitedListToStringArray(str, ",");   }

 

最终执行代码,也就是哪里把这个逗号的参数区分成两个参数的地方,众里寻他千百度啊:

public static String[] delimitedListToStringArray (String str, String delimiter, String charsToDelete) {      if (str == null) {         return new String[0];      }      if (delimiter == null) {         return new String[] {str};      }      List<String> result = new ArrayList<String>();      if ( "".equals(delimiter)) {         for ( int i = 0; i < str.length(); i++) {           result.add( deleteAny(str.substring(i, i + 1), charsToDelete));        }      }      else {         int pos = 0;         int delPos;        // 逗号分隔,组装新的list         while ((delPos = str.indexOf(delimiter, pos)) != -1) {           result.add( deleteAny(str.substring(pos, delPos), charsToDelete));           pos = delPos + delimiter.length();        }         if (str.length() > 0 && pos <= str.length()) {           // Add rest of String, but not in case of empty input.           result.add( deleteAny(str.substring(pos), charsToDelete));        }      }      return toStringArray(result);   }

 

 

让我们继续前行

----------------------------------------------------------------------

努力不一定成功,但不努力肯定不会成功。