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]: