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

2018-11-05 08:22:26来源:博客园 阅读 ()

新老客户大回馈,云服务器低至5折

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

 

标签:

版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有

上一篇:将项目部署到服务器之后从页面接收的中文乱码

下一篇:JavaBean和List&lt;JavaBean&gt;