Jackson问题分析

Jackson问题分析

1、问题发生:

升级了jackson版本后,导致@RestController下的某些接口报500,错误信息如下

拿其中一个接口为例,返回对象是vo,根据报错信息很明显可以看出来是因为vo对象中含有一个java.time.LocalDateTime 类型的字段导致无法反序列化

{
    "timestamp": 1657971517098,
    "status": 500,
    "error": "Internal Server Error",
    "exception": "org.springframework.http.converter.HttpMessageNotWritableException",
    "message": "Could not write JSON: Java 8 date/time type `java.time.LocalDateTime` not supported by default: add Module \"com.fasterxml.jackson.datatype:jackson-datatype-jsr310\" to enable handling; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type `java.time.LocalDateTime` not supported by default: add Module \"com.fasterxml.jackson.datatype:jackson-datatype-jsr310\" to enable handling (through reference chain: com.example.entity.SignUpVo[\"signTime\"])",
    "path": "/testDong"
}

1.1版本信息

springBoot版本为 1.5.6.Relase

jackson旧版本为,此时一切正常

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.10.8</version>
</dependency>

<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.9.9</version>
</dependency>

<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-annotations -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
    <version>2.9.9</version>
</dependency>

jackson新版本为,会报错

<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.13.3</version>
</dependency>

<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.13.3</version>
</dependency>

<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-annotations -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
    <version>2.13.3</version>
</dependency>

旧版本是可以正常将LocalDateTime转换成对应格式的字符串时间,但是升级了版本就报错了。

2、问题产生原因:

翻阅jackson官方文档发现

在2.11.4->2.12.0版本时做出了一些改动,

[https://github.com/FasterXML/jackson/wiki/Jackson-Release-2.12]:

Java 8 date/time
#94: Deserialization of timestamps with UTC timezone to LocalDateTime doesn't yield correct time
#165: Problem in serializing negative Duration values
#166: Cannot deserialize OffsetDateTime.MIN or OffsetDateTime.MAX with ADJUST_DATES_TO_CONTEXT_TIME_ZONE enabled
#175: ObjectMapper#setTimeZone ignored by jsr-310/datetime types during serialization when using @JsonFormat annotation
#184: DurationDeserializer should use @JsonFormat.pattern (and config override) to support configurable ChronoUnit
#189: Support use of "pattern" (ChronoUnit) for DurationSerializer too

将带时区LocalDateTime值反序列化时会出现时间解析不正确的情况,问题大抵就在这了

作者回复该问题,

[https://github.com/FasterXML/jackson-modules-java8/issues/94]:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kkh7LOgY-1657987268188)(image-20220716202940398.png)]

好了,问题找到了,那么来看看代码吧,靓仔(~ ̄▽ ̄)~

3、代码分析

​ 分析代码肯定是离不开MVC的,所以这里我们简单点说好了

直接上堆栈信息图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HUD62DWZ-1657987268189)(image-20220716204356439.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TGiOlxUQ-1657987268189)(image-20220716204650191.png)]

核心就是

FrameworkServlet
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
    processRequest(request, response);
}

是不是感觉又回到了学习web编程的岁月,哈哈(≧∇≦)ノ

流程图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PtFGAAH5-1657987268189)(image-20220716213444894.png)]

如果看不清楚的话,参考:【金山文档】 MVC简易版
https://kdocs.cn/l/cq0X1rL0bOwn

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EUszR87a-1657987268190)(image-20220716220405241-16579802485931.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wlSaGIDO-1657987268190)(image-20220716213711601.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D7HwLF61-1657987268190)(image-20220716213732849.png)]

返回值解析

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {

		Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
		setResponseStatus(webRequest);

		if (returnValue == null) {
			if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
				mavContainer.setRequestHandled(true);
				return;
			}
		}
		else if (StringUtils.hasText(getResponseStatusReason())) {
			mavContainer.setRequestHandled(true);
			return;
		}

		mavContainer.setRequestHandled(false);
		try {
			this.returnValueHandlers.handleReturnValue(
					returnValue, getReturnValueType(returnValue), mavContainer, webRequest);//返回值解析器,解析返回对象
		}
		catch (Exception ex) {
			if (logger.isTraceEnabled()) {
				logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);
			}
			throw ex;
		}
	}
@Override
	public void handleReturnValue(Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

		HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);//返回对象为RequestResponseBodyMethodProcessor
		if (handler == null) {
			throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
		}
		handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
	}

