sPRingmvc对同名参数处理扯淡:
中断发博客几个月,其实蛮不爽的,可能最近太忙太劳累了些,很多总结也没时间写,今天刚好遇到个小问题,就阅读下源码找找乐。因为考虑到网上大多是提供解决问题的方案,没有实际去看spring源码流程,所以就发个博文记录下,万一以后有同学搜到我的文章能深入看些东西吧。
问题描述:前端有多个相同name的input:
<inputtype="text"min="1"max="31"name="xName"value="1"/><inputtype="text"min="1"max="31"name="xName"value="2"/><inputtype="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.
*
*@parammap The map to populate
*@paramname The parameter name
*@paramvalue The parameter value*/privatestaticvoidputMapEntry( Map map, String name, String value) {
String[] newValues=null;
String[] oldValues=(String[]) map.get(name);if(oldValues ==null) {
newValues=newString[1];
newValues[0] =value;
}else{
newValues=newString[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中的参数和方法参数上数据的绑定:
privateObject[] resolveHandlerArguments(Method handlerMethod, Object handler,
NativeWebRequest webRequest, ExtendedModelMap implicitModel)throwsException {//1.获取方法参数类型的数组Class[] paramTypes =handlerMethod.getParameterTypes();//声明数组,存参数的值Object[] args =newObject[paramTypes.length];//2.遍历参数数组,获取每个参数的值for(inti = 0; i < args.length; i++) {
MethodParameter methodParam=newMethodParameter(handlerMethod, i);
methodParam.initParameterNameDiscovery(this.parameterNameDiscoverer);
GenericTypeResolver.resolveParameterType(methodParam, handler.getClass());
String paramName=null;
String headerName=null;booleanrequestBodyFound =false;
String cookieName=null;
String pathVarName=null;
String attrName=null;booleanrequired =false;
String defaultValue=null;booleanvalidate =false;intannotationsFound = 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++;
}elseif(RequestHeader.class.isInstance(paramAnn)) {
RequestHeader requestHeader=(RequestHeader) paramAnn;
headerName=requestHeader.value();
required=requestHeader.required();
defaultValue=parseDefaultValueAttribute(requestHeader.defaultValue());
annotationsFound++;
}elseif(RequestBody.class.isInstance(paramAnn)) {
requestBodyFound=true;
annotationsFound++;
}elseif(CookieValue.class.isInstance(paramAnn)) {
CookieValue cookieValue=(CookieValue) paramAnn;
cookieName=cookieValue.value();
required=cookieValue.required();
defaultValue=parseDefaultValueAttribute(cookieValue.defaultValue());
annotationsFound++;
}elseif(PathVariable.class.isInstance(paramAnn)) {
PathVariable pathVar=(PathVariable) paramAnn;
pathVarName=pathVar.value();
annotationsFound++;
}elseif(ModelAttribute.class.isInstance(paramAnn)) {
ModelAttribute attr=(ModelAttribute) paramAnn;
attrName=attr.value();
annotationsFound++;
}elseif(Value.class.isInstance(paramAnn)) {
defaultValue=((Value) paramAnn).value();
}elseif("Valid".equals(paramAnn.annotationType().getSimpleName())) {
validate=true;
}
}if(annotationsFound > 1) {thrownewIllegalStateException("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;
}elseif(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;
}elseif(sessionStatus.class.isAssignableFrom(paramType)) {
args[i]=this.sessionStatus;
}elseif(HttpEntity.class.isAssignableFrom(paramType)) {
args[i]=resolveHttpEntityRequest(methodParam, webRequest);
}elseif(Errors.class.isAssignableFrom(paramType)) {thrownewIllegalStateException("Errors/BindingResult argument declared " +
"without preceding model attribute. Check your handler method signature!");
}elseif(BeanUtils.isSimpleProperty(paramType)) {
paramName= "";
}else{
attrName= "";
}
}
}//从request中取值,并进行赋值操作if(paramName !=null) {//根据paramName从request中取值,如果没有通过RequestParam注解指定paramName,则使用asm读取class文件来获取paramNameargs[i] =resolveRequestParam(paramName, required, defaultValue, methodParam, webRequest, handler);
}elseif(headerName !=null) {
args[i]=resolveRequestHeader(headerName, required, defaultValue, methodParam, webRequest, handler);
}elseif(requestBodyFound) {
args[i]=resolveRequestBody(methodParam, webRequest, handler);
}elseif(cookieName !=null) {
args[i]=resolveCookieValue(cookieName, required, defaultValue, methodParam, webRequest, handler);
}elseif(pathVarName !=null) {
args[i]=resolvePathVariable(pathVarName, methodParam, webRequest, handler);
}elseif(attrName !=null) {
WebDataBinder binder=resolveModelAttribute(attrName, methodParam, implicitModel, webRequest, handler);booleanassignBindingResult = (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());
}
}//返回参数值数组returnargs;
}
resolveRequestParam方法将参数取出:
privateObject resolveRequestParam (String paramName,booleanrequired, String defaultValue,
MethodParameter methodParam, NativeWebRequest webRequest, Object handlerForInitBinderCall)throwsException {
Class<?> paramType =methodParam.getParameterType();if(Map.class.isAssignableFrom(paramType) && paramName.length() == 0) {returnresolveRequestParamMap((Class<?extendsMap>) 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此时是objectparamValue = (paramValues. length == 1 ? paramValues[0] : paramValues);
}
}if(paramValue ==null) {if(defaultValue !=null) {
paramValue=resolveDefaultValue(defaultValue);
}elseif(required) {
raiseMissingParameterException(paramName, paramType);
}
paramValue=checkValue(paramName, paramValue, paramType);
}
WebDataBinder binder= createBinder(webRequest,null, paramName);
initBinder(handlerForInitBinderCall, paramName, binder, webRequest);//将paramValue根据参数类型进行一次转换操作returnbinder.convertIfNecessary(paramValue, paramType, methodParam);
}
最终会执行到TypeConverterDelegate的convertIfNecessary方法:
public<T>T convertIfNecessary(String propertyName, Object oldValue, Object newValue,
Class<T> requiredType, TypeDescriptor typeDescriptor)throwsIllegalArgumentException {
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 belowfirstAttemptEx =ex;
}
}
}//Value not of required type?if(editor !=null|| (requiredType !=null&& !ClassUtils.isAssignableValue(requiredType, convertedValue))) {if(requiredType !=null&& Collection.class.isAssignableFrom(requiredType) && convertedValueinstanceofString) {
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);
}booleanstandardConversion =false;if(requiredType !=null) {//Try to apply some standard type conversion rules if appropriate.if(convertedValue !=null) {//因为我们的type是数组,所以执行这段逻辑,他会执行StringUtils.commaDelimitedListToStringArrayif(requiredType.isArray()) {//Array required -> apply appropriate conversion of elements.if(convertedValueinstanceofString && Enum.class.isAssignableFrom(requiredType.getComponentType())) {
convertedValue=StringUtils.commaDelimitedListToStringArray((String) convertedValue);
}return(T) convertToTypedArray(convertedValue, propertyName, requiredType.getComponentType());
}elseif(convertedValueinstanceofCollection) {//Convert elements to target type, if determined.convertedValue =convertToTypedCollection(
(Collection) convertedValue, propertyName, requiredType, typeDescriptor);
standardConversion=true;
}elseif(convertedValueinstanceofMap) {//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();
}elseif(convertedValueinstanceofString && !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 lookupif(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.returnnull;
}
convertedValue=attemptToConvertStringToEnum(requiredType, trimmedValue, convertedValue);
standardConversion=true;
}
}if(!ClassUtils. isAssignableValue(requiredType, convertedValue)) {if(firstAttemptEx !=null) {throwfirstAttemptEx;
}//Definitely doesn't match: throw IllegalArgumentException/IllegalStateExceptionStringBuilder msg =newStringBuilder();
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("]");thrownewIllegalArgumentException(msg.toString());
}else{
msg.append(": no matching editors or conversion strategy found");thrownewIllegalStateException(msg.toString());
}
}
}if(firstAttemptEx !=null) {if(editor ==null&& !standardConversion && requiredType !=null&& !Object.class.equals(requiredType)) {throwfirstAttemptEx;
}
logger.debug("Original ConversionService attempt failed - ignored since " +
"PropertyEditor based conversion eventually succeeded", firstAttemptEx);
}return(T) convertedValue;
}
StringUtils.commaDelimitedListToStringArray代码:
publicstaticString[] commaDelimitedListToStringArray (String str) {returndelimitedListToStringArray(str, ",");
}
最终执行代码,也就是哪里把这个逗号的参数区分成两个参数的地方,众里寻他千百度啊:
publicstaticString[] delimitedListToStringArray (String str, String delimiter, String charsToDelete) {if(str ==null) {returnnewString[0];
}if(delimiter ==null) {returnnewString[] {str};
}
List<String> result =newArrayList<String>();if( "".equals(delimiter)) {for(inti = 0; i < str.length(); i++) {
result.add( deleteAny(str.substring(i, i+ 1), charsToDelete));
}
}else{intpos = 0;intdelPos;//逗号分隔,组装新的listwhile((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));
}
}returntoStringArray(result);
}
让我们继续前行
----------------------------------------------------------------------
努力不一定成功,但不努力肯定不会成功。