我们在使用springboot开发请求接口时,有时会使用注解进行body请求参数映射,在一次愉快的coding中,我误将映射实JSONObject(fastjson工具类)写成JsonObjcet(Gson工具类),导致映射数据为空对象,排查了好久(为低级错误买单),于是考虑从源码层分析这两者的差距。
事件还原:定义mvc接口,使用JSONObject获取参数,请求body参数示例:{"start": "hello"},应用响应:body参数:{"start":"hello"}:
@RequestMapping("/bodyTest")
@ResponseBody
public void requestBodyTest(@RequestBody JSONObject object) {
System.out.println("body参数:" + JSON.toJSONString(object));
}
此时将参数类型JSONObject误写为JsonObject,此时传入参数:{"start": "hello"},应用响应:body参数:{}
@RequestMapping("/bodyTest")
@ResponseBody
public void requestBodyTest(@RequestBody JsonObject object) {
System.out.println("body参数:" + JSON.toJSONString(object));
}
为什么看似用了两个差不多工具类,都是序列化框架自带的,映射结果却完全不同,这个需要进行研究,得到相似结果不同,要搞清楚这个逻辑需要分析服务层对映射的实现。

图1-1 springboot请求流程
1 spring请求解析
springboot通过@RequestBody注解来标识对body请求参数的映射,请求基于springMVC,请求链路如下:
org.springframework.web.servlet.DispatcherServlet#doService
org.springframework.web.servlet.DispatcherServlet#doDispatch
org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter#handle
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#handleInternal
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#invokeHandlerMethod
org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle
org.springframework.web.method.support.InvocableHandlerMethod#invokeForRequest
org.springframework.web.method.support.InvocableHandlerMethod#getMethodArgumentValues
org.springframework.web.method.support.HandlerMethodArgumentResolverComposite#resolveArgument
org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver#readWithMessageConverters(org.springframework.http.HttpInputMessage, org.springframework.core.MethodParameter, java.lang.reflect.Type)
经过分析发现底层是通过AbstractMessageConverterMethodArgumentResolver#readWithMessageConverters方法进行获取参数,包括两个方法,通过genericConverter.read实现参数获取,内部通过jackson序列化框架进行处理:
for (HttpMessageConverter<?> converter : this.messageConverters) {
Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
GenericHttpMessageConverter<?> genericConverter =
(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
(targetClass != null && converter.canRead(targetClass, contentType))) {
if (message.hasBody()) {
HttpInputMessage msgToUse =
getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
}
else {
body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
}
break;
}
}
2 反序列化解析
追加断点,继续跟踪read方法内部调用时序。
2.1 JSONObject解析时序
org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter#readJavaType
com.fasterxml.jackson.databind.ObjectMapper#readValue(java.io.InputStream, com.fasterxml.jackson.databind.JavaType)
com.fasterxml.jackson.databind.ObjectMapper#_readMapAndClose
com.fasterxml.jackson.databind.deser.BeanDeserializer#deserialize(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.DeserializationContext)
com.fasterxml.jackson.databind.deser.BeanDeserializer#deserializeFromObject
2.2 JsonObject解析时序
org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter#readJavaType
com.fasterxml.jackson.databind.ObjectMapper#readValue(java.io.InputStream, com.fasterxml.jackson.databind.JavaType)
com.fasterxml.jackson.databind.ObjectMapper#_readMapAndClose
com.fasterxml.jackson.databind.deser.std.MapDeserializer#deserialize(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.DeserializationContext)
com.fasterxml.jackson.databind.deser.std.MapDeserializer#_readAndBindStringKeyMap
通过时序分析,可以发现,jackson在对处理不同类时使用了不同的Json反序列化器(JsonDeserializer),对JSONObject使用的是MapDeserializer,而对JsonObject使用的是BeanDeserializer。
3 获取反序列化器
继续对上面jackson源码ObjectMapper类的readMapAndClose方法进行分析,发现它是通过findRootDeserializer方法进行获取反序列化器的:
protected Object _readMapAndClose(JsonParser p0, JavaType valueType)
throws IOException
{
try (JsonParser p = p0) {
Object result;
JsonToken t = _initForReading(p, valueType);
final DeserializationConfig cfg = getDeserializationConfig();
final DeserializationContext ctxt = createDeserializationContext(p, cfg);
if (t == JsonToken.VALUE_NULL) {
// Ask JsonDeserializer what 'null value' to use:
result = _findRootDeserializer(ctxt, valueType).getNullValue(ctxt);
} else if (t == JsonToken.END_ARRAY || t == JsonToken.END_OBJECT) {
result = null;
} else {
JsonDeserializer<Object> deser = _findRootDeserializer(ctxt, valueType);
if (cfg.useRootWrapping()) {
result = _unwrapAndDeserialize(p, ctxt, cfg, valueType, deser);
} else {
result = deser.deserialize(p, ctxt);
}
ctxt.checkUnresolvedObjectId();
}
if (cfg.isEnabled(DeserializationFeature.FAIL_ON_TRAILING_TOKENS)) {
_verifyNoTrailingTokens(p, ctxt, valueType);
}
return result;
}
}
进入内部可以进一步发现,在ObjectMapper类的内部维护了一个ConcurrentHashMap,作为存储反序列化工具的集合,使用时从该工具池中根据类型获取。
final protected ConcurrentHashMap<JavaType, JsonDeserializer<Object>> _rootDeserializers
= new ConcurrentHashMap<JavaType, JsonDeserializer<Object>>(64, 0.6f, 2);
protected JsonDeserializer<Object> _findRootDeserializer(DeserializationContext ctxt,
JavaType valueType)
throws JsonMappingException
{
// First: have we already seen it?
JsonDeserializer<Object> deser = _rootDeserializers.get(valueType);
if (deser != null) {
return deser;
}
// Nope: need to ask provider to resolve it
deser = ctxt.findRootValueDeserializer(valueType);
if (deser == null) { // can this happen?
return ctxt.reportBadDefinition(valueType,
"Cannot find a deserializer for type "+valueType);
}
_rootDeserializers.put(valueType, deser);
return deser;
}
4 初始化反序列化工具池
上面讲的是从反序列化工具池获取反序列化器,那么反序列化工具池中的数据来源在哪呢,还是得从第一步spring源码中寻找。通过断点和分析,可以发现在readWithMessageConverters中的genericConverter.read执行反序列化之前,还有一步genericConverter.canRead的逻辑判断。