/**
 * Resolves method arguments annotated with {@code @RequestBody} and handles return
 * values from methods annotated with {@code @ResponseBody} by reading and writing
 * to the body of the request or response with an {@link HttpMessageConverter}.
 *
 * <p>An {@code @RequestBody} method argument is also validated if it is annotated
 * with {@code @javax.validation.Valid}. In case of validation failure,
 * {@link MethodArgumentNotValidException} is raised and results in an HTTP 400
 * response status code if {@link DefaultHandlerExceptionResolver} is configured.
 *
 * @author Arjen Poutsma
 * @author Rossen Stoyanchev
 * @author Juergen Hoeller
 * @since 3.1
 */
//解析使用 @RequestBody 注释的方法参数,并通过使用 HttpMessageConverter 读取和写入请求或响应的正文来处理使用 @ResponseBody 注释的方法的返回值。如果使用@javax.validation.Valid 注释@RequestBody 方法参数,它也会被验证。在验证失败的情况下,如果配置了 DefaultHandlerExceptionResolver,则会引发 MethodArgumentNotValidException 并导致 HTTP 400 响应状态代码。

public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {..................................}

参数中的了@RequestBody时,使用RequestResponseBodyMethodProcessor 处理器解析,同时返回值是json时,也需要使用该处理器

@Override
	public void handleReturnValue(Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
			throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

		mavContainer.setRequestHandled(true);
		ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
		ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);

		// Try even with null return value. ResponseBodyAdvice could get involved.
		writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);//返回值解析
	}

AbstractMessageConverterMethodProcessor#writeWithMessageConverters(…)

返回值解析---------------------------------------------------------------------------------------

@SuppressWarnings("unchecked")
	protected <T> void writeWithMessageConverters(T value, MethodParameter returnType,
			ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
			throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

		Object outputValue;
		Class<?> valueType;
		Type declaredType;

		if (value instanceof CharSequence) {//字符串
			outputValue = value.toString();
			valueType = String.class;
			declaredType = String.class;
		}
		else {
			outputValue = value;//获取对象
			valueType = getReturnValueType(outputValue, returnType);//对象类信息
			declaredType = getGenericType(returnType);//Object
		}

		HttpServletRequest request = inputMessage.getServletRequest();//转化request对象
		List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(request);//获得可接受的媒体类型,content-type,demon中是*/**
		List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType);//获得可生产的媒体类型  对照下图,非常重要

		if (outputValue != null && producibleMediaTypes.isEmpty()) {
			throw new IllegalArgumentException("No converter found for return value of type: " + valueType);
		}

		Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();
		for (MediaType requestedType : requestedMediaTypes) {
			for (MediaType producibleType : producibleMediaTypes) {
				if (requestedType.isCompatibleWith(producibleType)) {
					compatibleMediaTypes.add(getMostSpecificMediaType(requestedType, producibleType));//兼容的媒体类型
				}
			}
		}
		if (compatibleMediaTypes.isEmpty()) {
			if (outputValue != null) {
				throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);
			}
			return;
		}

		List<MediaType> mediaTypes = new ArrayList<MediaType>(compatibleMediaTypes);
		MediaType.sortBySpecificityAndQuality(mediaTypes);

		MediaType selectedMediaType = null;
		for (MediaType mediaType : mediaTypes) {
			if (mediaType.isConcrete()) {
				selectedMediaType = mediaType;
				break;
			}
			else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION)) {//不允许请求类型的content-type为*/**
				selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
				break;
			}
		}

		if (selectedMediaType != null) {//selectedMediaType为application/json
			selectedMediaType = selectedMediaType.removeQualityValue();
			for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
				if (messageConverter instanceof GenericHttpMessageConverter) {
					if (((GenericHttpMessageConverter) messageConverter).canWrite(
							declaredType, valueType, selectedMediaType)) {//只有这里是真正的解析,因为我们需要将对象转化成json
                        //需要GenericHttpMessageConverter下的转换器,GenericHttpMessageConverter下有9种转换器,而spring默认将对象转换为json用的是MappingJackson2HttpMessageConverter,这一看就是jackson里面的
						outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,
								(Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(),
								inputMessage, outputMessage);
						if (outputValue != null) {
							addContentDispositionHeader(inputMessage, outputMessage);
                            //本次请求的报错位置是在这里,看下张图了,这张讲完啦
							((GenericHttpMessageConverter) messageConverter).write(
									outputValue, declaredType, selectedMediaType, outputMessage);
							if (logger.isDebugEnabled()) {
								logger.debug("Written [" + outputValue + "] as \"" + selectedMediaType +
										"\" using [" + messageConverter + "]");
							}
						}
						return;
					}
				}
				else if (messageConverter.canWrite(valueType, selectedMediaType)) {
					outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,
							(Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(),
							inputMessage, outputMessage);
					if (outputValue != null) {
						addContentDispositionHeader(inputMessage, outputMessage);
						((HttpMessageConverter) messageConverter).write(outputValue, selectedMediaType, outputMessage);
						if (logger.isDebugEnabled()) {
							logger.debug("Written [" + outputValue + "] as \"" + selectedMediaType +
									"\" using [" + messageConverter + "]");
						}
					}
					return;
				}
			}
		}

		if (outputValue != null) {
			throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
		}
	}
