SpringMVC的Controller接口方法参数解析

来源:互联网 发布:淘宝怎么更改支付宝 编辑:程序博客网 时间:2024/05/17 07:49

一、举例说明

(1)示例:方法参数没有任何注解 public Object query(List<Long> idList),传递参数为 .param("idList", "1").param("idList", "2")

结果:失败。org.springframework.beans.BeanInstantiationException: Could not instantiate bean class [java.util.List]: Specified class is an interface

原因:没有任何注解并且不属于原生类型以及在业务系统不常见的类型,常用的List,Set就不满足前面的条件,所以参数的解析就交给了保底的ServletModelAttributeMethodProcessor以及它的父类ModelAttributeMethodProcessor,进过一系列的处理到达BeanUtils.instantiateClass(parameter.getParameterType())函数进行参数类型初始化。

(2)示例:方法参数没有任何注解 public Object query(ArrayList<Long> idList) ,传递参数为 .param("idList", "1").param("idList", "2")

结果:失败。内部报错throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName,matches.buildErrorMessage(), matches.getPossibleMatches()),SpringMVC隐藏了错误,结果虽然没有报错,但是参数也没有映射成功。

原因:同(1),区别是ArrayList可以初始化,但是参数具体映射需要根据set/get属性方法进行反射赋值,ArrayList并没有set属性方法,所以失败。

(3)示例:方法参数有注解 public Object query(@RequestParam("idList") List<Long> idList) ,传递参数为 .param("idList", "1").param("idList", "2")

结果:成功

原因:因为我们有@RequestParam注解,所以参数解析给了优先级最高的RequestParamMethodArgumentResolver,它不需要对参数类型进行初始化,

而是采用另一套解析逻辑(它的父类AbstractNamedValueMethodArgumentResolver)来处理,在这里先针对注解进行解析,获取参数名value,通过String[] paramValues = webRequest.getParameterValues(name)获取参数值,然后选择SpringMVC容器预先加载的100多个转换器中的StringToCollectionConverter进行参数转化,不经过set/get属性方法,根据需要的参数类型List直接创建ArrayList,然后赋值成功。

(4)示例:方法参数有注解 public Object query(@RequestParam("idList") List<Long> notIdList) ,传递参数为 .param("idList", "1").param("idList", "2")

结果:成功

原因:当有注解时不会关注接口的参数名,只会以注解里面的value为准

(5)示例:方法参数没有任何注解 public Object query(User user) ,传递参数为 .param("name", "1").param("age", "2").param("time", "2017-09-22 00:00:00"),User对象里面含有这三个属性。

结果:成功

原因:同(1),区别是该类的两个属性具有set/get属性方法,writeMethod.invoke(this.object, value)可以赋值成功,在反射之前,需要对参数值进行类型转换Converter,因为字符串转日期的转换器容器自身并没有,所以我们需要自定义,通过@InitBinder注解定义一个转换器,容器启动就会加载到转换器列表里,用的时候会取出来使用。

(6)示例:方法参数没有任何注解 public Object query(User user) ,传递参数为.param("idList", "1").param("idList", "2"),User对象里面含有这个List集合属性。

结果:成功

原因:同(5),获取传递来的参数Enumeration<String> paramNames = request.getParameterNames(),同过set方法反射赋值

二、总结

1、SpringMVC其实是鼓励我们对参数使用注解的,容器会预先加载几十个Resolver到list集合里面,在进行参数解析的时候侯从中按顺序选择,RequestParamMethodArgumentResolver是排在第一个的,而不用注解的话会使用ServletModelAttributeMethodProcessor进行解析,它在集合的位置是最后,所以每个参数需要遍历一遍,但是容器会对每次参数的解析Resolver关系进行缓存,效率也没有多少降低。

2、集合参数传递,容器内部支持以逗号的集合参数param("idList", "1,2,3,4")

3、ServletModelAttributeMethodProcessor会先给变量类进行实例化,再根据set/get反射赋值

4、自定义对象User最好使用@ModelAttribute 注解,其他的基本类型包装对象以及集合使用@RequestParam注解,虽然@RequestParam的required方法经常会打印出很多错误日志,RESTful API接口可以使用@PathVariable注解。

三、补充

根据前面的例子显示,集合类参数不使用注解的话就不能成功被解析,这怎么能忍?下面是我参考源码改写的一个支持集合的CollectionArgumentResolver

