深入理解Spring系列之十一:SpringMVC-@RequestBody接收json数据报415

网上对这个问题的分析及解决不是很深入,大部分并不能解决问题,而且内容基本相同,拿来主义把内容放在自己的博客上!本篇将深入Spring源码来分析问题出现的根本原因。

问题原因

报错原因可能有两种情况:

1.请求头中没有设置Content-Type参数,或Content-Type参数值不是application/json;

2.请求头中正确设置了Content-Type参数及参数值,但是在项目jar依赖中(pom.xml或build.gradle)没有添加处理json字符串的处理类,如果SpringMVC框架在启动的时候,检查com.fasterxml.jackson.databind.ObjectMapper和com.fasterxml.jackson.core.JsonGenerator有一个不存在或不能加载,则不会注册MappingJackson2HttpMessageConverter,这个类使用Jackson将json请求参数转成相应的方法参数;同样检查com.google.gson.Gson,如果不存在或不能加载,则不会注册GsonHttpMessageConverter,这个类使用Gson将json请求参数转成相应的方法参数;如果依赖的Jackson和Gson都没有被添加或不能加载,则SpringMVC将找不到对应的参数处理类。

源码分析

在使用SpringMVC的时候,都会添加<mvc:annotation-driven />注解,这个注解下有很多可以配置的扩展参数,有兴趣的可以研究一下。有这个注解,就必定有对应的注解解析,查看NamespaceHandler接口的实现类,发现有一个MvcNamespaceHandler。

annotation-driven注解做了什么,直接看AnnotationDrivenBeanDefinitionParser类。这个类中主要的就是parse方法,这个方法中做了很多重要的事,如对一些可扩展的参数进行了解析注册,这些不是本篇的重点,有兴趣的可以研究一下,关注重点代码。

代码中的messageConverters是消息转换器集合,里面包含了对json、xml、atom、rss格式报文的转换。接着,把messageConverters添加到RequestMappingHandlerAdapter中,RequestMappingHandlerAdapter是处理@RequestMapping注解的HandlerAdapter,简单说就是标注了@RequestMapping注解的Controller,是经过RequestMappingHandlerAdapter进行调用的。messageConverters是它的一个属性,代码如下。

继续看AnnotationDrivenBeanDefinitionParser类,分析上图红框中的ManagedList<?> messageConverters = getMessageConverters(element, source, parserContext),深入这个getMessageConverters方法。

romePresent、jaxb2Present、jackson2Present、jackson2XmlPresent、gsonPresent为true则将对应的转换器包装成BeanDefinition,然后将其添加到messageConverters集合中。这几个布尔变量的值在AnnotationDrivenBeanDefinitionParser类的开头处就赋值了。

如果相应的实现类存在并且可以被加载,则对应的布尔变量值为true,否则为false。也就是说,如果SpringMVC框架在启动的时候,检查com.fasterxml.jackson.databind.ObjectMapper和com.fasterxml.jackson.core.JsonGenerator有一个不存在或不能加载,则不会注册MappingJackson2HttpMessageConverter,这个类使用Jackson将json请求参数转成相应的方法参数;同样检查com.google.gson.Gson,如果不存在或不能加载,则不会注册GsonHttpMessageConverter,这个类使用Gson将json请求参数转成相应的方法参数;如果依赖的Jackson和Gson都没有被添加或不能加载,则SpringMVC将找不到json参数转换类,也就没办法处理。

如果配置了json参数转换处理类,SpringMVC框架将根据请求头中的Content-Type参数遍历messageConverters,选择匹配的转换器类,进行参数转换。如果Content-Type参数值类型是messageConverters中不支持的,那么就没办法做转换。

总结

首先,SpringMVC框架在启动的时候会遍历Spring容器中的所有bean,对标注了@Controller或@RequestMapping注解的类中方法进行遍历,将类和方法上的@RequestMapping注解值进行合并,使用@RequestMapping注解的相关参数值(如value、method等)封装一个RequestMappingInfo,将这个Controller实例、方法及方法参数信息(类型、注解等)封装到HandlerMethod中,然后以RequestMappingInfo为key,HandlerMethod为value存到一个以Map为结构的handlerMethods中。

