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);
        }
    }
  • 相关阅读:
    (网页)中的简单的遮罩层
    (后端)shiro:Wildcard string cannot be null or empty. Make sure permission strings are properly formatted.
    (网页)jQuery的时间datetime控件在AngularJs中使用实例
    Maven Myeclipse 搭建项目
    MyBatis 环境搭建 (一)
    java 常用方法
    XML 基础
    JS BOM
    js 事件
    js 的使用原则
  • 原文地址:https://www.cnblogs.com/zengyuanjun/p/9900350.html
Copyright © 2011-2022 走看看