前台传递的参数为集合对象时,后台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);
}
}

SpringMVC自动封装List对象 —— 自定义参数解析器的更多相关文章

  1. SpringMVC 自定义参数解析器.

    一.简述 有没有想过像 @RequestParam.@RequestBody 这些注解的工作原理呢?为什么 form 表单.application/json 的参数能够直接封装进 Bean 对象中呢? ...

  2. springmvc 源码分析(三) -- 自定义处理器映射器和自定义处理器适配器,以及自定义参数解析器 和错误跳转自定页面

    测试环境搭建: 本次搭建是基于springboot来实现的,代码在码云的链接:https://gitee.com/yangxioahui/thymeleaf.git DispatcherServlet ...

  3. SpringBoot系列教程web篇之如何自定义参数解析器

    title: 190831-SpringBoot系列教程web篇之如何自定义参数解析器 banner: /spring-blog/imgs/190831/logo.jpg tags: 请求参数 cat ...

  4. Spring自定义参数解析器

    结合redis编写User自定义参数解析器UserArgumentResolver import javax.servlet.http.Cookie; import javax.servlet.htt ...

  5. SpringBoot自定义参数解析器

    一.背景 平常经常用 @RequestParam注解来获取参数,然后想到我能不能写个自己注解获取请求的ip地址呢?就像这样 @IP String ip 二.分析 于是开始分析 @RequestPara ...

  6. 一步一步自定义SpringMVC参数解析器

    随心所欲,自定义参数解析器绑定数据. 题图:from Zoommy 干货 SpringMVC解析器用于解析request请求参数并绑定数据到Controller的入参上. 自定义一个参数解析器需要实现 ...

  7. 自定义springmvc参数解析器

    实现spring HandlerMethodArgumentResolver接口 通过使用@JsonArg自定义注解来解析json数据(通过fastjson的jsonPath),支持多个参数(@Req ...

  8. 自定义HandlerMethodArgumentResolver参数解析器和源码分析

    在初学springmvc框架时,我就一直有一个疑问,为什么controller方法上竟然可以放这么多的参数,而且都能得到想要的对象,比如HttpServletRequest或HttpServletRe ...

  9. SpringMVC接收复杂集合对象(参数)代码示例

    原文: https://www.jb51.net/article/128233.htm SpringMVC接收复杂集合对象(参数)代码示例 更新时间:2017年11月15日 09:18:15   作者 ...

随机推荐

  1. java基础(一)---数据类型&Math方法&强制转换

    数据类型及各种Math类方法 public class HelloWorld { public static void main(String args[]) { //各种数据类型的熟悉掌握,强制类型 ...

  2. abseil初体验[google开源的C++库]

    Google公开了其项目内部使用的一系列C++库,具体介绍参考: http://www.infoq.com/cn/news/2017/10/abseil?utm_source=infoq&ut ...

  3. win7文件搜索技巧

    重要说明   (1)搜索的字符串是大小写不敏感的 (2)字符串带双引号与不带双引号是有区别的 如:hello,搜索包含hello单词开头的文件或目录,名为“aa_HELLOcc...”.“cc-Hel ...

  4. ERP主副机和打印机配置FAQ

    一.主副机 1. ERP主副机配置 先配置主机 再配置副机 在主机的电脑上键盘按win+R键,输入cmd 再输入ipconfig查看主机的IP 然后在副机上安装银盒子ERP,在红框位置输入主机IP 选 ...

  5. linux富规则的案例

    使用firewall-cmd添加富规则: 1.拒绝来自public区域中ip地址为192.168.0.11的所有流量 firewall-cmd --permanent --zone=public -- ...

  6. 原生js :removeClass和addClass

    function removeClass(obj, aClass) { var re = new RegExp('\\b' + aClass + '\\b'); if (obj.className ! ...

  7. 4.11Python数据处理篇之Matplotlib系列(十一)---图例,网格,背景的设置

    目录 目录 前言 (一)图例legend 1.默认不带参数的图例 2.添加参数的图例 3.将图例移动到框外 (二)网格grid 1.说明 2.源代码: 3.输出效果 (三)背景axses 1.设置全局 ...

  8. Java入门(一):Hello World !

    前言 从今天开始,准备写Java Web开发的系列文章,毕竟自己主攻的还是Java方向,Python只是业余学习的兴趣使然,在第二技能还没有培养成熟前,做好第一技能的巩固和提高是很有必要的.从正式入行 ...

  9. June.19 2018, Week 25th Tuesday

    True love is visible not to the eyes but to the heart. 真爱不靠眼睛看,要用心感受. True love is visible not to th ...

  10. GMM算法的matlab程序(初步)

    GMM算法的matlab程序 在https://www.cnblogs.com/kailugaji/p/9648508.html文章中已经介绍了GMM算法,现在用matlab程序实现它. 作者:凯鲁嘎 ...