Spring MVC 请求参数解析

上一篇记录了处理请求的过程,这篇记录对请求参数与返回参数的处理
在前面分析《Spring MVC 源码笔记 @EnableWebMvc》中,提到了完全配置类WebMvcConfigurationSupport,正是在这里声明的beanRequestMappingHandlerAdapter,创建了默认的消息转换器和方法参数解析器。

WebMvcConfigurationSupport
    @Bean
    public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
        RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
        adapter.setContentNegotiationManager(mvcContentNegotiationManager());
        adapter.setMessageConverters(getMessageConverters());
        adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer());
        adapter.setCustomArgumentResolvers(getArgumentResolvers());
        adapter.setCustomReturnValueHandlers(getReturnValueHandlers());

        if (jackson2Present) {
            adapter.setRequestBodyAdvice(
                    Collections.singletonList(new JsonViewRequestBodyAdvice()));
            adapter.setResponseBodyAdvice(
                    Collections.>singletonList(new JsonViewResponseBodyAdvice()));
        }

        AsyncSupportConfigurer configurer = new AsyncSupportConfigurer();
        configureAsyncSupport(configurer);
        if (configurer.getTaskExecutor() != null) {
            adapter.setTaskExecutor(configurer.getTaskExecutor());
        }
        if (configurer.getTimeout() != null) {
            adapter.setAsyncRequestTimeout(configurer.getTimeout());
        }
        adapter.setCallableInterceptors(configurer.getCallableInterceptors());
        adapter.setDeferredResultInterceptors(configurer.getDeferredResultInterceptors());

        return adapter;
    }

先看这里的getMessageConverters()

WebMvcConfigurationSupport
    protected final List> getMessageConverters() {
        if (this.messageConverters == null) {
            this.messageConverters = new ArrayList>();
            configureMessageConverters(this.messageConverters);
            if (this.messageConverters.isEmpty()) {
                addDefaultHttpMessageConverters(this.messageConverters);
            }
            extendMessageConverters(this.messageConverters);
        }
        return this.messageConverters;
    }
...
    protected final void addDefaultHttpMessageConverters(List> messageConverters) {
        StringHttpMessageConverter stringConverter = new StringHttpMessageConverter();
        stringConverter.setWriteAcceptCharset(false);

        messageConverters.add(new ByteArrayHttpMessageConverter());
        messageConverters.add(stringConverter);
        messageConverters.add(new ResourceHttpMessageConverter());
        messageConverters.add(new SourceHttpMessageConverter());
        messageConverters.add(new AllEncompassingFormHttpMessageConverter());

        if (romePresent) {
            messageConverters.add(new AtomFeedHttpMessageConverter());
            messageConverters.add(new RssChannelHttpMessageConverter());
        }

        if (jackson2XmlPresent) {
            ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.xml().applicationContext(this.applicationContext).build();
            messageConverters.add(new MappingJackson2XmlHttpMessageConverter(objectMapper));
        }
        else if (jaxb2Present) {
            messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
        }

        if (jackson2Present) {
            ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().applicationContext(this.applicationContext).build();
            messageConverters.add(new MappingJackson2HttpMessageConverter(objectMapper));
        }
        else if (gsonPresent) {
            messageConverters.add(new GsonHttpMessageConverter());
        }
    }

可以看到,addDefaultHttpMessageConverters内添加了默认的消息转换器。
RequestMappingHandlerAdapter也实现了接口InitializingBean,所以关注其afterPropertiesSet方法

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

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

        // 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(), this.requestResponseBodyAdvice));
        resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
        resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
        resolvers.add(new RequestHeaderMapMethodArgumentResolver());
        resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
        resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
        resolvers.add(new SessionAttributeMethodArgumentResolver());
        resolvers.add(new RequestAttributeMethodArgumentResolver());

        // Type-based argument resolution
        resolvers.add(new ServletRequestMethodArgumentResolver());
        resolvers.add(new ServletResponseMethodArgumentResolver());
        resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
        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;
    }

可以看到,在getDefaultArgumentResolvers方法内,创建了多个方法参数解析器,(其中RequestParamMethodArgumentResolver添加了两次,不同之处为最后添加的为默认的参数解析器)最后在HandlerMethodArgumentResolverCompositeaddResolvers中将创建的方法参数解析器保存在其argumentResolvers变量上。

  • 记录完方法参数解析器的初始化,直接来到执行方法的片段。
    方法的调用在InvocableHandlerMethodinvokeForRequest
InvocableHandlerMethod
    public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer,
            Object... providedArgs) throws Exception {

        Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
        if (logger.isTraceEnabled()) {
            logger.trace("Invoking '" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) +
                    "' with arguments " + Arrays.toString(args));
        }
        Object returnValue = doInvoke(args);
        if (logger.isTraceEnabled()) {
            logger.trace("Method [" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) +
                    "] returned [" + returnValue + "]");
        }
        return returnValue;
    }

其中getMethodArgumentValues方法用来获取请求参数

