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) 具體的參數解析實現。

  先使用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);
    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM