SpringMVC请求处理之对方法参数的处理

本文详细剖析了SpringMVC框架中方法参数的处理流程,包括不同类型参数的解析、转换及自定义编辑器的使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


前言

讲完了DispatchServlet(也可以说是SpringMVC框架)的初始化之后,我们再接着看DispatchServlet处理请求的原理,也可以说是SpringMVC处理请求的原理。今天就先来看看SpringMVC对方法参数的处理。

我们先给出一个测试的类

package com.wangcc.controller;

import java.util.Date;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import com.wangcc.entity.Player;

@Controller
// 不是以/开头的,springmvc会自动帮你添加/
@RequestMapping("/test")
public class TestController {
    @RequestMapping("testRb")
    @ResponseBody
    public Player testRb(@RequestBody Player player) {
        return player;
    }

    @RequestMapping("testEntity")
    @ResponseBody
    public Player testEntity(Player player) {
        return player;
    }

    @RequestMapping("testEntityWithRp")
    @ResponseBody
    public Player testEntityWithRp(@RequestParam Player player) {
        return player;
    }

    @RequestMapping("/testDate")
    @ResponseBody
    public Date testDate(Date date) {
        return date;
    }
}

我们之前已经讲过,对带有@Controller注解的Bean以及其方法上有@RequestMapping注解的对应的url请求的处理,调用的是RequestMappingHandlerAdapter的invokeHandleMethod方法。

private ModelAndView invokeHandleMethod(HttpServletRequest request,
            HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

        ServletWebRequest webRequest = new ServletWebRequest(request, response);

        WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
        ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
        ServletInvocableHandlerMethod requestMappingMethod = createRequestMappingMethod(handlerMethod, binderFactory);

        ModelAndViewContainer mavContainer = new ModelAndViewContainer();
        mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
        modelFactory.initModel(webRequest, mavContainer, requestMappingMethod);
        mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

        AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
        asyncWebRequest.setTimeout(this.asyncRequestTimeout);

        final WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        asyncManager.setTaskExecutor(this.taskExecutor);
        asyncManager.setAsyncWebRequest(asyncWebRequest);
        asyncManager.registerCallableInterceptors(this.callableInterceptors);
        asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);

        if (asyncManager.hasConcurrentResult()) {
            Object result = asyncManager.getConcurrentResult();
            mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
            asyncManager.clearConcurrentResult();

            if (logger.isDebugEnabled()) {
                logger.debug("Found concurrent result value [" + result + "]");
            }
            requestMappingMethod = requestMappingMethod.wrapConcurrentResult(result);
        }

        requestMappingMethod.invokeAndHandle(webRequest, mavContainer);

        if (asyncManager.isConcurrentHandlingStarted()) {
            return null;
        }

        return getModelAndView(mavContainer, modelFactory, webRequest);
    }

就是通过这个方法得到了ModelAndView实例,所以当完整的走完这个方法之后,也就对请求的处理的主干部分走完了。今天我们就来看这个方法的一小部分。

    ServletInvocableHandlerMethod requestMappingMethod = createRequestMappingMethod(handlerMethod, binderFactory);
        requestMappingMethod.invokeAndHandle(webRequest, mavContainer);

SpringMVC处理方法参数

  • 我们先看下上面ServletInvocableHandlerMethod实例的获取
ServletInvocableHandlerMethod requestMappingMethod = createRequestMappingMethod(handlerMethod, binderFactory);
    private ServletInvocableHandlerMethod createRequestMappingMethod(
            HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) {

        ServletInvocableHandlerMethod requestMethod;
        requestMethod = new ServletInvocableHandlerMethod(handlerMethod);
        requestMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
        requestMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
        requestMethod.setDataBinderFactory(binderFactory);
        requestMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
        return requestMethod;
    }

1.使用我们在初始化RequestMappingHandlerMapping时注册到AbstractHandlerMapping的urlMap时封装的HandlerMethod实例handlerMethod为参数构建一个ServletInvocableHandlerMethod实例。

2.分别以RequestMappingHandlerAdapter的argumentResolvers和returnValueHandlers属性注入到requestMethod的argumentResolvers属性和returnValueHandlers属性中。

这里需要讲解下RequestMappingHandlerAdapter的argumentResolvers和returnValueHandlers怎么得到的以及内容是什么。

