1. 程式人生 > >SpringMVC請求處理之對方法引數的處理

SpringMVC請求處理之對方法引數的處理

前言

講完了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;
    }

上面我們分析過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物件。