接着,将@RequestMapping注解中的value(即请求路径)值取出,即url,然后以url为key,以RequestMappingInfo为value,存到一个以Map为结构的urlMap属性中。

客户端发起请求的时候,根据请求的URL到urlMap中查找,找到RequestMappingInfo,然后根据RequestMappingInfo到handlerMethods中查找,找到对应的HandlerMethod,接着将HandlerMethod封装到HandlerExecutionChain;接着遍历容器中所有HandlerAdapter实现类,找到支持这次请求的HandlerAdapter,如RequestMappingHandlerAdapter,然后执行SpringMVC拦截器的前置方法(preHandle方法),然后对请求参数解析及转换,这里主要根据HandlerMethod中封装的参数信息(方法参数上的注解)来遍历argumentResolvers(List结构,存储了HandlerMethodArgumentResolver接口实现类,不同实现类,实现对不同注解参数的解析,如RequestResponseBodyMethodProcessor可以实现对@RequestBody和@ResponseBody参数的解析),找到支持这个注解的HandlerMethodArgumentResolver实现类,然后解析请求参数。

插播一下请求参数的解析及转换,下图是HandlerMethodArgumentResolver接口的实现类。

从上图中可以看到很多常见注解参数的解析类,这里分析RequestResponseBodyMethodProcessor,其它处理类感兴趣的可以自己研究一下。RequestResponseBodyMethodProcessor会从请求头中获取Content-Type参数值,例如application/json,然后遍历messageConverters,查找能够处理这种Content-Type的转换器类,如果messageConverters中有可以处理application/json请求的处理类,如Jackson或Gson,则使用Jackson或Gson对请求体中的参数进行读取转换,转换成具体方法参数类型,下面是Jackson具体的处理代码。

如果messageConverters没有匹配的处理类,那就会报415。

最后,(使用反射)调用具体Controller的对应方法返回一个ModelAndView对象,执行拦截器的后置方法(postHandle方法),然后对返回的结果进行处理,最后执行afterCompletion方法。

原文发布于微信公众号 - JavaQ(Java-Q)

原文发表时间:2017-10-09

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏java工会

Java 必看的 Spring 知识汇总!有比这更全的算我输!

2172
来自专栏Java技术栈

Java 必看的 Spring 知识汇总!

2233
来自专栏程序猿DD

【译】Spring 官方教程:创建批处理服务

原文:Creating a Batch Service 译者:Mr.lzc 校对:lexburner 本指南将引导你完成创建基本的批处理驱动解决方案的过程。 你...

5397
来自专栏Gaussic

Spring MVC绑定 List 对象参数 原

       最近做的一个小小的项目碰上了如何用 post 传递一整个 list 的问题,在解决这个问题的同时,也顺带升级一下 Spring 的版本,并精简一下...

1981
来自专栏coderhuo

虚拟内存探究 -- 第三篇:一步一步画虚拟内存图

这是虚拟内存系列文章的第三篇。 前面我们提到在进程的虚拟内存中可以找到哪些东西,以及在哪里去找。 本文我们将通过打印程序中不同元素内存地址的方式,一步一步细...

2334
来自专栏Java 技术分享

两篇 Spring 总结(一)

1363
来自专栏微服务生态

Flume-NG源码分析-整体结构及配置载入分析

终于开始Flume源码的分析研究工作了,我也是边学边和大家分享,内容上难免有不足之处,望大家见谅。

1374
来自专栏Java帮帮-微信公众号-技术文章全总结

Spring IOC源码解读

Spring IOC源码解读 此文讲解非常详细,内容较多,请耐心看完,谢谢 讲解内容: 一、 什么是Ioc/DI? 二、 Spring IOC体系结构 (1) ...

5275
来自专栏黑泽君的专栏

day33_Spring学习回顾_01

1、导入jar包:4 + 1 --> beans/core/context/expression + commons-logging

772
来自专栏aoho求索

Spring Cloud 覆写远端的配置属性

覆写远端的配置属性 应用的配置源通常都是远端的Config Server服务器,默认情况下,本地的配置优先级低于远端配置仓库。如果想实现本地应用的系统变量和c...

3919

扫码关注云+社区

领取腾讯云代金券