我们在讲解RequestMappingHandlerMapping的时候提到了InitializingBean接口,而我们发现RequestMappingHandlerAdapter也实现了这个接口,那么我们就知道了在初始化这个类的时候是需要执行他的afterPropertiesSet方法,而这两个属性的注入就是在这个方法里完成的。

afterPropertiesSet

    public void afterPropertiesSet() {
        // Do this first, it may add ResponseBody advice beans
        initControllerAdviceCache();

        if (this.argumentResolvers == null) {
            List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
            this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
        }
        if (this.initBinderArgumentResolvers == null) {
            List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
            this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
        }
        if (this.returnValueHandlers == null) {
            List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
            this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
        }
    }

我们先通过getDefaultArgumentResolvers得到一个HandlerMethodArgumentResolver集合

private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
        List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();

        // Annotation-based argument resolution
        resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
        resolvers.add(new RequestParamMapMethodArgumentResolver());
        resolvers.add(new PathVariableMethodArgumentResolver());
        resolvers.add(new PathVariableMapMethodArgumentResolver());
        resolvers.add(new MatrixVariableMethodArgumentResolver());
        resolvers.add(new MatrixVariableMapMethodArgumentResolver());
        resolvers.add(new ServletModelAttributeMethodProcessor(false));
        resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters()));
        resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters()));
        resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
        resolvers.add(new RequestHeaderMapMethodArgumentResolver());
        resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
        resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));

        // Type-based argument resolution
        resolvers.add(new ServletRequestMethodArgumentResolver());
        resolvers.add(new ServletResponseMethodArgumentResolver());
        resolvers.add(new HttpEntityMethodProcessor(getMessageConverters()));
        resolvers.add(new RedirectAttributesMethodArgumentResolver());
        resolvers.add(new ModelMethodProcessor());
        resolvers.add(new MapMethodProcessor());
        resolvers.add(new ErrorsMethodArgumentResolver());
        resolvers.add(new SessionStatusMethodArgumentResolver());
        resolvers.add(new UriComponentsBuilderMethodArgumentResolver());

        // Custom arguments
        if (getCustomArgumentResolvers() != null) {
            resolvers.addAll(getCustomArgumentResolvers());
        }

        // Catch-all
        resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
        resolvers.add(new ServletModelAttributeMethodProcessor(true));

        return resolvers;
    }

然后将这个集合放入到HandlerMethodArgumentResolverComposite实例中。然后把这个实例赋给argumentResolvers属性。

