SpringMVC自动封装List对象——自定义参数解析器

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

时间: 2024-10-10 05:06:43

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

Spring自定义argumentResolver参数解析器

在一个web程序中,当一个HTTP请求进来时,会被容器处理进而转换成一个servlet请求.http请求所携带的数据,虽然是格式化的但是无类型:而java作为强类型语言,同时为了健壮性考虑,必然要有完善的类型约束.当然,那么,将数据从servlet请求中转换到java中,一个很原始的方式是手动处理.幸好,Spring MVC通过以注解往函数添加额外信息的方式,使得上述的数据转换过程能够交由框架自动处理.从一个角度去看,Controller中的函数声明及注解定义了此HTTP请求的数据格式和类型,也

SpringMVC ArgumentREsoler(方法参数解析器)

例: 先创建自定义注解 // @Target(ElementType.PARAMETER) //在运行时生效 //RetentionPolicy.RUNTIME 给方法使用 @Retention(RetentionPolicy.RUNTIME) public @interface CurrentUser { } 去定义自定义注解的规则 package com.lanou.demo.resolvers; import com.lanou.demo.annotation.CurrentUser; i

HandlerMethodArgumentResolver 参数解析器

关于springMvc中的参数解析器 springMvc中的HandlerAdapter会检测所有的 HandlerMethodArgumentResolver(对参数的解析器) HandlerMethodArgumentResolver接口包含两个接口方法 1 boolean supportsParameter(MethodParameter parameter); MethodParameter方法参数对象 通过它可以获取该方法参数上的一些信息 如方法参数中的注解信息等 通过该方法我们如果需

BeanNameViewResolver自定义视图解析器

RedirectView:定义外部资源视图对象 JstlView:定义内部资源视图对象 使用自定义视图解析器 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

SpringMVC中ModelAndView对象与“视图解析器”

摘要: spring MVC这个环境中,Spring MVC会依据controller(或者你叫它handler)中处理方法的返回值,进行解析,解析之后提供一个视图,作为响应. 标注了@Controller的处理器,实际上本质是一个POJO,你标注了@Controller,我就高看你一眼. spring MVC这个环境中,Spring MVC会依据controller(或者你叫它handler)中处理方法的返回值,进行解析,解析之后提供一个视图,作为响应.标注了@Controller的处理器,实

7.SpringMVC 配置式开发-ModelAndView和视图解析器

ModelAndView 1.Model(模型) 1.model的本质就是HashMap,向模型中添加数据,就是往HashMap中去添加数据 2.HashMap 是一个单向查找数组,单向链表数组 3.LinkedHashMap,本质是一个HashMap,但其将Entry进行了扩展,变成双向的了 2.View(视图) ModelAndView 内部通过 setViewName() 指定视图名称 这个视图名称对应一个视图对象,这个视图对象会被封装在ModelAndView中,传给视图解析器来解析 视

自定义表达式解析器

解析器: import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.List; import java.util.regex.Pattern; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; imp

osg学习笔记2, 命令行参数解析器ArgumentParser

ArgumentParser主要负责命令行参数的读取 #include <osgDB/ReadFile> #include <osgViewer/Viewer> int main(int argc, char **argv) { //命令行参数读取 osg::ArgumentParser arguments(&argc, argv); std::string filename; arguments.read("--model", filename); o

Rails 4.0 移除了 XML 参数解析器。若要使用请加入 actionpack-xml_parser

拜读了用 Rails 搭建微信公众平台 API之后发现, params[:xml]这个办法在Rails 4里面已经被办掉了,于是就看了一下Rails 4的新特性发现XML Parameter parsing has been sent to a plugin. http://www.rubydoc.info/github/rails/actionpack-xml_parser actionpack-xml_parser A XML parameters parser for Action Pac