Spring WebMVC List容器元素的Data Bind
最近学习SpringMVC,做了一个小Demo, 发现一个问题:无法绑定command对象List Field中的元素的属性。
?
Command Object 类似如下:
public class CommandObject {private List<ListElement> mylist = new ArrayList<ListElement>();// getter and setter}
这里list属性必须先行实例化,否则会错误。这个与Hibernate要求PO的一对多Set必须实例化一样。?
?
页面大致如下:
?
<input type="text" name="command.xxxlist[0].name" value="" /><input type="text" name="command.xxxlist[1].name" value="" /><input type="text" name="command.xxxlist[2].name" value="" />
?
运行后会报错, 由于绑定的时候list为empty, xxxlist.get(index)自然会数组越界,name要set都不知set到哪里去了。Spring也不会自行实例化,这让我感到相当恼火。
?
研读Source Code,寻求解决方法,不禁释然:当前版本要兼容1.4,Spring要如何知道实例化哪个Class,在没有使用泛型的情况下。
?
追踪代码,核心在org.springframework.beans.BeanWrapperImpl.java中。
?
private Object getPropertyValue(PropertyTokenHolder tokens) throws BeansException {String propertyName = tokens.canonicalName;String actualName = tokens.actualName;PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(actualName);if (pd == null || pd.getReadMethod() == null) {throw new NotReadablePropertyException(getRootClass(), this.nestedPath + propertyName);}Method readMethod = pd.getReadMethod();try {if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {readMethod.setAccessible(true);}Object value = readMethod.invoke(this.object, (Object[]) null);if (tokens.keys != null) {// apply indexes and map keysfor (int i = 0; i < tokens.keys.length; i++) {String key = tokens.keys[i];if (value == null) {throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + propertyName,"Cannot access indexed value of property referenced in indexed " +"property path '" + propertyName + "': returned null");}else if (value.getClass().isArray()) {value = Array.get(value, Integer.parseInt(key));}else if (value instanceof List) {List list = (List) value;value = list.get(Integer.parseInt(key));}else if (value instanceof Set) {// Apply index to Iterator in case of a Set.Set set = (Set) value;int index = Integer.parseInt(key);if (index < 0 || index >= set.size()) {throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,"Cannot get element with index " + index + " from Set of size " +set.size() + ", accessed using property path '" + propertyName + "'");}Iterator it = set.iterator();for (int j = 0; it.hasNext(); j++) {Object elem = it.next();if (j == index) {value = elem;break;}}}else if (value instanceof Map) {Map map = (Map) value;Class mapKeyType = null;if (JdkVersion.isAtLeastJava15()) {mapKeyType = GenericCollectionTypeResolver.getMapKeyReturnType(pd.getReadMethod(), i + 1);}// IMPORTANT: Do not pass full property name in here - property editors// must not kick in for map keys but rather only for map values.Object convertedMapKey = this.typeConverterDelegate.convertIfNecessary(key, mapKeyType);// Pass full property name and old value in here, since we want full// conversion ability for map values.value = map.get(convertedMapKey);}else {throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,"Property referenced in indexed property path '" + propertyName +"' is neither an array nor a List nor a Set nor a Map; returned value was [" + value + "]");}}}return value;}catch (InvocationTargetException ex) {throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,"Getter for property '" + actualName + "' threw exception", ex);}catch (IllegalAccessException ex) {throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,"Illegal attempt to get property '" + actualName + "' threw exception", ex);}catch (IndexOutOfBoundsException ex) {throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,"Index of out of bounds in property path '" + propertyName + "'", ex);}catch (NumberFormatException ex) {throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,"Invalid index in property path '" + propertyName + "'", ex);}}
?于是决定改Source了,具体如下。顺便还发现Map也有同样问题,便一道改了。
?
private Object getPropertyValue(PropertyTokenHolder tokens) throws BeansException {String propertyName = tokens.canonicalName;String actualName = tokens.actualName;PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(actualName);if (pd == null || pd.getReadMethod() == null) {throw new NotReadablePropertyException(getRootClass(), this.nestedPath + propertyName);}Method readMethod = pd.getReadMethod();try {if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {readMethod.setAccessible(true);}Object value = readMethod.invoke(this.object, (Object[]) null);if (tokens.keys != null) {// apply indexes and map keysfor (int i = 0; i < tokens.keys.length; i++) {String key = tokens.keys[i];if (value == null) {throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + propertyName,"Cannot access indexed value of property referenced in indexed " +"property path '" + propertyName + "': returned null");}else if (value.getClass().isArray()) {value = Array.get(value, Integer.parseInt(key));}else if (value instanceof List) {List list = (List) value;int positionOfList = Integer.parseInt(key);Class listElementClass = GenericCollectionTypeResolver.getCollectionReturnType(readMethod);if(list.size() <= positionOfList && JdkVersion.isAtLeastJava15()&& listElementClass != null&& !(ClassUtils.isPrimitiveOrWrapper(listElementClass) || listElementClass.equals(String.class))) {if(positionOfList > Byte.MAX_VALUE) {throw new IllegalAccessException("Invalid property '" + nestedPath + propertyName + "' of ["+ getRootClass() + "] : "+ positionOfList+ " is too large to support (max value is " + Byte.MAX_VALUE+")");}for(int j = list.size(); j < positionOfList; j++) {Object listElement = BeanUtils.instantiateClass(listElementClass);list.add(listElement);}value = BeanUtils.instantiateClass(listElementClass);list.add(value);} else {value = list.get(positionOfList);}}else if (value instanceof Set) {// Apply index to Iterator in case of a Set.Set set = (Set) value;int index = Integer.parseInt(key);if (index < 0 || index >= set.size()) {throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,"Cannot get element with index " + index + " from Set of size " +set.size() + ", accessed using property path '" + propertyName + "'");}Iterator it = set.iterator();for (int j = 0; it.hasNext(); j++) {Object elem = it.next();if (j == index) {value = elem;break;}}}else if (value instanceof Map) {Map map = (Map) value;Class mapKeyType = null;Class mapValueType = null;if (JdkVersion.isAtLeastJava15()) {mapKeyType = GenericCollectionTypeResolver.getMapKeyReturnType(readMethod, i + 1);mapValueType = GenericCollectionTypeResolver.getMapValueReturnType(readMethod, i + 1);}// IMPORTANT: Do not pass full property name in here - property editors// must not kick in for map keys but rather only for map values.Object convertedMapKey = this.typeConverterDelegate.convertIfNecessary(key, mapKeyType);// Pass full property name and old value in here, since we want full// conversion ability for map values.value = map.get(convertedMapKey);if(value == null && mapValueType != null && !(ClassUtils.isPrimitiveOrWrapper(mapValueType) || mapValueType.equals(String.class))) {value = BeanUtils.instantiateClass(mapValueType);map.put(convertedMapKey, value);}}else {throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,"Property referenced in indexed property path '" + propertyName +"' is neither an array nor a List nor a Set nor a Map; returned value was [" + value + "]");}}}return value;}catch (InvocationTargetException ex) {throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,"Getter for property '" + actualName + "' threw exception", ex);}catch (IllegalAccessException ex) {throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,"Illegal attempt to get property '" + actualName + "' threw exception", ex);}catch (IndexOutOfBoundsException ex) {throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,"Index of out of bounds in property path '" + propertyName + "'", ex);}catch (NumberFormatException ex) {throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,"Invalid index in property path '" + propertyName + "'", ex);}}
?虽然代码有点难看,毕竟解决了问题。最后顺便提下,Spring内部的工具类相当给力,大大减轻了了修改工作量。^_^