前台传递的参数为集合对象时,后台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. ReactNative调研结果

    React Native相关调研总结 一.概要 React Native - 使用React开发世界一流的原生应用: 使用JavaScript和React(对JS有一定扩展)作为开发语言: React ...

  2. onmouseover和onmouseenter区别

    onmouseover和onmouseenter都是鼠标进入时触发,onmouseover在所选元素的子元素间切换的时候也触发! <!doctype html><html lang= ...

  3. <1>Linux日志查找方法

    Linux日志查找方法 适用于测试,开发,运维人员,用来查找Linux服务器问题的一般方法,比较实用,如果有更好的办法可以一块讨论,欢迎大神们来指导哈!!! 进入正题 第一步.通过Xshell登录服务 ...

  4. C#核心基础--浅谈类和对象的概念

    浅谈类和对象的概念 一.什么是类?什么是对象? 学习一门面向对象编程语言,我们必须得知道什么是类?什么是对象? 类(Class)实际上是对某种类型的对象定义变量和方法的原型.它表示对现实生活中一类具有 ...

  5. JavaWeb入门笔记

    Java web笔记 一.HTTP协议 HTTP(超文本传输协议),它是一种主流B/S架构中应用的通信协议.具有以下特点: 1.无状态 服务端不会记录客户端每次提交的请求,服务器一旦相应客户端之后,就 ...

  6. 初识Spring Boot

    ​ 1.Spring Boot简介 Spring Boot是由Pivotal团队提供的全新框架,用于简化基于Spring的搭建与开发过程,通过少量的代码创建Spring应用. 2.Spring Boo ...

  7. SQL Server 执行计划解析

    前置说明: 本文旨在通过一个简单的执行计划来引申并总结一些SQL Server数据库中的SQL优化的关键点,日常总结,其中的概念介绍中有不足之处有待补充修改,希望大神勘误. SQL语句如下: SELE ...

  8. OkHttp简单使用

    OkHttp是一个轻量优秀的网络框架.用于替代HttpUrlConnection和Apache HttpClient(android API23 6.0里已移除HttpClient,) 使用方法: 1 ...

  9. Python输出和输入

    一.input()函数 在 Python 中,使用内置函数 input()可以接收用户的键盘输入. input()函数的基本用法如 下: variable = input("提示文字") 其中,var ...

  10. LeetCode算法题-Word Pattern(Java实现)

    这是悦乐书的第202次更新,第212篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第68题(顺位题号是290).给定一个模式和一个字符串str,找到str是否完全匹配该模 ...