InvocableHandlerMethod
    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);
            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.isDebugEnabled()) {
                        logger.debug(getArgumentResolutionErrorMessage("Failed to resolve", i), ex);
                    }
                    throw ex;
                }
            }
            if (args[i] == null) {
                throw new IllegalStateException("Could not resolve method parameter at index " +
                        parameter.getParameterIndex() + " in " + parameter.getMethod().toGenericString() +
                        ": " + getArgumentResolutionErrorMessage("No suitable resolver for", i));
            }
        }
        return args;
    }

getMethodParameters方法是InvocableHandlerMethodHandlerMethod继承而来

HandlerMethod
    public MethodParameter[] getMethodParameters() {
        return this.parameters;
    }

而关于parameters的初始化是在RequestMappingHandlerMappingafterPropertiesSet方法执行时调用,只是简单的封装方法参数,不需要太过关注。parameters数组的长度为方法的参数个数。
this.argumentResolversInvocableHandlerMethod默认创建的HandlerMethodArgumentResolverComposite,关注其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;
    }

这里的this.argumentResolvers为内置的方法参数解析器,最开始已经说明。展示几个常用的方法参数解析器

RequestParamMethodArgumentResolver
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        if (parameter.hasParameterAnnotation(RequestParam.class)) {
            if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
                String paramName = parameter.getParameterAnnotation(RequestParam.class).name();
                return StringUtils.hasText(paramName);
            }
            else {
                return true;
            }
        }
        else {
            if (parameter.hasParameterAnnotation(RequestPart.class)) {
                return false;
            }
            parameter = parameter.nestedIfOptional();
            if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
                return true;
            }
            else if (this.useDefaultResolution) {
                return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
            }
            else {
                return false;
            }
        }
    }

第一个是RequestParamMethodArgumentResolver,可以看到,如果controller的method的参数列表有使用@RequestParam注解,将会使用RequestParamMethodArgumentResolver来解析参数。如果找不到合适的处理参数的解析器,也是用此解析器来处理,因为在内置的方法参数解析器中有一个RequestParamMethodArgumentResolverthis.useDefaultResolution为true,表示默认。

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

第二个是RequestResponseBodyMethodProcessor,可以看到是判断方法上有没有@RequestBody注解。
回到InvocableHandlerMethodgetMethodArgumentValues方法中,如果参数解析HandlerMethodArgumentResolverComposite内的方法参数解析器能解析当前参数,会调用其resolveArgument方法

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

        HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
        if (resolver == null) {
            throw new IllegalArgumentException("Unknown parameter type [" + parameter.getParameterType().getName() + "]");
        }
        return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
    }

可以看到实际调用了选中的方法参数解析器的resolveArgument方法。这里选用RequestResponseBodyMethodProcessor来看下

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

        parameter = parameter.nestedIfOptional();
        Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
        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 adaptArgumentIfNecessary(arg, parameter);
    }
...
    @Override
    protected  Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter,
            Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {

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

        Object arg = readWithMessageConverters(inputMessage, parameter, paramType);
        if (arg == null) {
            if (checkRequired(parameter)) {
                throw new HttpMessageNotReadableException("Required request body is missing: " +
                        parameter.getMethod().toGenericString());
            }
        }
        return arg;
    }
...

关注readWithMessageConverters方法,此方法在其基类AbstractMessageConverterMethodArgumentResolver

AbstractMessageConverterMethodArgumentResolver
    protected  Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
            Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {

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

        Class contextClass = (parameter != null ? parameter.getContainingClass() : null);
        Class targetClass = (targetType instanceof Class ? (Class) targetType : null);
        if (targetClass == null) {
            ResolvableType resolvableType = (parameter != null ?
                    ResolvableType.forMethodParameter(parameter) : ResolvableType.forType(targetType));
            targetClass = (Class) resolvableType.resolve();
        }

        HttpMethod httpMethod = ((HttpRequest) inputMessage).getMethod();
        Object body = NO_VALUE;

        try {
            inputMessage = new EmptyBodyCheckingHttpInputMessage(inputMessage);

            for (HttpMessageConverter converter : this.messageConverters) {
                Class> converterType = (Class>) converter.getClass();
                if (converter instanceof GenericHttpMessageConverter) {
                    GenericHttpMessageConverter genericConverter = (GenericHttpMessageConverter) converter;
                    if (genericConverter.canRead(targetType, contextClass, contentType)) {
                        if (logger.isDebugEnabled()) {
                            logger.debug("Read [" + targetType + "] as \"" + contentType + "\" with [" + converter + "]");
                        }
                        if (inputMessage.getBody() != null) {
                            inputMessage = getAdvice().beforeBodyRead(inputMessage, parameter, targetType, converterType);
                            body = genericConverter.read(targetType, contextClass, inputMessage);
                            body = getAdvice().afterBodyRead(body, inputMessage, parameter, targetType, converterType);
                        }
                        else {
                            body = getAdvice().handleEmptyBody(null, inputMessage, parameter, targetType, converterType);
                        }
                        break;
                    }
                }
                else if (targetClass != null) {
                    if (converter.canRead(targetClass, contentType)) {
                        if (logger.isDebugEnabled()) {
                            logger.debug("Read [" + targetType + "] as \"" + contentType + "\" with [" + converter + "]");
                        }
                        if (inputMessage.getBody() != null) {
                            inputMessage = getAdvice().beforeBodyRead(inputMessage, parameter, targetType, converterType);
                            body = ((HttpMessageConverter) converter).read(targetClass, inputMessage);
                            body = getAdvice().afterBodyRead(body, inputMessage, parameter, targetType, converterType);
                        }
                        else {
                            body = getAdvice().handleEmptyBody(null, inputMessage, parameter, targetType, converterType);
                        }
                        break;
                    }
                }
            }
        }
        catch (IOException ex) {
            throw new HttpMessageNotReadableException("Could not read document: " + ex.getMessage(), ex);
        }

        if (body == NO_VALUE) {
            if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) ||
                    (noContentType && inputMessage.getBody() == null)) {
                return null;
            }
            throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes);
        }

        return body;
    }