那么returnValueHandlers属性同理。

  • 接着分析requestMappingMethod.invokeAndHandle(webRequest, mavContainer);

    public void invokeAndHandle(ServletWebRequest webRequest,
            ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
    
        Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
        setResponseStatus(webRequest);
    
        if (returnValue == null) {
            if (isRequestNotModified(webRequest) || hasResponseStatus() || mavContainer.isRequestHandled()) {
                mavContainer.setRequestHandled(true);
                return;
            }
        }
        else if (StringUtils.hasText(this.responseReason)) {
            mavContainer.setRequestHandled(true);
            return;
        }
    
        mavContainer.setRequestHandled(false);
        try {
            this.returnValueHandlers.handleReturnValue(
                    returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
        }
        catch (Exception ex) {
            if (logger.isTraceEnabled()) {
                logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);
            }
            throw ex;
        }
    }
    

    我们看下第一行

        Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
    

    这一行就已经得到了这个方法的返回值了。我们进入这个方法看。

    public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer,
            Object... providedArgs) throws Exception {
    
        Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
        if (logger.isTraceEnabled()) {
            StringBuilder sb = new StringBuilder("Invoking [");
            sb.append(getBeanType().getSimpleName()).append(".");
            sb.append(getMethod().getName()).append("] method with arguments ");
            sb.append(Arrays.asList(args));
            logger.trace(sb.toString());
        }
        Object returnValue = doInvoke(args);
        if (logger.isTraceEnabled()) {
            logger.trace("Method [" + getMethod().getName() + "] returned [" + returnValue + "]");
        }
        return returnValue;
    }
    

    我们发现第一行就是对方法参数的处理,嗯,终于找到我们今天要重点讲解的地方了。

    private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer,
            Object... providedArgs) throws Exception {
    
        MethodParameter[] parameters = getMethodParameters();
        Object[] args = new Object[parameters.length];
        for (int i = 0; i < parameters.length; i++) {
            MethodParameter parameter = parameters[i];
            parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
            GenericTypeResolver.resolveParameterType(parameter, getBean().getClass());
            args[i] = resolveProvidedArgument(parameter, providedArgs);
            if (args[i] != null) {
                continue;
            }
            if (this.argumentResolvers.supportsParameter(parameter)) {
                try {
                    args[i] = this.argumentResolvers.resolveArgument(
                            parameter, mavContainer, request, this.dataBinderFactory);
                    continue;
                }
                catch (Exception ex) {
                    if (logger.isTraceEnabled()) {
                        logger.trace(getArgumentResolutionErrorMessage("Error resolving argument", i), ex);
                    }
                    throw ex;
                }
            }
            if (args[i] == null) {
                String msg = getArgumentResolutionErrorMessage("No suitable resolver for argument", i);
                throw new IllegalStateException(msg);
            }
        }
        return args;
    }

    1.先通过this.argumentResolvers.supportsParameter(parameter)来找到能处理该方法参数的HandlerMethodArgumentResolver实例。

    2.然后通过this.argumentResolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);来调用HandlerMethodArgumentResolver实例的resolveArgument方法来处理参数。

    supportsParameter

    HandlerMethodArgumentResolverComposite

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return getArgumentResolver(parameter) != null;
    }
    private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
        HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
        if (result == null) {
            for (HandlerMethodArgumentResolver methodArgumentResolver : this.argumentResolvers) {
                if (logger.isTraceEnabled()) {
                    logger.trace("Testing if argument resolver [" + methodArgumentResolver + "] supports [" +
                            parameter.getGenericParameterType() + "]");
                }
                if (methodArgumentResolver.supportsParameter(parameter)) {
                    result = methodArgumentResolver;
                    this.argumentResolverCache.put(parameter, result);
                    break;
                }
            }
        }
        return result;
    }

    这段代码不难理解,就是在我们开始讲解的注入的HandlerMethodArgumentResolver集合里面筛选出能够处理参数的实例。

    我们以这个方法为例:

    @RequestMapping("testRb")
    @ResponseBody
    public Player testRb(@RequestBody Player player) {
        return player;
    }

    相应的实例就是RequestResponseBodyMethodProcessor,我们瞅一眼他的supportsParameter方法就一目了然了。

    RequestResponseBodyMethodProcessor

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(RequestBody.class);
    }

    所以参数上有@RequestBody注解的都会使用这个实例来处理参数。

接着看看是如何调用resolveArgument方法的

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {

        HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
        Assert.notNull(resolver, "Unknown parameter type [" + parameter.getParameterType().getName() + "]");
        return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
    }

所以调用的就是RequestResponseBodyMethodProcessor的resolveArgument方法了。

resolveArgument

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {

        Object arg = readWithMessageConverters(webRequest, parameter, parameter.getGenericParameterType());
        String name = Conventions.getVariableNameForParameter(parameter);
        WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
        if (arg != null) {
            validateIfApplicable(binder, parameter);
            if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
                throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
            }
        }
        mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
        return arg;
    }

我们先使用readWithMessageConverters来处理参数

readWithMessageConverters

    @Override
    protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter methodParam,
            Type paramType) throws IOException, HttpMediaTypeNotSupportedException {

        HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
        HttpInputMessage inputMessage = new ServletServerHttpRequest(servletRequest);

        InputStream inputStream = inputMessage.getBody();
        if (inputStream == null) {
            return handleEmptyBody(methodParam);
        }
        else if (inputStream.markSupported()) {
            inputStream.mark(1);
            if (inputStream.read() == -1) {
                return handleEmptyBody(methodParam);
            }
            inputStream.reset();
        }
        else {
            final PushbackInputStream pushbackInputStream = new PushbackInputStream(inputStream);
            int b = pushbackInputStream.read();
            if (b == -1) {
                return handleEmptyBody(methodParam);
            }
            else {
                pushbackInputStream.unread(b);
            }
            inputMessage = new ServletServerHttpRequest(servletRequest) {
                @Override
                public InputStream getBody() {
                    // Form POST should not get here
                    return pushbackInputStream;
                }
            };
        }

        return super.readWithMessageConverters(inputMessage, methodParam, paramType);
    }

