目录
[1.1 SpringMvc是如何处理请求报文和响应报文](#1.1 SpringMvc是如何处理请求报文和响应报文)
[1.2 JacksonConfig配置排查](#1.2 JacksonConfig配置排查)
[2.1 没有addSerializer](#2.1 没有addSerializer)
[2.2 添加了@EnableMvc注解](#2.2 添加了@EnableMvc注解)
[2.3 另外有地方配置了Jacksonhttpconver覆盖了配置](#2.3 另外有地方配置了Jacksonhttpconver覆盖了配置)
前言
上一篇文章《使用Jackson进行序列化和反序列化》中指出,Jackson默认是不支持处理java8的时间类型如:LocalDateTime类型会被序列化成带T的时间格式。需要在字段上面添加**@DateFomter**或者在ObjectMapper中注册JavaTimeModule。但是注册JavaTimeModule的方式我这边一直没有效果,时间类型并没有安装我设置的去格式化。本篇文章是我排查我的配置为何不生效,并最终找到原因使配置生效的过程。
首先我把我的配置先贴出来,有经验的大神应该一眼就看出来导致我时间格式化模块配置没生效的原因了,但是我在排查的时候累计花费时间有一天了。
@Configuration
public class JacksonConfig {
@Bean("objectMapper")
@Primary
@ConditionalOnMissingBean(ObjectMapper.class)
public ObjectMapper getObjectMapper(Jackson2ObjectMapperBuilder builder) {
ObjectMapper mapper = builder.build();
// 日期格式
mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd"));
//GMT+8
//map.put("CTT", "Asia/Shanghai");
mapper.setTimeZone(TimeZone.getTimeZone("GMT+8"));
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN)));
javaTimeModule.addSerializer(new LocalTimeSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_TIME_PATTERN)));
javaTimeModule.addSerializer(new LocalDateSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN)));
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DatePattern.CHINESE_DATE_PATTERN)));
javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN)));
javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_TIME_PATTERN)));
mapper.registerModule(javaTimeModule);
//Include.NON_NULL 属性为NULL 不序列化
//ALWAYS // 默认策略,任何情况都执行序列化
//NON_EMPTY // null、集合数组等没有内容、空字符串等,都不会被序列化
//NON_DEFAULT // 如果字段是默认值,就不会被序列化
//NON_ABSENT // null的不会序列化,但如果类型是AtomicReference,依然会被序列化
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
//允许字段名没有引号(可以进一步减小json体积):
mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
//允许单引号:
mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
// 允许出现特殊字符和转义符
//mapper.configure(Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);这个已经过时。
mapper.configure(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature(), true);
//允许C和C++样式注释:
mapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
//序列化结果格式化,美化输出
mapper.enable(SerializationFeature.INDENT_OUTPUT);
//枚举输出成字符串
//WRITE_ENUMS_USING_INDEX:输出索引
mapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
//空对象不要抛出异常:
mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
//Date、Calendar等序列化为时间格式的字符串(如果不执行以下设置,就会序列化成时间戳格式):
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
//反序列化时,遇到未知属性不要抛出异常:
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
//反序列化时,遇到忽略属性不要抛出异常:
mapper.disable(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES);
//反序列化时,空字符串对于的实例属性为null:
mapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
return mapper;
}
}
我在网上找了很多方法,但是都没有解决我的问题,所以没办法只能想着静下心来,通过查看源码的方式来排查问题了
一、问题排查过程
1.1 SpringMvc是如何处理请求报文和响应报文
这个我之前的文章有写过,这里就直接引用之前的文章了《深入探究Spring MVC如何处理请求报文和响应报文》根据文章中的内容,处理响应报文的地方是在RequestResponseBodyMethodProcessor#handleReturnValue中。 这这里的逻辑的意思就是根据请求头和响应头里面的类型type找到处理响应报文的HttpMessageConverter。
这里直接看结果,最终用来序列化的HttpMessageConvert 中ObjectMapper 中的LocalDateTimeSerializer 中根本就没有formatter, 说明在config 中配置的JavaTimeModel并没有生效。但是其他的配置却生效了。
通过上面的分析,可以推断出肯定是MappingJackson2HttpMessageConverter在生成的时候并没有将我在ObjectMapperConfig中配置的JavaTimeModel给带进去。
1.2 JacksonConfig配置排查
通过上面的排查,知道JavaTimeModel配置未生效,但是其它的配置却是没问题的,所以JacksonConfig中的其它配置是没问题的。下面要排查只能看MappingJackson2HttpMessageConverter 生成的地方了**。** 这时候我想起了上次也试了一个配置的方法是有效的,就是在Jackson2ObjectMapperBuilder 上直接注册model。直接注册model有效,在ObjectMapper上注册就没效果。难道是Jackson2ObjectMapperBuilder没有读取ObjectMapper中的配置,或者是注册之前已经读取了。
想到这里我就想点开这一段代码看一下
ObjectMapper mapper = builder.build();
因为我怀疑build方法是重新创建了一个对象返回了(其实这个想法有点荒谬,重新创建一个就有两个ObjectMapper了,那就是一个ObjectMapper配置一半了)。 所以我就进入代码一探究竟。
大概的意思是Model已经注册好了,所以是build方法不能调用。所以后面我就将配置改成
ObjectMapper mapper = new ObjectMapper();
果然就配置生效了。
二、导致Jackson配置失效的原因
2.1 没有addSerializer
如果配置Jackson的时候,只是添加了JavaTimeModel,是不会生效的。需要添加LocalDateTimeSerializer 、LocalDateTimeDeserializer 、LocalTimeSerializer 、LocalDateDeserializer 、LocalDateSerializer 、LocalTimeDeserializer才会够成功序列化和反序列化时间类型。
2.2 添加了@EnableMvc注解
直接参考这个《jackson全局配置没有生效》
2.3 另外有地方配置了Jacksonhttpconver覆盖了配置
需要自己找到其它配置的地方,或者在配置上添加@Primary注解
总结
本文主要是上个章节留下来的问题,关于builder.build()导致model已经被加载过了的问题似乎我没有说清楚,主要是也不想花时间深究了,有空再去看看MappingJackson2HttpMessageConverter初始化的过程。