前台传递的参数为集合对象时,后台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) 具体的参数解析实现。
import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.springframework.beans.BeanUtils; import org.springframework.core.MethodParameter; import org.springframework.stereotype.Component; 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/> * 目前仅支持List集合中元素为基本数据类型或由基本数据类型组成的简单对象 * @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 { List<Object> actualParameter = new ArrayList<Object>(); String[] parameterValues = null; Object element = null; Object attributeValue = null; Method setter = null; Class<?> elementClass = getElementTypeFromAnnotation(parameter); WebDataBinder binder = binderFactory.createBinder(webRequest, actualParameter, parameter.getParameterName()); if (BeanUtils.isSimpleValueType(elementClass)) { // 如果是基本对象集合,直接转换类型后添加到集合中 parameterValues = webRequest.getParameterValues(parameter.getParameterName()); for (int i = 0; i < parameterValues.length; i++) { attributeValue = binder.convertIfNecessary(parameterValues[i], elementClass); actualParameter.add(attributeValue); } } else { // 否则,使用反射获取属性的setter方法注入值 Map<String, Method> setterMap = getSetterMap(elementClass); Set<String> attributeNameSet = setterMap.keySet(); for (String attributeName : attributeNameSet) { parameterValues = webRequest.getParameterValues(attributeName); if (null == parameterValues) { continue; } setter = setterMap.get(attributeName); for (int i = 0; i < parameterValues.length; i++) { if (actualParameter.size() <= i) { element = BeanUtils.instantiateClass(elementClass); actualParameter.add(element); } else { element = actualParameter.get(i); } attributeValue = binder.convertIfNecessary(parameterValues[i], setter.getParameterTypes()[0]); setter.invoke(element, attributeValue); } } } return actualParameter; } private Class<?> getElementTypeFromAnnotation(MethodParameter parameter) { ListParam parameterAnnotation = parameter.getParameterAnnotation(ListParam.class); return parameterAnnotation.value(); } private Map<String, Method> getSetterMap(Class<?> clz) { Map<String, Method> setterMap = new HashMap<String, Method>(); Field[] fields = clz.getDeclaredFields(); Method[] declaredMethods = clz.getDeclaredMethods(); for (Field field : fields) { for (Method method : declaredMethods) { if (isSetter(field, method)) { setterMap.put(field.getName(), method); break; } } } return setterMap; } private boolean isSetter(Field field, Method method) { Class<?>[] parameterTypes = method.getParameterTypes(); if (parameterTypes.length != 1 || !parameterTypes[0].equals(field.getType())) { return false; } return method.getName().equalsIgnoreCase("set" + field.getName()); } }
定义好解析器后,需要注入到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注解,申明一下类型就好。最后还是给个使用的小例子吧!
@RestController public class UserContoller { @Autowired private UserService userService; @RequestMapping("/addUsers") public Object addUsers(@ListParam(User.class) List<User> users){ return userService.addUsers(users); } }
原文地址:https://www.cnblogs.com/zengyuanjun/p/9900350.html