SpringBoot实现WebMvcConfigurationSupport导致自定义的JSON时间返回格式不生效

给理想留点时间,熬过低谷,繁华自现。

一、场景:返回数据对象带有时间类型属性

返回数据结构如下:(ps:SpringBoot版本为2.0.3-RELEASE)

@Setter
@Getter
public class DateResponse implements Serializable {

    private LocalDate localDate = LocalDate.now();
    private LocalDateTime localDateTime = LocalDateTime.now();
    private LocalTime localTime = LocalTime.now();
    private Date date = new Date();
}

二、出现的问题

请求返回的数据如下:

{
  "localDate": [
    2020,
    6,
    20
  ],
  "localDateTime": [
    2020,
    6,
    20,
    11,
    46,
    55,
    810000000
  ],
  "localTime": [
    11,
    46,
    55,
    810000000
  ],
  "date": 1592624815810
}

Java8的时间类型都变成了数组,Date则变成了时间戳。这和我们期望的不符,我们期望能返回如下的结果:

{
  "localDate": "2020-06-21",
  "localDateTime": "2020-06-21 10:41:33",
  "localTime": "10:41:33",
  "date": "2020-06-21 10:41:33"
}

三、分析问题

官方文档里的介绍如下所示
在这里插入图片描述
Spring MVC已经为我们提供了一些默认的HttpMessageConverters 来对HTTP请求内容进行转换,但显然这些默认的Converters不是我们需要的。

继续往下看,发现Spring还提供了几种方法来让我们自定义Converter在这里插入图片描述
按照上面说的,可以通过自定义Jackson2ObjectMapperBuilderCustomizerObjectMapperJackson2ObjectMapperBuilderMappingJackson2HttpMessageConverter 等Bean来自定义转换格式。但是按照文档里面的解释,例如按照自定义ObjectMapper方式,会禁用ObjectMapper的所有自动配置。综合考虑,还是选择自定义MappingJackson2HttpMessageConverter的方式。

@Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {
    MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter(objectMapper());
    return mappingJackson2HttpMessageConverter;
}

private ObjectMapper objectMapper() {
    ObjectMapper objectMapper = new ObjectMapper();
    // 不序列化null的属性
    objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
    objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
    objectMapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);
    JavaTimeModule javaTimeModule = new JavaTimeModule();
    javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(Constants.DEFAULT_DATE_TIME_FORMAT)));
    javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(Constants.DEFAULT_DATE_FORMAT)));
    javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(Constants.DEFAULT_TIME_FORMAT)));
    javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(Constants.DEFAULT_DATE_TIME_FORMAT)));
    javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(Constants.DEFAULT_DATE_FORMAT)));
    javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(Constants.DEFAULT_TIME_FORMAT)));
    objectMapper.registerModule(javaTimeModule).registerModule(new ParameterNamesModule());
    objectMapper.setDateFormat(new SimpleDateFormat(Constants.DEFAULT_DATE_TIME_FORMAT));
    return objectMapper;
}

static class Constants {
     /**
      * 默认日期时间格式
      */
     public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
     /**
      * 默认日期格式
      */
     public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
     /**
      * 默认时间格式
      */
     public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";
 }

通过如上的配置,我们可以得到我们期望的返回了。

但是,问题又来了。

如果我们在程序里继承了WebMvcConfigurationSupport, 那么我们得到的返回又会是像最开始的那样。这是为什么呢?
官方文档有这样的介绍:
在这里插入图片描述
仔细看第二段话,问题好像出现在WebMvcConfigurationSupport这里。我们调试一下程序来看看到底是什么情况。
没有继承WebMvcConfigurationSupport的时候,我们看看WebMvcConfigurationSupport里的Converters有哪些。
在这里插入图片描述
在这里插入图片描述
在如图所示的两个地方打上断点。
第一个断点我们可以得到如下信息(注意这个@5484):在这里插入图片描述在这里插入图片描述
这个messageConvertersWebMvcConfigurationSupport里的一个属性,Spring通过这个属性里的Converters来对HTTP请求数据进行转换。我们可以看到,下标为7的那个Converter就是我们上面自定义的Converter。所以这个时候,数据是按照我们定的格式返回的。