在对数据做一些封装处理后,最后会调用父类AbstractMessageConverterMethodArgumentResolver的readWithMessageConverters方法

@SuppressWarnings("unchecked")
    protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage,
            MethodParameter methodParam, Type targetType) throws IOException, HttpMediaTypeNotSupportedException {

        MediaType contentType;
        try {
            contentType = inputMessage.getHeaders().getContentType();
        }
        catch (InvalidMediaTypeException ex) {
            throw new HttpMediaTypeNotSupportedException(ex.getMessage());
        }
        if (contentType == null) {
            contentType = MediaType.APPLICATION_OCTET_STREAM;
        }

        Class<?> contextClass = methodParam.getContainingClass();
        Class<T> targetClass = (Class<T>)
                ResolvableType.forMethodParameter(methodParam, targetType).resolve(Object.class);

        for (HttpMessageConverter<?> converter : this.messageConverters) {
            if (converter instanceof GenericHttpMessageConverter) {
                GenericHttpMessageConverter<?> genericConverter = (GenericHttpMessageConverter<?>) converter;
                if (genericConverter.canRead(targetType, contextClass, contentType)) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Reading [" + targetType + "] as \"" +
                                contentType + "\" using [" + converter + "]");
                    }
                    return genericConverter.read(targetType, contextClass, inputMessage);
                }
            }
            if (converter.canRead(targetClass, contentType)) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Reading [" + targetClass.getName() + "] as \"" +
                            contentType + "\" using [" + converter + "]");
                }
                return ((HttpMessageConverter<T>) converter).read(targetClass, inputMessage);
            }
        }

        throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes);
    }

这里会在messageConverters集合中选择一个合适的HttpMessageConverter来处理数据,这里的messageConverters就是RequestMappingHandlerAdapter中的属性,该属性的注入具体在SpringMVC配置文件解析(六)中有说明。

而RequestResponseBodyMethodProcessor是在初始化的时候注入messageConverters属性的,回头看getDefaultArgumentResolvers,有一句

        resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters()));

但是遗憾的是,我们在所有的messageConverters集合中都找不到能够处理这个数据的HttpMessageConverter,其中ByteArrayHttpMessageConverter能处理这种MediaType(使用get方式的http://localhost:8080/SpringMVC/test/testRb?name=kobe&age=39的MediaType是application/octet-stream),但是他只支持byte类型,而我们这里的参数是Player这种自定义类型。

没有任何的HttpMessageConverter可以处理,所以就导致了报错,本来按照程序应该是报如下错误。

    public HttpMediaTypeNotSupportedException(MediaType contentType, List<MediaType> supportedMediaTypes) {
        this(contentType, supportedMediaTypes, "Content type '" + contentType + "' not supported");
    }

但是实际结果却是http 400 bad request。这个我们需要再看看到底是什么原因。

到这里,就把第一个方法的参数处理过程分析完了。

那么需要如何更改才能使得不报错了,我们可以不使用application/octet-stream这中MediaType来传输数据了,我们只要把他改成application/json,使用json格式来传递输出就可以使用处理Json格式的Convert来处理数据了,而处理完参数之后的操作我们留到以后再分析。

ServletModelAttributeMethodProcessor

    @RequestMapping("testEntity")
    @ResponseBody
    public Player testEntity(Player player) {
        return player;
    }

我们接着看第二个方法,先还是在HandlerMethodArgumentResolverComposite的supportsParameter方法中筛选出适合的HandlerMethodArgumentResolver来处理。

得到的答案是ServletModelAttributeMethodProcessor

他的注册方式如下:

        resolvers.add(new ServletModelAttributeMethodProcessor(false));
        resolvers.add(new ServletModelAttributeMethodProcessor(true));

我们看看这个类先

    public ServletModelAttributeMethodProcessor(boolean annotationNotRequired) {
        super(annotationNotRequired);
    }
    //ModelAttributeMethodProcessor
        public ModelAttributeMethodProcessor(boolean annotationNotRequired) {
        this.annotationNotRequired = annotationNotRequired;
    }

初始化ServletModelAttributeMethodProcessor时,会调用父类ModelAttributeMethodProcessor的骨构造方法,而且supportsParameter也是在父类中,我们看看这个方法。

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        if (parameter.hasParameterAnnotation(ModelAttribute.class)) {
            return true;
        }
        else if (this.annotationNotRequired) {
            return !BeanUtils.isSimpleProperty(parameter.getParameterType());
        }
        else {
            return false;
        }
    }

