spring boot 是如何利用jackson进行反序列化的?
以下面的代码为例:
@RestController public class HelloController { @RequestMapping("/") public BillSearch hello(@RequestBody BillSearch search) { return search; } }
前端通过Postman进行模拟:
下面开始一步步的揭开它的面纱:
先从HandlerMethodArgumentResolverComposite开始:
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter); if (resolver == null) { throw new IllegalArgumentException( "Unsupported parameter type [" + parameter.getParameterType().getName() + "]." + " supportsParameter should be called first."); } return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory); }
resolver为RequestResponseBodyMethodProcessor 这个类是序列化和反序列化常用到的类。下面是它的resolveArgument方法:
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { parameter = parameter.nestedIfOptional(); Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType()); String name = Conventions.getVariableNameForParameter(parameter); if (binderFactory != null) { 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()); } } if (mavContainer != null) { mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult()); } } return adaptArgumentIfNecessary(arg, parameter); }
readWithMessageConverters方法如下:
protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter, Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException { HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class); Assert.state(servletRequest != null, "No HttpServletRequest"); ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(servletRequest); Object arg = readWithMessageConverters(inputMessage, parameter, paramType); if (arg == null && checkRequired(parameter)) { throw new HttpMessageNotReadableException("Required request body is missing: " + parameter.getExecutable().toGenericString(), inputMessage); } return arg; }
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.getContainingClass(); Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null); if (targetClass == null) { ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter); targetClass = (Class<T>) resolvableType.resolve(); } HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null); Object body = NO_VALUE; EmptyBodyCheckingHttpInputMessage message; try { message = new EmptyBodyCheckingHttpInputMessage(inputMessage); 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; } } } catch (IOException ex) { throw new HttpMessageNotReadableException("I/O error while reading input message", ex, inputMessage); } if (body == NO_VALUE) { if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) || (noContentType && !message.hasBody())) { return null; } throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes); } MediaType selectedContentType = contentType; Object theBody = body; LogFormatUtils.traceDebug(logger, traceOn -> { String formatted = LogFormatUtils.formatValue(theBody, !traceOn); return "Read \"" + selectedContentType + "\" to [" + formatted + "]"; }); return body; }
上一篇博客里介绍了messageConverters,在项目启动时添加了MappingJackson2HttpMessageConverter,这里主是就是找到这个converter对参数进行解析:
再进一步追踪:在AbstractJackson2HttpMessageConverter类中,就找到了我们要找到objectMapper:
private Object readJavaType(JavaType javaType, HttpInputMessage inputMessage) throws IOException { try { if (inputMessage instanceof MappingJacksonInputMessage) { Class<?> deserializationView = ((MappingJacksonInputMessage) inputMessage).getDeserializationView(); if (deserializationView != null) { return this.objectMapper.readerWithView(deserializationView).forType(javaType). readValue(inputMessage.getBody()); } } return this.objectMapper.readValue(inputMessage.getBody(), javaType); } catch (InvalidDefinitionException ex) { throw new HttpMessageConversionException("Type definition error: " + ex.getType(), ex); } catch (JsonProcessingException ex) { throw new HttpMessageNotReadableException("JSON parse error: " + ex.getOriginalMessage(), ex, inputMessage); } }
顺便再介绍一下objectMapper反序列化的主要步骤:
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 {
//com.fasterxml.jackson.databind.deser.BeanDeserializer 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; } }
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { // common case first if (p.isExpectedStartObjectToken()) { if (_vanillaProcessing) { return vanillaDeserialize(p, ctxt, p.nextToken()); } // 23-Sep-2015, tatu: This is wrong at some many levels, but for now... it is // what it is, including "expected behavior". p.nextToken(); if (_objectIdReader != null) { return deserializeWithObjectId(p, ctxt); } return deserializeFromObject(p, ctxt); } return _deserializeOther(p, ctxt, p.getCurrentToken()); }
if (p.hasTokenId(JsonTokenId.ID_FIELD_NAME)) { String propName = p.getCurrentName(); do { p.nextToken();
//根据获取属性名,获取这个属性 SettableBeanProperty prop = _beanProperties.find(propName); if (prop != null) { // normal case try { prop.deserializeAndSet(p, ctxt, bean); } catch (Exception e) { wrapAndThrow(e, bean, propName, ctxt); } continue; } handleUnknownVanilla(p, ctxt, bean, propName); } while ((propName = p.nextFieldName()) != null); } return bean;
针对这个属性进行反序列化解析,由于这个属性是个枚举,所以它的_valueDeserializer是com.fasterxml.jackson.databind.deser.std.EnumDeserializer
public void deserializeAndSet(JsonParser p, DeserializationContext ctxt, Object instance) throws IOException { Object value; if (p.hasToken(JsonToken.VALUE_NULL)) { if (_skipNulls) { return; } value = _nullProvider.getNullValue(ctxt); } else if (_valueTypeDeserializer == null) {
//com.fasterxml.jackson.databind.deser.std.EnumDeserializer value = _valueDeserializer.deserialize(p, ctxt); // 04-May-2018, tatu: [databind#2023] Coercion from String (mostly) can give null if (value == null) { if (_skipNulls) { return; } value = _nullProvider.getNullValue(ctxt); } } else { value = _valueDeserializer.deserializeWithType(p, ctxt, _valueTypeDeserializer); } try { _setter.invoke(instance, value); } catch (Exception e) { _throwAsIOE(p, e, value); } }
那为什么jackson枚举的反序列化默认用的是EnumDeserializer呢?
这要回到文章开始的地方说起:在一步中会判断指定的类型是否能够进行canRead()
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; } }
就从canRead()方法说起:
public boolean canRead(Type type, @Nullable Class<?> contextClass, @Nullable MediaType mediaType) { if (!canRead(mediaType)) { return false; } JavaType javaType = getJavaType(type, contextClass); AtomicReference<Throwable> causeRef = new AtomicReference<>(); if (this.objectMapper.canDeserialize(javaType, causeRef)) { return true; } logWarningIfNecessary(javaType, causeRef.get()); return false; }
public boolean canDeserialize(JavaType type, AtomicReference<Throwable> cause) { return createDeserializationContext(null, getDeserializationConfig()).hasValueDeserializerFor(type, cause); }
public boolean hasValueDeserializerFor(JavaType type, AtomicReference<Throwable> cause) { try { return _cache.hasValueDeserializerFor(this, _factory, type); } ... }
public boolean hasValueDeserializerFor(DeserializationContext ctxt, DeserializerFactory factory, JavaType type) throws JsonMappingException { /* Note: mostly copied from findValueDeserializer, except for * handling of unknown types */ JsonDeserializer<Object> deser = _findCachedDeserializer(type); if (deser == null) { deser = _createAndCacheValueDeserializer(ctxt, factory, type); } return (deser != null); }
注意这个名称createAndCache它是会缓存的,也就是这个类型只会找一次,找到之后,就它的反序列化类就绑定了,缓存起来了,
这们有时配置 jackson的objectMapper,可能会添加很多反序列化的模块,都会注册到_factoryConfig.deserializers()
protected JsonDeserializer<Object> _findCustomBeanDeserializer(JavaType type, DeserializationConfig config, BeanDescription beanDesc) throws JsonMappingException { for (Deserializers d : _factoryConfig.deserializers()) { JsonDeserializer<?> deser = d.findBeanDeserializer(type, config, beanDesc); if (deser != null) { return (JsonDeserializer<Object>) deser; } } return null; }
private final JsonDeserializer<?> _find(JavaType type) { if (_classMappings == null) { return null; } return _classMappings.get(new ClassKey(type.getRawClass())); }
每个模块都有_classMappings这样的集合,记录着类与它的序列化类的对应关系。由于我们没有定义这样的关系,那它就有一个默认的。
再找到这个类的反序列化类以后,再开始找类里面的属性的:
同样是在DeserializerCache类中的_createAndCache2方法:
protected JsonDeserializer<Object> _createAndCache2(DeserializationContext ctxt, DeserializerFactory factory, JavaType type) throws JsonMappingException { JsonDeserializer<Object> deser; try { deser = _createDeserializer(ctxt, factory, type); } catch (IllegalArgumentException iae) { // We better only expose checked exceptions, since those // are what caller is expected to handle throw JsonMappingException.from(ctxt, ClassUtil.exceptionMessage(iae), iae); } if (deser == null) { return null; } /* cache resulting deserializer? always true for "plain" BeanDeserializer * (but can be re-defined for sub-classes by using @JsonCachable!) */ // 27-Mar-2015, tatu: As per [databind#735], avoid caching types with custom value desers boolean addToCache = !_hasCustomHandlers(type) && deser.isCachable(); /* we will temporarily hold on to all created deserializers (to * handle cyclic references, and possibly reuse non-cached * deserializers (list, map)) */ /* 07-Jun-2010, tatu: Danger: [JACKSON-296] was caused by accidental * resolution of a reference -- couple of ways to prevent this; * either not add Lists or Maps, or clear references eagerly. * Let's actually do both; since both seem reasonable. */ /* Need to resolve? Mostly done for bean deserializers; required for * resolving cyclic references. */ if (deser instanceof ResolvableDeserializer) { _incompleteDeserializers.put(type, deser); ((ResolvableDeserializer)deser).resolve(ctxt); _incompleteDeserializers.remove(type); } if (addToCache) { _cachedDeserializers.put(type, deser); } return deser; }
遍历每一个属性:
for (SettableBeanProperty prop : _beanProperties) { if (!prop.hasValueDeserializer()) { // [databind#125]: allow use of converters JsonDeserializer<?> deser = findConvertingDeserializer(ctxt, prop); if (deser == null) { deser = ctxt.findNonContextualValueDeserializer(prop.getType()); } SettableBeanProperty newProp = prop.withValueDeserializer(deser); _replaceProperty(_beanProperties, creatorProps, prop, newProp); } }
type.isEnumType()这里是关键:
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); } 。。。 }
public JsonDeserializer<?> createEnumDeserializer(DeserializationContext ctxt, JavaType type, BeanDescription beanDesc) throws JsonMappingException { final DeserializationConfig config = ctxt.getConfig(); final Class<?> enumClass = type.getRawClass(); // 23-Nov-2010, tatu: Custom deserializer? JsonDeserializer<?> deser = _findCustomEnumDeserializer(enumClass, config, beanDesc); if (deser == null) { ValueInstantiator valueInstantiator = _constructDefaultValueInstantiator(ctxt, beanDesc); SettableBeanProperty[] creatorProps = (valueInstantiator == null) ? null : valueInstantiator.getFromObjectArguments(ctxt.getConfig()); // May have @JsonCreator for static factory method:
//这里是重点,如上面的注释,如果在enum中定义了工厂方法,找打上了JsonCreator的话,那就算指定了反序列化的方法了,会通过反射执行反序列化
for (AnnotatedMethod factory : beanDesc.getFactoryMethods()) { if (_hasCreatorAnnotation(ctxt, factory)) { if (factory.getParameterCount() == 0) { // [databind#960] deser = EnumDeserializer.deserializerForNoArgsCreator(config, enumClass, factory); break; } Class<?> returnType = factory.getRawReturnType(); // usually should be class, but may be just plain Enum<?> (for Enum.valueOf()?) if (returnType.isAssignableFrom(enumClass)) { deser = EnumDeserializer.deserializerForCreator(config, enumClass, factory, valueInstantiator, creatorProps); break; } } } // Need to consider @JsonValue if one found if (deser == null) { deser = new EnumDeserializer(constructEnumResolver(enumClass, config, beanDesc.findJsonValueAccessor()), config.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS)); } } // and then post-process it too if (_factoryConfig.hasDeserializerModifiers()) { for (BeanDeserializerModifier mod : _factoryConfig.deserializerModifiers()) { deser = mod.modifyEnumDeserializer(config, type, beanDesc, deser); } } return deser; }
_findCustomEnumDeserializer又云objectMapper中的配置中找:由于没有配置,所以返回null
protected JsonDeserializer<?> _findCustomEnumDeserializer(Class<?> type, DeserializationConfig config, BeanDescription beanDesc) throws JsonMappingException { for (Deserializers d : _factoryConfig.deserializers()) { JsonDeserializer<?> deser = d.findEnumDeserializer(type, config, beanDesc); if (deser != null) { return deser; } } return null; }
由一没找到,所以就指定了EnumDeserializer为枚举的默认反序列化类了。