public class CollectionArgumentResolver extends AbstractNamedValueMethodArgumentResolver
        implements UriComponentsContributor {


    public CollectionArgumentResolver() {
        super();
    }


    @Override
    protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
        return  new RequestParamNamedValueInfo();
    }




    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        if (parameter.hasParameterAnnotations()) {
            return false;
        }
        Class<?> paramType = parameter.getParameterType();
        return Collection.class.isAssignableFrom(paramType);
    }


    @Override
    protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest webRequest) throws Exception {
        Object arg;
        HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
        MultipartHttpServletRequest multipartRequest =
                WebUtils.getNativeRequest(servletRequest, MultipartHttpServletRequest.class);


        if (MultipartFile.class.equals(parameter.getParameterType())) {
            assertIsMultipartRequest(servletRequest);
            Assert.notNull(multipartRequest, "Expected MultipartHttpServletRequest: is a MultipartResolver configured?");
            arg = multipartRequest.getFile(name);
        }
        else if (isMultipartFileCollection(parameter)) {
            assertIsMultipartRequest(servletRequest);
            Assert.notNull(multipartRequest, "Expected MultipartHttpServletRequest: is a MultipartResolver configured?");
            arg = multipartRequest.getFiles(name);
        }
        else if(isMultipartFileArray(parameter)) {
            assertIsMultipartRequest(servletRequest);
            Assert.notNull(multipartRequest, "Expected MultipartHttpServletRequest: is a MultipartResolver configured?");
            arg = multipartRequest.getFiles(name).toArray(new MultipartFile[0]);
        }
        else if ("javax.servlet.http.Part".equals(parameter.getParameterType().getName())) {
            assertIsMultipartRequest(servletRequest);
            arg = servletRequest.getPart(name);
        }
        else if (isPartCollection(parameter)) {
            assertIsMultipartRequest(servletRequest);
            arg = new ArrayList<Object>(servletRequest.getParts());
        }
        else if (isPartArray(parameter)) {
            assertIsMultipartRequest(servletRequest);
            arg = RequestPartResolver.resolvePart(servletRequest);
        }
        else {
            arg = null;
            if (multipartRequest != null) {
                List<MultipartFile> files = multipartRequest.getFiles(name);
                if (!files.isEmpty()) {
                    arg = (files.size() == 1 ? files.get(0) : files);
                }
            }
            if (arg == null) {
                String[] paramValues = webRequest.getParameterValues(name);
                if (paramValues != null) {
                    arg = paramValues.length == 1 ? paramValues[0] : paramValues;
                }
            }
        }


        return arg;
    }


    private void assertIsMultipartRequest(HttpServletRequest request) {
        String contentType = request.getContentType();
        if (contentType == null || !contentType.toLowerCase().startsWith("multipart/")) {
            throw new MultipartException("The current request is not a multipart request");
        }
    }


    private boolean isMultipartFileCollection(MethodParameter parameter) {
        Class<?> collectionType = getCollectionParameterType(parameter);
        return ((collectionType != null) && collectionType.equals(MultipartFile.class));
    }


    private boolean isPartCollection(MethodParameter parameter) {
        Class<?> collectionType = getCollectionParameterType(parameter);
        return ((collectionType != null) && "javax.servlet.http.Part".equals(collectionType.getName()));
    }


    private boolean isPartArray(MethodParameter parameter) {
        Class<?> paramType = parameter.getParameterType().getComponentType();
        return ((paramType != null) && "javax.servlet.http.Part".equals(paramType.getName()));
    }


    private boolean isMultipartFileArray(MethodParameter parameter) {
        Class<?> paramType = parameter.getParameterType().getComponentType();
        return ((paramType != null) && MultipartFile.class.equals(paramType));
    }


    private Class<?> getCollectionParameterType(MethodParameter parameter) {
        Class<?> paramType = parameter.getParameterType();
        if (Collection.class.equals(paramType) || List.class.isAssignableFrom(paramType)){
            Class<?> valueType = GenericCollectionTypeResolver.getCollectionParameterType(parameter);
            if (valueType != null) {
                return valueType;
            }
        }
        return null;
    }


    @Override
    protected void handleMissingValue(String paramName, MethodParameter parameter) throws ServletException {
        throw new MissingServletRequestParameterException(paramName, parameter.getParameterType().getSimpleName());
    }


    @Override
    public void contributeMethodArgument(MethodParameter parameter, Object value,
                                         UriComponentsBuilder builder, Map<String, Object> uriVariables, ConversionService conversionService) {
    }


    private static class RequestParamNamedValueInfo extends NamedValueInfo {


        public RequestParamNamedValueInfo() {
            super("", false, ValueConstants.DEFAULT_NONE);
        }


    }


    private static class RequestPartResolver {


        public static Object resolvePart(HttpServletRequest servletRequest) throws Exception {
            return servletRequest.getParts().toArray(new Part[servletRequest.getParts().size()]);
        }
    }


}


 <mvc:annotation-driven>
        <mvc:argument-resolvers>
            <bean class="com.ph3636.CollectionArgumentResolver"/>
        </mvc:argument-resolvers>
    </mvc:annotation-driven>


阅读全文
0 0
原创粉丝点击