最近学习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内部的工具类相当给力,大大减轻了了修改工作量。^_^
分享到:
相关推荐
14. <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" /> 15. 16. <!-- 对模型视图名称的解析,即在模型视图名称添加前后缀 --> 17. <bean class="org....
<artifactId>spring-webmvc <version>5.3.23 <groupId>org.springframework <artifactId>spring-jdbc <version>5.3.23 <!-- 其他可能需要的依赖,如:JSTL,连接池等 --> ``` 接着,我们需要配置...
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; @Controller ...
import org.springframework.web.bind.annotation.GetMapping; @Controller public class HomeController { @GetMapping("/") public String home(Model model) { model.addAttribute("message", "Hello, ...
Spring MVC 是 Spring Framework 的一个重要模块,它实现了 MVC(模型-视图-控制器)设计模式,帮助开发者构建易于维护和扩展的 Web 应用程序。在 Spring MVC 中,控制器负责接收用户请求,并决定调用哪个模型来处理...
- 添加SpringDataJPA、SpringWeb和JPA的依赖项到Maven或Gradle构建文件中。 - 配置数据源和JPA设置,如实体管理工厂、事务管理器等。 - 创建`persistence.xml`文件,指定数据源、实体类包及持久化提供商。 ### 3. ...
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import java.util...
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.servlet.ModelAndView; @Controller public ...
<artifactId>spring-webmvc</artifactId> ${springmvc.version}</version> </dependency> <!-- Spring Data JPA --> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-jpa...
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller ...
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.multipart.MultipartFile; @PostMapping("/upload...
在Spring MVC的配置文件(如`web.xml`或Spring Boot的配置类)中,我们需要添加一个MultipartResolver来处理文件上传。通常我们会选择`CommonsMultipartResolver`,这是基于Apache Commons FileUpload库的。配置如下...
import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequestMapping("/api/users") public class UserController { @Autowired private UserService userService...
import org.springframework.web.bind.annotation.RestController; import java.util.List; @RestController public class MyController { @GetMapping("/getDropdownData") public List<MyObject> ...
public List<MyPojo> handleListRequest(@RequestBody List<MyPojo> listData) { // 处理List请求 } } ``` 在上面的例子中,`@RestController`注解表示这是一个RESTful风格的控制器,而`@RequestMapping("/api")...
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.multipart.MultipartFile; import org.spring...
<artifactId>spring-webmvc <version>5.3.23 <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind <version>2.13.4.2 ``` 接下来,创建一个控制器(Controller)类,使用`@...
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; @RestController @RequestMapping("/server") public...
<artifactId>spring-webmvc <version>5.3.23 <groupId>mysql <artifactId>mysql-connector-java <version>8.0.30 <!-- 其他如Spring Data JPA, Hibernate等持久层依赖 --> ``` 接下来,我们创建 ...
在IT行业中,SpringMVC是Java企业级应用开发中广泛使用的Web MVC框架,它提供了模型-视图-控制器(MVC)模式的实现,使得开发者能够更高效地构建可维护和可扩展的Web应用程序。本篇文章将深入探讨如何利用SpringMVC...