protected List<MediaType> getProducibleMediaTypes(HttpServletRequest request, Class<?> valueClass, Type declaredType) {
		Set<MediaType> mediaTypes = (Set<MediaType>) request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
		if (!CollectionUtils.isEmpty(mediaTypes)) {
			return new ArrayList<MediaType>(mediaTypes);
		}
		else if (!this.allSupportedMediaTypes.isEmpty()) {
			List<MediaType> result = new ArrayList<MediaType>();
			for (HttpMessageConverter<?> converter : this.messageConverters) {
				if (converter instanceof GenericHttpMessageConverter && declaredType != null) {
					if (((GenericHttpMessageConverter<?>) converter).canWrite(declaredType, valueClass, null)) {//判断哪个转换器可以转换obj
						result.addAll(converter.getSupportedMediaTypes());
					}
				}
				else if (converter.canWrite(valueClass, null)) {
					result.addAll(converter.getSupportedMediaTypes());
				}
			}
			return result;
		}
		else {
			return Collections.singletonList(MediaType.ALL);
		}
	}
public final void write(final T t, final Type type, MediaType contentType, HttpOutputMessage outputMessage)
			throws IOException, HttpMessageNotWritableException {

		final HttpHeaders headers = outputMessage.getHeaders();
		addDefaultHeaders(headers, t, contentType);//添加默认请求头,拼接编码格式,默认是utf-8

		if (outputMessage instanceof StreamingHttpOutputMessage) {//我们是json,不是流
			StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage;
			streamingOutputMessage.setBody(new StreamingHttpOutputMessage.Body() {
				@Override
				public void writeTo(final OutputStream outputStream) throws IOException {
					writeInternal(t, type, new HttpOutputMessage() {
						@Override
						public OutputStream getBody() throws IOException {
							return outputStream;
						}
						@Override
						public HttpHeaders getHeaders() {
							return headers;
						}
					});
				}
			});
		}
		else {//逻辑处理分支
			writeInternal(t, type, outputMessage);
			outputMessage.getBody().flush();
		}
	}


@Override
	protected void writeInternal(Object object, Type type, HttpOutputMessage outputMessage)
			throws IOException, HttpMessageNotWritableException {

		MediaType contentType = outputMessage.getHeaders().getContentType();//application/json;charset=UTF-8
		JsonEncoding encoding = getJsonEncoding(contentType);//UTF-8
		JsonGenerator generator = this.objectMapper.getFactory().createGenerator(outputMessage.getBody(), encoding);//UTF8JsonGenerator
		try {
			writePrefix(generator, object);//什么都不做

			Object value = object;
			Class<?> serializationView = null;
			FilterProvider filters = null;
			JavaType javaType = null;

			if (object instanceof MappingJacksonValue) {//false
				MappingJacksonValue container = (MappingJacksonValue) object;
				value = container.getValue();
				serializationView = container.getSerializationView();
				filters = container.getFilters();
			}
			if (type != null && value != null && TypeUtils.isAssignable(type, value.getClass())) {
				javaType = getJavaType(type, null);
			}

			ObjectWriter objectWriter = (serializationView != null ?
					this.objectMapper.writerWithView(serializationView) : this.objectMapper.writer());//对象编写器
			if (filters != null) {
				objectWriter = objectWriter.with(filters);
			}
			if (javaType != null && javaType.isContainerType()) {
				objectWriter = objectWriter.forType(javaType);
			}
			SerializationConfig config = objectWriter.getConfig();//设置对象编写器的序列化规则
			if (contentType != null && contentType.isCompatibleWith(TEXT_EVENT_STREAM) &&
					config.isEnabled(SerializationFeature.INDENT_OUTPUT)) {
				objectWriter = objectWriter.with(this.ssePrettyPrinter);
			}
			objectWriter.writeValue(generator, value);//真正的序列化对象,对应下图

			writeSuffix(generator, object);
			generator.flush();
		}
		catch (JsonProcessingException ex) {
			throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getOriginalMessage(), ex);
		}
	}

