最近学习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 keys
for (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 keys for (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内部的工具类相当给力,大大减轻了了修改工作量。^_^