前台传递的参数为集合对象时,后台Controller希望用一个List集合接收数据。
原生SpringMVC是不支持,Controller参数定义为List类型时,接收参数会报如下错误:
org.springframework.beans.BeanInstantiationException: Failed to instantiate [java.util.List]: Specified class is an interface
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:99) ~[spring-beans-4.3.16.RELEASE.jar:4.3.16.RELEASE]
at org.springframework.web.method.annotation.ModelAttributeMethodProcessor.createAttribute(ModelAttributeMethodProcessor.java:139) ~[spring-web-4.3.16.RELEASE.jar:4.3.16.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor.createAttribute(ServletModelAttributeMethodProcessor.java:82) ~[spring-webmvc-4.3.16.RELEASE.jar:4.3.16.RELEASE]
at org.springframework.web.method.annotation.ModelAttributeMethodProcessor.resolveArgument(ModelAttributeMethodProcessor.java:106) ~[spring-web-4.3.16.RELEASE.jar:4.3.16.RELEASE]
at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121) ~[spring-web-4.3.16.RELEASE.jar:4.3.16.RELEASE]
at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:158) ~[spring-web-4.3.16.RELEASE.jar:4.3.16.RELEASE]
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:128) ~[spring-web-4.3.16.RELEASE.jar:4.3.16.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:97) ~[spring-webmvc-4.3.16.RELEASE.jar:4.3.16.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827) ~[spring-webmvc-4.3.16.RELEASE.jar:4.3.16.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738) ~[spring-webmvc-4.3.16.RELEASE.jar:4.3.16.RELEASE]
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85) ~[spring-webmvc-4.3.16.RELEASE.jar:4.3.16.RELEASE]
查看了一下源码,发现问题在于ModelAttributeMethodProcessor解析参数时,会先使用BeanUtils.instantiateClass方法创建一个对象实例来接收参数。然而List是一个接口,不能被实例化。于是我想到,既然自带的参数解析器不能解析,那就自定义一个参数解析器来实现这个功能。
List存在类型擦除,在运行期不能够通过反射来获取泛型,所以得有个办法获取泛型,我便定义了一个注解ListParam
/**
* 强制申明List的泛型<br/>
* 用于反射获取参数类型
*
* @author zengyuanjun
*
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ListParam {
public Class<?> value();
}
接下来写自定义的解析器ListArgumentResolver 。解析器需要实现HandlerMethodArgumentResolver接口,该接口有两个方法:
1. supportsParameter(MethodParameter parameter) 返回当前解析器是否支持该参数。
2. resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) 具体的参数解析实现。
先使用webRequest.getParameterValues(String paramName)获取request中的参数数组,遍历添加到List<MutablePropertyValues>中,以分配给各个对象。再循环使用ServletRequestDataBinder绑定PropertyValues进行类型转换,得到需要的对象集合。
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List; import org.springframework.beans.BeanUtils;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer; import com.zyj.springboot.study.annotation.ListParam; /**
* List集合参数解析器 <br/>
* @author zengyuanjun
*
*/
@Component
public class ListArgumentResolver implements HandlerMethodArgumentResolver { @Override
public boolean supportsParameter(MethodParameter parameter) {
if (null != parameter.getParameterAnnotation(ListParam.class)
&& List.class.equals(parameter.getParameterType())) {
return true;
}
return false;
} @Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
String[] parameterValues = null;
MutablePropertyValues mutablePropertyValues = null;
Class<?> elementClass = getElementTypeFromAnnotation(parameter);
List<MutablePropertyValues> mpvList = new ArrayList<MutablePropertyValues>();
Field[] fields = elementClass.getDeclaredFields();
for (Field field : fields) {
parameterValues = webRequest.getParameterValues(field.getName());
if (null == parameterValues) {
continue;
}
for (int i = 0; i < parameterValues.length; i++) {
if (mpvList.size() <= i) {
mutablePropertyValues = new MutablePropertyValues();
mutablePropertyValues.add(field.getName(), parameterValues[i]);
mpvList.add(mutablePropertyValues);
} else {
mutablePropertyValues = mpvList.get(i);
mutablePropertyValues.add(field.getName(), parameterValues[i]);
}
}
} String name = ClassUtils.getShortNameAsProperty(elementClass);
Object attribute = null;
WebDataBinder binder = null;
ServletRequestDataBinder servletBinder = null;
Object element = null;
List<Object> actualParameter = new ArrayList<Object>(mpvList.size());
for (int i = 0; i < mpvList.size(); i++) {
attribute = BeanUtils.instantiateClass(elementClass);
binder = binderFactory.createBinder(webRequest, attribute, name);
servletBinder = (ServletRequestDataBinder) binder;
servletBinder.bind(mpvList.get(i));
element = binder.getTarget();
actualParameter.add(binder.convertIfNecessary(element, elementClass, parameter));
} return actualParameter;
} private Class<?> getElementTypeFromAnnotation(MethodParameter parameter) {
ListParam parameterAnnotation = parameter.getParameterAnnotation(ListParam.class);
return parameterAnnotation.value();
} }
定义好解析器后,需要注入到RequestMappingHandlerAdapter中,这里要注意,自带的ServletModelAttributeMethodProcessor解析器是对List类型生效的!!!所以必须把自定义的解析器放到ServletModelAttributeMethodProcessor前面,我这里直接把自定义的解析器ListArgumentResolver放到了第一个。
import java.util.LinkedList;
import java.util.List; import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; import com.zyj.springboot.study.resolver.ListArgumentResolver; /**
* 初始化将自定义的ListArgumentResolver注入到RequestMappingHandlerAdapter中
* @author zengyuanjun
*
*/
@Component
public class InitialListArgumentResolver implements ApplicationContextAware {
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
RequestMappingHandlerAdapter handlerAdapter = applicationContext.getBean(RequestMappingHandlerAdapter.class);
List<HandlerMethodArgumentResolver> argumentResolvers = handlerAdapter.getArgumentResolvers();
List<HandlerMethodArgumentResolver> resolvers = new LinkedList<HandlerMethodArgumentResolver>();
resolvers.add(new ListArgumentResolver());
resolvers.addAll(argumentResolvers);
handlerAdapter.setArgumentResolvers(resolvers);
}
}
然后?就没有然后了,使用时在List参数前加上ListParam注解,申明一下类型就好。最后还是给个使用的小例子吧!
页面定义了多个user对象的信息,它们的name是重复的。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>(-.-) ZYJ (*-*)</title>
</head>
<body>
<form action="http://localhost:8080/addUser">
<input name="id" value="1"></input>
<input name="userName" value="user1"></input>
<input name="telephone" value="13411111111"></input>
<input name="birthDay" value="2018-11-01"></input> <input name="id" value="2"></input>
<input name="userName" value="user2"></input>
<input name="telephone" value="15822222222"></input>
<input name="birthDay" value="2018-11-02"></input> <input name="id" value="3"></input>
<input name="userName" value="user3"></input>
<input name="telephone" value="18033333333"></input>
<input name="birthDay" value="2018-11-03"></input>
</form> <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
<script>
$(function(){ $.ajax({
url : $('form').attr('action'),
type : 'post',
data : $('form').serialize(),
success : function(data){
alert(data);
}
})
})
</script>
</body>
</html>
后台controller使用List<User>接收,注意要加上@ListParam(User.class)申明类型。
@RestController
public class UserContoller {
@Autowired
private UserService userService; @RequestMapping("/addUsers")
public Object addUsers(@ListParam(User.class) List<User> users){
return userService.addUsers(users);
}
}
随机推荐
-
转】C#接口-显式接口和隐式接口的实现
[转]C#接口-显式接口和隐式接口的实现 C#中对于接口的实现方式有隐式接口和显式接口两种: 类和接口都能调用到,事实上这就是“隐式接口实现”. 那么“显示接口实现”是神马模样呢? interface ...
-
android用讯飞实现TTS语音合成 实现中文版
Android系统从1.6版本开始就支持TTS(Text-To-Speech),即语音合成.但是android系统默认的TTS引擎:Pic TTS不支持中文.所以我们得安装自己的TTS引擎和语音包. ...
-
struts2校验总结
struts校验框架提供两种校验:客户端校验和服务端校验.它们都是主要检查浏览器输入数据是否合法的校验器. 服务端校验 服务端校验是在服务器上检查输入数据,它的实现方法是重写validate()方法. ...
-
【转】SQL中内连接和外连接
如表 ------------------------------------------------- table1 | table2 | ----------------- ...
-
OpenCV 2.4.10 Linux Qt Conifguration
Download CMake 2.8.12 Extract the file, and run "./bootstrap", then "make", then ...
-
[GCJ]Password Attacker
https://code.google.com/codejam/contest/4214486/dashboard#s=p0 排列组合.DP递推式,如下代码.dp[m][n]表示长度为n的字符串里有m ...
-
CSS实现文字竖排 DIV CSS文字垂直竖列排版显示如何实现?
DIV CSS实现文字竖排排版显示兼容各大浏览器,让文字垂直竖列排版布局. 有时我们需要一段文字进行从上到下竖列排版,我们知道CSS样式中有一样式可以让其竖列排版,但所有浏览器不全兼容,逼不得已放弃. ...
-
oschina开源硬件其它开源,开源硬件
硬件驱动/工具 25Linux内核 101桌面环境 40开源字体 58嵌入式操作系统 33输入法 110开源硬件 57开源图书 5开源家居 17物联网 87开源货币/比特币 32NASA 开源项目 1 ...
-
js nextSibling属性和previousSibling属性概述及使用注意
1:nextSibling属性 该属性表示当前节点的下一个节点(其后的节点与当前节点同属一个级别):如果其后没有与其同级的节点,则返回null. 需要特别注意的是:该属性在不同的浏览器中的执行结果并不 ...
-
201521123112《Java程序设计》第5周学习总结
1. 本周学习总结 1.1 尝试使用思维导图总结有关多态与接口的知识点 1.2 可选:使用常规方法总结其他上课内容 课上讲了一些Markdown的用法,包括分割线.参考链接.代码引入等等. 2. 书面 ...