如果继承了WebMvcConfigurationSupport之后呢?
在这里插入图片描述
如上图所示,这些Bean的序号都是连着的,我们自定义的Converter根本没有加入进去。不过文档也说了,可以通过configureMessageConverters这个方法将我们自定义的Converter加入进去。我们来试一下。

@Override
protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    converters.add(new MappingJackson2HttpMessageConverter(objectMapper()));
}

在这里插入图片描述
好了,这下messageConverters里面就一个我们自定义的Converter,发起请求,数据按照我们期待的格式返回了。但是会有个疑问,其余的那些Converter都没有了,会不会有什么影响。目前我也不知道会有什么影响>_<,但是看WebMvcConfigurationSupport的源码,发现了一个方法addDefaultHttpMessageConverters,这个方法在什么时候被调用呢?WebMvcConfigurationSupport还有另一个方法getMessageConverters,这个方法源码如下:

protected final List<HttpMessageConverter<?>> getMessageConverters() {
	if (this.messageConverters == null) {
	    this.messageConverters = new ArrayList();
	    this.configureMessageConverters(this.messageConverters);
	    if (this.messageConverters.isEmpty()) {
	        this.addDefaultHttpMessageConverters(this.messageConverters);
	    }
	    this.extendMessageConverters(this.messageConverters);
	}
	return this.messageConverters;
}

这说明了,如果我们通过configureMessageConverters方法加入自定义的Converter,Spring提供的默认的Converters就不会加入到messageConverters里了(其实官方文档有这么一句话就说明了这个问题: However, unlike with normal MVC, you can supply only additional converters that you need (because Spring Boot uses the same mechanism to contribute its defaults).)。再往下看,发现了另一个方法extendMessageConverters,顾名思义,这个方法是扩展messageConverters的。那我们是不是可以实现extendMessageConverters这个方法来只加入我们自定义的Converter而不改变默认的配置呢。动手一试!

@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
 	//需要注释掉configureMessageConverters的实现
    converters.add(new MappingJackson2HttpMessageConverter(objectMapper()));
}

然而,这样配置了,发现返回的数据,时间类型的又变成了数组和时间戳。这是为什么呢。再次打断点调试。
在这里插入图片描述
断点打在extendMessageConverters里,得到结果如下
在这里插入图片描述
可以看到,我们自定义的Converter尚未加入到converters里,而converters已经有了8个默认的Converter了,继续下一步。
在这里插入图片描述
现在messageConverters里一共9个Converter,最后一个(5495)是我们自定义的。看来问题就出现在这里了,我们自定义的Converter被放在了最后面。而messageConverters是个ArrayList类型的,它是顺序访问的。知道问题的所在,那就好办了。把默认的MappingJackson2HttpMessageConverter去掉或者将自定义的MappingJackson2HttpMessageConverter放到第一个就好了。重新实现extendMessageConverters方法如下:

@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
    // 去除掉默认的MappingJackson2HttpMessageConverter,加入自定义的MappingJackson2HttpMessageConverter
    converters.removeIf(converter -> converter instanceof MappingJackson2HttpMessageConverter);
    // 还有种方式:converters.add(0, new MappingJackson2HttpMessageConverter(objectMapper()))
    converters.add(new MappingJackson2HttpMessageConverter(objectMapper()));
}

按照上述方式实现之后,我们就可以得到期望的返回格式了。

四、总结一下

出现这个问题的时候,在网上索搜了很多文章,但要么是通过注解一个个去格式化,要么就是最开始的那种,自定义Jackson2ObjectMapperBuilderCustomizerObjectMapperJackson2ObjectMapperBuilderMappingJackson2HttpMessageConverter 等Bean来解决。正常情况下,这些方式都是能解决问题的。但是不够全面,就比如当我们自己实现了WebMvcConfigurationSupport的时候,第二种方式就不生效了。最终通过打断点调试才发现了问题所在。其实这些问题在官方文档中都有提及到。所以,没事多去读读官方文档,看看源码,还是有好处的。

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值