zoukankan      html  css  js  c++  java
  • springmvc对同名参数处理-我们到底能走多远系列(44)

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

    让我们继续前行

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

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

  • 相关阅读:
    Objective-C 调用C++,C
    ios项目不能再用UDID了
    xcode 4 制作静态库详解
    Icon specified in the Info.plist not found under the top level app wrapper: Icon.png
    吼吼 尬English
    Redis
    处理android 经典蓝牙发送文件时接收包的问题
    Md5加密的文件流中是否会包含其md5值
    Android gradle buid failed case
    Android GDB 调试
  • 原文地址:https://www.cnblogs.com/killbug/p/4936894.html
Copyright © 2011-2022 走看看