如果参数是有@ModelAttribute注解的就支持,如果没有这个注解,当构造方法的实参是true时,如果Method的参数类型不是简单类型也支持,因为有一个实参为true的ServletModelAttributeMethodProcessor被注册,并且Method的参数类型是Player,不是简单类型,所以符合。

直接看resolveArgument方法,这个方法的实现还是在父类中

    @Override
    public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {

        String name = ModelFactory.getNameForParameter(parameter);
        Object attribute = (mavContainer.containsAttribute(name) ?
                mavContainer.getModel().get(name) : createAttribute(name, parameter, binderFactory, webRequest));

        WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
        if (binder.getTarget() != null) {
            bindRequestParameters(binder, webRequest);
            validateIfApplicable(binder, parameter);
            if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
                throw new BindException(binder.getBindingResult());
            }
        }

        // Add resolved attribute and BindingResult at the end of the model
        Map<String, Object> bindingResultModel = binder.getBindingResult().getModel();
        mavContainer.removeAttributes(bindingResultModel);
        mavContainer.addAllAttributes(bindingResultModel);

        return binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
    }

这个过程的具体实现先不分析了,以后有空再细说,主要就是

通过DataBinder实例化了Employee对象,并写入了对应的属性,最后把这个实例对象返回给我们。

RequestParamMethodArgumentResolver

@RequestMapping("testEntityWithRp")
    @ResponseBody
    public Player testEntityWithRp(@RequestParam Player player) {
        return player;
    }

还是一样,通过筛选,得到了对应的HandlerMethodArgumentResolver是RequestParamMethodArgumentResolver,对应的注册代码

        resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
        resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));

看看他的构造方法

    public RequestParamMethodArgumentResolver(ConfigurableBeanFactory beanFactory, boolean useDefaultResolution) {
        super(beanFactory);
        this.useDefaultResolution = useDefaultResolution;
    }
        public AbstractNamedValueMethodArgumentResolver(ConfigurableBeanFactory beanFactory) {
        this.configurableBeanFactory = beanFactory;
        this.expressionContext = (beanFactory != null ? new BeanExpressionContext(beanFactory, new RequestScope()) : null);
    }

实例化的时候调用了父类AbstractNamedValueMethodArgumentResolver的构造方法。

supportsParameter方法在本类中实现,resolveArgument在父类中实现。

先看supportsParameter

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        Class<?> paramType = parameter.getParameterType();
        if (parameter.hasParameterAnnotation(RequestParam.class)) {
            if (Map.class.isAssignableFrom(paramType)) {
                String paramName = parameter.getParameterAnnotation(RequestParam.class).value();
                return StringUtils.hasText(paramName);
            }
            else {
                return true;
            }
        }
        else {
            if (parameter.hasParameterAnnotation(RequestPart.class)) {
                return false;
            }
            else if (MultipartFile.class.equals(paramType) || "javax.servlet.http.Part".equals(paramType.getName())) {
                return true;
            }
            else if (this.useDefaultResolution) {
                return BeanUtils.isSimpleProperty(paramType);
            }
            else {
                return false;
            }
        }
    }

当Method参数有@RequestParam注解时,如果此时参数实现了Map接口的时候,@RequestParam注解需要具有value属性,否则不支持,如果有@RequestPart注解,不支持,如果参数是简单类型,支持。如果是MultipartFile类型,且是javax.servlet.http.Part,支持。

显然我们这个方法符合这个要求。我们接着看resolveArgument

@Override
    public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {

        Class<?> paramType = parameter.getParameterType();
        NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);

        Object arg = resolveName(namedValueInfo.name, parameter, webRequest);
        if (arg == null) {
            if (namedValueInfo.defaultValue != null) {
                arg = resolveDefaultValue(namedValueInfo.defaultValue);
            }
            else if (namedValueInfo.required && !parameter.getParameterType().getName().equals("java.util.Optional")) {
                handleMissingValue(namedValueInfo.name, parameter);
            }
            arg = handleNullValue(namedValueInfo.name, arg, paramType);
        }
        else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
            arg = resolveDefaultValue(namedValueInfo.defaultValue);
        }

        if (binderFactory != null) {
            WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
            arg = binder.convertIfNecessary(arg, paramType, parameter);
        }

        handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);

        return arg;
    }

