Spring MVC 源码笔记 三

作者: 丶含光 | 来源:发表于2019-12-09 00:23 被阅读0次

    上一篇记录了处理请求的过程,这篇记录对请求参数与返回参数的处理
    在前面分析《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.<RequestBodyAdvice>singletonList(new JsonViewRequestBodyAdvice()));
                adapter.setResponseBodyAdvice(
                        Collections.<ResponseBodyAdvice<?>>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<HttpMessageConverter<?>> getMessageConverters() {
            if (this.messageConverters == null) {
                this.messageConverters = new ArrayList<HttpMessageConverter<?>>();
                configureMessageConverters(this.messageConverters);
                if (this.messageConverters.isEmpty()) {
                    addDefaultHttpMessageConverters(this.messageConverters);
                }
                extendMessageConverters(this.messageConverters);
            }
            return this.messageConverters;
        }
    ...
        protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
            StringHttpMessageConverter stringConverter = new StringHttpMessageConverter();
            stringConverter.setWriteAcceptCharset(false);
    
            messageConverters.add(new ByteArrayHttpMessageConverter());
            messageConverters.add(stringConverter);
            messageConverters.add(new ResourceHttpMessageConverter());
            messageConverters.add(new SourceHttpMessageConverter<Source>());
            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<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);
            }
        }
    ...
        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(), 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 <T> 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 <T> 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<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);
            if (targetClass == null) {
                ResolvableType resolvableType = (parameter != null ?
                        ResolvableType.forMethodParameter(parameter) : ResolvableType.forType(targetType));
                targetClass = (Class<T>) resolvableType.resolve();
            }
    
            HttpMethod httpMethod = ((HttpRequest) inputMessage).getMethod();
            Object body = NO_VALUE;
    
            try {
                inputMessage = new EmptyBodyCheckingHttpInputMessage(inputMessage);
    
                for (HttpMessageConverter<?> converter : this.messageConverters) {
                    Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) 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<T>) 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将HttpInputMessage转化的流转化为处理请求的方法参数中指定的java类并返回。

    相关文章

      网友评论

        本文标题:Spring MVC 源码笔记 三

        本文链接:https://www.haomeiwen.com/subject/hgemgctx.html