既然选择了RequestResponseBodyMethodProcessor@RequestBody注解,那么传入的参数一定为json数据。for循环遍历的消息转换器this.messageConverters在开始已经记录过,当遍历到GsonHttpMessageConverter时,它是GenericHttpMessageConverter的间接子类,通过instanceof判断后,关注GsonHttpMessageConverter的父类AbstractGenericHttpMessageConvertercanRead方法

AbstractGenericHttpMessageConverter
    @Override
    public boolean canRead(Type type, Class contextClass, MediaType mediaType) {
        return (type instanceof Class ? canRead((Class) type, mediaType) : canRead(mediaType));
    }

再到父类AbstractHttpMessageConvertercanRead

AbstractHttpMessageConverter
    @Override
    public boolean canRead(Class clazz, MediaType mediaType) {
        return supports(clazz) && canRead(mediaType);
    }
...
    protected boolean canRead(MediaType mediaType) {
        if (mediaType == null) {
            return true;
        }
        for (MediaType supportedMediaType : getSupportedMediaTypes()) {
            if (supportedMediaType.includes(mediaType)) {
                return true;
            }
        }
        return false;
    }

传入的mediaType为发起请求时指定的'Content-TypegetSupportedMediaTypes返回的是当前消息转换器支持的MediaType,对于GsonHttpMessageConverter来说,它是支持此Content-Type的,这在它的构造器中很容易看到。

GsonHttpMessageConverter
    public GsonHttpMessageConverter() {
        super(MediaType.APPLICATION_JSON, new MediaType("application", "*+json"));
        this.setDefaultCharset(DEFAULT_CHARSET);
    }

选中消息转换器后,开始执行其read方法

GsonHttpMessageConverter
    @Override
    @SuppressWarnings("deprecation")
    public Object read(Type type, Class contextClass, HttpInputMessage inputMessage)
            throws IOException, HttpMessageNotReadableException {

        TypeToken token = getTypeToken(type);
        return readTypeToken(token, inputMessage);
    }
...
    private Object readTypeToken(TypeToken token, HttpInputMessage inputMessage) throws IOException {
        Reader json = new InputStreamReader(inputMessage.getBody(), getCharset(inputMessage.getHeaders()));
        try {
            return this.gson.fromJson(json, token.getType());
        }
        catch (JsonParseException ex) {
            throw new HttpMessageNotReadableException("Could not read JSON document: " + ex.getMessage(), ex);
        }
    }

type为当前处理请求的方法的参数类型,TypeToken是谷歌json包中的类,用来封装参数类型。在readTypeToken方法中,可以看到使用this.gson.fromJson将HttpInputMessage转化的流转化为java类的过程。(这里就是对controller请求的json格式的参数,是怎么被SpringMVC封装到我们指定的model里的过程)
重新回到AbstractMessageConverterMethodArgumentResolverreadWithMessageConverters方法,可以看到在消息转换器执行read动作前后,执行了RequestResponseBodyAdviceChain的环绕方法。
回到RequestResponseBodyMethodProcessorresolveArgument方法,再回到InvocableHandlerMethodgetMethodArgumentValues方法中,方法参数解析完成。

总结:

  • WebMvcConfigurationSupport配置的RequestMappingHandlerAdapter负责初始化消息转换器和方法参数解析器
  • HandlerMethodArgumentResolverComposite内遍历方法参数解析器以选中能处理当前对应的方法的参数解析器。
  • 这里挑选了处理json格式参数的解析器RequestResponseBodyMethodProcessor
  • RequestResponseBodyMethodProcessor的间接父类AbstractMessageConverterMethodArgumentResolver中选出了处理json格式参数的消息转换器GsonHttpMessageConverter
  • 在消息转换器GsonHttpMessageConverter中使用谷歌的gson(默认jackson,这里因为引了gson的jar所以被替代)将HttpInputMessage转化的流转化为处理请求的方法参数中指定的java类并返回。

你可能感兴趣的:(Spring MVC 请求参数解析)