图4-1 readWithMessageConverters方法部分截取
canRead方法的内部调用链路如下:
org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter#canRead(java.lang.reflect.Type, java.lang.Class<?>, org.springframework.http.MediaType)
com.fasterxml.jackson.databind.ObjectMapper#canDeserialize(com.fasterxml.jackson.databind.JavaType, java.util.concurrent.atomic.AtomicReference<java.lang.Throwable>)
com.fasterxml.jackson.databind.ObjectMapper#createDeserializationContext
com.fasterxml.jackson.databind.deser.DeserializerCache#_createAndCacheValueDeserializer
com.fasterxml.jackson.databind.deser.DeserializerCache#_createAndCache2
com.fasterxml.jackson.databind.deser.DeserializerCache#_createDeserializer
com.fasterxml.jackson.databind.deser.DeserializerCache#_createDeserializer2
其中_createDeserializer2方法,根据JavaType的类型来创建不同的反序列器,里面有一个type.isContainerType()判断,可以做部分反序列化器的生成规则分划。
protected JsonDeserializer<?> _createDeserializer2(DeserializationContext ctxt,
DeserializerFactory factory, JavaType type, BeanDescription beanDesc)
throws JsonMappingException
{
final DeserializationConfig config = ctxt.getConfig();
// If not, let's see which factory method to use:
if (type.isEnumType()) {
return factory.createEnumDeserializer(ctxt, type, beanDesc);
}
if (type.isContainerType()) {
if (type.isArrayType()) {
return factory.createArrayDeserializer(ctxt, (ArrayType) type, beanDesc);
}
if (type.isMapLikeType()) {
// 11-Mar-2017, tatu: As per [databind#1554], also need to block
// handling as Map if overriden with "as POJO" option.
// Ideally we'd determine it bit later on (to allow custom handler checks)
// but that won't work for other reasons. So do it here.
// (read: rewrite for 3.0)
JsonFormat.Value format = beanDesc.findExpectedFormat(null);
if ((format == null) || format.getShape() != JsonFormat.Shape.OBJECT) {
MapLikeType mlt = (MapLikeType) type;
if (mlt.isTrueMapType()) {
return factory.createMapDeserializer(ctxt,(MapType) mlt, beanDesc);
}
return factory.createMapLikeDeserializer(ctxt, mlt, beanDesc);
}
}
if (type.isCollectionLikeType()) {
/* 03-Aug-2012, tatu: As per [databind#40], one exception is if shape
* is to be Shape.OBJECT. Ideally we'd determine it bit later on
* (to allow custom handler checks), but that won't work for other
* reasons. So do it here.
*/
JsonFormat.Value format = beanDesc.findExpectedFormat(null);
if ((format == null) || format.getShape() != JsonFormat.Shape.OBJECT) {
CollectionLikeType clt = (CollectionLikeType) type;
if (clt.isTrueCollectionType()) {
return factory.createCollectionDeserializer(ctxt, (CollectionType) clt, beanDesc);
}
return factory.createCollectionLikeDeserializer(ctxt, clt, beanDesc);
}
}
}
if (type.isReferenceType()) {
return factory.createReferenceDeserializer(ctxt, (ReferenceType) type, beanDesc);
}
if (JsonNode.class.isAssignableFrom(type.getRawClass())) {
return factory.createTreeDeserializer(config, type, beanDesc);
}
return factory.createBeanDeserializer(ctxt, type, beanDesc);
}
通过断点调试,可以发现JSONObject类对应的JavaType的子类是MapType,而JsonObject类对应的JavaType的子类是SimpleType,这两个类对isContainerType()方法的重载实现分别是:
MapType(继承了MapLikeType类)方法的实现:
@Override
public boolean isContainerType() {
return true;
}
SimpleType方法的实现:
@Override
public boolean isContainerType() { return false; }
结合上述代码分析判断可得,JSONObject(对应MapType)执行createMapDeserializer方法,生成MapDeserializer,而JsonObject(对应SimpleType)执行createBeanDeserializer方法,生成BeanDeserializer,谜团终于被揭开。
5 总结与思考
JSONObject继承自Map类,所以jackson为它配备MapDeserializer的反序列化器进行解析,我们把JSONObject换成HashMap,效果是相同的,请求body参数示例:{"start": "hello"},应用响应:body参数:{"start":"hello"}:
@RequestMapping("/bodyTest")
@ResponseBody
public void requestBodyTest(@RequestBody HashMap object) {
System.out.println("body参数:" + JSON.toJSONString(object));
}
JsonObject没有继承Map类,jackson为它配置了BeanDeserializer解析器,但是它没有属性名为“start”的field成员,我们把它换成一个含有“start”属性的类即可,如Param类,此时传入参数:{"start": "hello"},应用响应:body参数:{"start":"hello"}。
public class Param {
private String start;
public String getStart() {
return start;
}
public void setStart(String start) {
this.start = start;
}
}
@RequestMapping("/bodyTest")
@ResponseBody
public void requestBodyTest(@RequestBody Param object) {
System.out.println("body参数:" + JSON.toJSONString(object));
}
以上就是本次分析springmvc对body请求映射的处理过程的全部内容,主要通过对servlet层、springmvc层、反序列工具层进行的代码阅读和理解,来解释实际使用中遇到的一些坑或困惑,受益很大,便记录下来,以作学习和分享。




