当前位置: 代码迷 >> Web前端 >> Spring WebMVC List器皿元素的Data Bind
  详细解决方案

Spring WebMVC List器皿元素的Data Bind

热度:807   发布时间:2012-10-08 19:54:56.0
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 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内部的工具类相当给力,大大减轻了了修改工作量。^_^

  相关解决方案