zoukankan      html  css  js  c++  java
  • SpringMVC自动封装List对象 —— 自定义参数解析器

      下文方法受spring版本影响,不具备通用性,仅做参考

      前台传递的参数为集合对象时,后台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);
        }
    }
  • 相关阅读:
    BIOS详解:什么是BIOS ?BIOS的作用?CMOS及其与BIOS的关系?
    随机数不随机
    解决hexo神烦的DTraceProviderBindings MODULE_NOT_FOUND
    保护模式特权级别DPL,RPL,CPL 之间的联系和区别
    Linux内核 hlist_head/hlist_node结构解析
    x86中的页表结构和页表项格式
    Linux下/proc目录简介
    bdev文件系统
    X86 IO端口和MMIO
    Mac OS Alfred 2 tips
  • 原文地址:https://www.cnblogs.com/zengyuanjun/p/9900350.html
Copyright © 2011-2022 走看看