仔细阅读源码,你会发现在处理参数的时候会使用request.getParameter(参数名)即request.getParameter(“player”)得到,很明显我们的参数传的是name=1&age=3。因此得到null,RequestParamMethodArgumentResolver处理missing value会触发MissingServletRequestParameterException异常。

那需要如何处理呢,很简单,去掉@RequestParam注解就好了,这样就给方法2一样了。

@InitBinder注解

我们继续看方法四

@RequestMapping("/testDate")
    @ResponseBody
    public Date testDate(Date date) {
        return date;
    }

我们用这样的链接http://localhost:8080/SpringMVC/test/testDate?date=2018-01-07去调用,会返回错误,400 bad request。为什么呢?来分析一下。

上面我们分析过RequestParamMethodArgumentResolver和ServletModelAttributeMethodProcessor,他们一个支持简单类型一个支持非简单类型,那么这里的Date类型到底是简单类型还是非简单类型呢,看下BeanUtils.isSimpleProperty(paramType);的源码就知道了,他属于简单类型,所以使用的是RequestParamMethodArgumentResolver。这时我们使用request.getParameter(“date”)得到了日期字符串,到这里还是一切正常的,但是后面的使用DataBinder找到合适的属性编辑器进行类型转换时,最终找到java.util.Date对象的构造函数 public Date(String s),而由于我们传递的格式不是标准的UTC时间格式,因此最终触发了IllegalArgumentException异常。

所以要解决这个问题的方法最简单就是把日期格式设置为标准的UTC时间格式,但是这样并不符合我们的日常习惯,我们肯定是想能够使用请求中的那种日期格式的,那我们能怎么办呢。其实要实现这个功能并不难。在TestController中添加如下代码

@InitBinder
public void initBinder(WebDataBinder binder) {
  SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
  binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
}

是的只要添加这几行代码就可以了。

那到底是为什么呢?
@InitBinder注解在实例化ServletInvocableHandlerMethod的时候被注入到WebDataBinderFactory中的,而WebDataBinderFactory是ServletInvocableHandlerMethod的一个属性。在RequestMappingHandlerAdapter的invokeHandleMethod方法中的getDataBinderFactory就是得到的WebDataBinderFactory。

我们来把目光转向getDataBinderFactory方法

private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception {
        Class<?> handlerType = handlerMethod.getBeanType();
        Set<Method> methods = this.initBinderCache.get(handlerType);
        if (methods == null) {
            methods = HandlerMethodSelector.selectMethods(handlerType, INIT_BINDER_METHODS);
            this.initBinderCache.put(handlerType, methods);
        }
        List<InvocableHandlerMethod> initBinderMethods = new ArrayList<InvocableHandlerMethod>();
        // Global methods first
        for (Entry<ControllerAdviceBean, Set<Method>> entry : this.initBinderAdviceCache .entrySet()) {
            if (entry.getKey().isApplicableToBeanType(handlerType)) {
                Object bean = entry.getKey().resolveBean();
                for (Method method : entry.getValue()) {
                    initBinderMethods.add(createInitBinderMethod(bean, method));
                }
            }
        }
        for (Method method : methods) {
            Object bean = handlerMethod.getBean();
            initBinderMethods.add(createInitBinderMethod(bean, method));
        }
        return createDataBinderFactory(initBinderMethods);
    }

这个方法就是筛选出有@InitBinder注解的方法,将其注入到DataBinderFactory中。

我们需要重点关注的就是

            methods = HandlerMethodSelector.selectMethods(handlerType, INIT_BINDER_METHODS);
    public static final MethodFilter INIT_BINDER_METHODS = new MethodFilter() {

        @Override
        public boolean matches(Method method) {
            return AnnotationUtils.findAnnotation(method, InitBinder.class) != null;
        }
    };

筛选出有@InitBinder注解的方法。

之后RequestParamMethodArgumentResolver通过WebDataBinderFactory创建的WebDataBinder里的自定义属性编辑器找到合适的属性编辑器(我们自定义的属性编辑器是用CustomDateEditor处理Date对象,而testDate的参数刚好是Date),最终CustomDateEditor把这个String对象转换成Date对象。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值