public void writeValue(JsonGenerator g, Object value) throws IOException
{
    _assertNotNull("g", g);
    _configureGenerator(g);
    if (_config.isEnabled(SerializationFeature.CLOSE_CLOSEABLE)
            && (value instanceof Closeable)) {//是不是克隆对象

        Closeable toClose = (Closeable) value;
        try {
            _prefetch.serialize(g, value, _serializerProvider());
            if (_config.isEnabled(SerializationFeature.FLUSH_AFTER_WRITE_VALUE)) {
                g.flush();
            }
        } catch (Exception e) {
            ClassUtil.closeOnFailAndThrowAsIOE(null, toClose, e);
            return;
        }
        toClose.close();
    } else {
        _prefetch.serialize(g, value, _serializerProvider());//不是克隆对象时,预取序列化器
        if (_config.isEnabled(SerializationFeature.FLUSH_AFTER_WRITE_VALUE)) {
            g.flush();
        }
    }
}
public void serializeValue(JsonGenerator gen, Object value) throws IOException
    {
        _generator = gen;
        if (value == null) {
            _serializeNull(gen);
            return;
        }
        final Class<?> cls = value.getClass();
        // true, since we do want to cache root-level typed serializers (ditto for null property)
        final JsonSerializer<Object> ser = findTypedValueSerializer(cls, true, null);//获取对象及其属性的序列化器
        PropertyName rootName = _config.getFullRootName();
        if (rootName == null) { // not explicitly specified
            if (_config.isEnabled(SerializationFeature.WRAP_ROOT_VALUE)) {
                _serialize(gen, value, ser, _config.findRootName(cls));
                return;
            }
        } else if (!rootName.isEmpty()) {
            _serialize(gen, value, ser, rootName);
            return;
        }
        _serialize(gen, value, ser);
    }

com.fasterxml.jackson.databind.ser.BeanPropertyWriter#serializeAsField 字段序列化器

@Override
public void serializeAsField(Object bean, JsonGenerator gen,
        SerializerProvider prov) throws Exception {
    // inlined 'get()'
    final Object value = (_accessorMethod == null) ? _field.get(bean)
            : _accessorMethod.invoke(bean, (Object[]) null);//LocalDateTime 的实例对象

    // Null handling is bit different, check that first
    if (value == null) {
        if (_nullSerializer != null) {
            gen.writeFieldName(_name);
            _nullSerializer.serialize(null, gen, prov);
        }
        return;
    }
    // then find serializer to use
    JsonSerializer<Object> ser = _serializer;//走到这里,图穷匕见,_serializer为UnsupportTypeSerializer,意味着上图就报错了
    if (ser == null) {
        Class<?> cls = value.getClass();
        PropertySerializerMap m = _dynamicSerializers;
        ser = m.serializerFor(cls);
        if (ser == null) {
            ser = _findAndAddDynamic(m, cls, prov);
        }
    }
    // and then see if we must suppress certain values (default, empty)
    if (_suppressableValue != null) {
        if (MARKER_FOR_EMPTY == _suppressableValue) {
            if (ser.isEmpty(prov, value)) {
                return;
            }
        } else if (_suppressableValue.equals(value)) {
            return;
        }
    }
    // For non-nulls: simple check for direct cycles
    if (value == bean) {
        // four choices: exception; handled by call; pass-through or write null
        if (_handleSelfReference(bean, gen, prov, ser)) {
            return;
        }
    }
    gen.writeFieldName(_name);
    if (_typeSerializer == null) {
        ser.serialize(value, gen, prov);
    } else {
        ser.serializeWithType(value, gen, prov, _typeSerializer);
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-srORdep3-1657987268191)(image-20220716234752464-16579864748753.png)]

代码证明有点烂尾,Bucket那一块阅读实在吃力,继续加油把。。。。。。。。。。。。。。。。。。。。。至少问题找到了

4、总结

没事别升版本

参考:

[https://cloud.tencent.com/developer/article/1611093]:

if (value == bean) {
// four choices: exception; handled by call; pass-through or write null
if (_handleSelfReference(bean, gen, prov, ser)) {
return;
}
}
gen.writeFieldName(_name);
if (_typeSerializer == null) {
ser.serialize(value, gen, prov);
} else {
ser.serializeWithType(value, gen, prov, _typeSerializer);
}
}


[外链图片转存中...(img-srORdep3-1657987268191)]

代码证明有点烂尾,Bucket那一块阅读实在吃力,继续加油把。。。。。。。。。。。。。。。。。。。。。至少问题找到了

## 4、总结

没事别升版本

参考:

[https://cloud.tencent.com/developer/article/1611093]: 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值