Spring Boot升级到2.x,Jackson对Date时间类型序列化的变化差点让项目暴雷【享学Spring Boot】(下)

简介: Spring Boot升级到2.x,Jackson对Date时间类型序列化的变化差点让项目暴雷【享学Spring Boot】(下)

WebMvcAutoConfiguration


对该自动配置类一句话解释:通过EnableAutoConfiguration方式代替@EnableWebMvc


说明:在Spring Boot环境下,强烈不建议你启用@EnableWebMvc注解


@Configuration
@ConditionalOnWebApplication
...
// 若你开启了`@EnableWebMvc`,该自动配置类就不生效啦
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class) 
public class WebMvcAutoConfiguration {
  // 对WebMvcConfigurerAdapter你肯定不陌生:它是你定制MVC的钩子(WebMvcConfigurer接口)
  // EnableWebMvcConfiguration继承自DelegatingWebMvcConfiguration,效果同@EnableWebMvc呀
  @Configuration
  @Import(EnableWebMvcConfiguration.class)
  @EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
  public static class WebMvcAutoConfigurationAdapter extends WebMvcConfigurerAdapter {
    ...
    // 通过构造器注入(请注意使用了@Lazy,这是很重要的一个技巧)
    private final HttpMessageConverters messageConverters;
    ...
    // 把容器内所有的消息转换器,全部添加进去,让它生效
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
      converters.addAll(this.messageConverters.getConverters());
    }
    ...
  }
}


容器内的所有的消息转换器,最终通过WebMvcConfigurer#configureMessageConverters()这个API被放进去,为了更方便同学们理解,再回顾下这段代码:

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



so,如果你自己定义了消息转换器,那么messageConverters将不再是empty,所以默认的那些转换器们(包括默认会装配的MappingJackson2HttpMessageConverter)也就不会再执行了。


但是,你可千万不要轻易得出结论:Spring Boot下默认只有两个消息转换器。请看如下代码

HttpMessageConverters构造器:
  // 这里的addDefaultConverters()就是把默认注册的那些注册好
  // 并且最终还有个非常有意思的Combined动作
  public HttpMessageConverters(boolean addDefaultConverters, Collection<HttpMessageConverter<?>> converters) {
    List<HttpMessageConverter<?>> combined = getCombinedConverters(converters, addDefaultConverters ? getDefaultConverters() : Collections.<HttpMessageConverter<?>>emptyList());
    combined = postProcessConverters(combined);
    this.converters = Collections.unmodifiableList(combined);
  }


Spring Boot它不仅保留了默认的消息转换器们,保持最大的向下兼容能力,同时还让你定义的Bean也能加入进来。最终拥有的消息转换器我截图如下:


image.png


可以看见:MappingJackson2HttpMessageConverter和StringHttpMessageConverter均出现了两次(HttpMessageConverters内部有个顺序的协商,有兴趣的可自行了解),但是@Bean方式进来的均在前面,所以会覆盖默认行为。


出现差异的根本原因


最后的最后,终于轮到解答如标题"险些暴雷"疑问的根本原因了。解答这个原因本身其实非常简单,展示不同版本JacksonAutoConfiguration的源码对比一看便知:


1.5.22.RELEASE版本:

@Configuration
@ConditionalOnClass(ObjectMapper.class)
public class JacksonAutoConfiguration {
  ... // 无static静态代码块
}


2.0.0.RELEASE版本


@Configuration
@ConditionalOnClass(ObjectMapper.class)
public class JacksonAutoConfiguration {
  private static final Map<?, Boolean> FEATURE_DEFAULTS;
  static {
    Map<Object, Boolean> featureDefaults = new HashMap<>();
    featureDefaults.put(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
    FEATURE_DEFAULTS = Collections.unmodifiableMap(featureDefaults);
  }
  ...
}


Jackson默认是开启SerializationFeature.WRITE_DATES_AS_TIMESTAMPS这个特征值的,所以它对时间类型的序列化方式是用时间戳方式。

1.x并没有对Jackson默认行为做更改,而自2.0.0.RELEASE版本起,Spring Boot默认把此特征值给置为fasle了。小小改动,巨大能量,险些让我项目暴雷。


说明:因我写的脚手架多个团队使用,因此向下兼容能力及其重要


解决方案


虽然说这对Spring Boot本身不是问题,但是如果你想要向下兼容这便成了问题。

定位到了问题所在,从来不缺解决方案。若你仍旧像保持之前的序列化数据格式,你可以这么做(提供两种方案以供参考):


  1. 增加属性spring.jackson.serialization.write-dates-as-timestamps=true
  2. [享学Jackson] 专栏里有讲述,此属性值的优先级高于静态代码块,所以这么做是有效的
  3. 自定义一个Jackson2ObjectMapperBuilderCustomizer(保证在默认的定制器之后执行即可)


小知识点:1.x和2.x动态代理方式的差异


1.x默认使用的JDK动态代码方式

2.x默认使用的CGLIB的动态代理方式


需要注意的是这和Spring无差别,Spring一直默认的就是JDK代理方式,而是Spring在其基础上做了定制,详情请参见AopAutoConfiguration自动配置类,它在1.x和2.x的表现是不一样的。


总结


本篇文章作为采坑指导系列具有很强的现实意义,如果你现正处在1.x升到2.x的状态,那么本文应该能对你有些帮助。这次遇到的问题,作为程序员我们应该能得出如下总结:


  1. 一定要有版本意识,一定要有版本意识,一定要有版本意识
  2. 序列化/反序列化是特别敏感的一个知识点,平时很少人关注所以容易导致出了问题就摸瞎,建议团队内有专人研究
  3. 小小改动,往往具有大大能量。对未知应具有敬畏之心,小心为之。
相关文章
|
4月前
|
消息中间件 NoSQL Java
spring boot2升级boot3指南
本文介绍了如何将Spring Boot 2.x升级至Spring Boot 3.x,涵盖使用OpenRewrite自动化重构工具进行代码转换、依赖版本升级、配置属性调整及常见问题处理等内容,帮助开发者高效完成升级工作。
1975 6
|
6月前
|
JSON 分布式计算 大数据
springboot项目集成大数据第三方dolphinscheduler调度器
springboot项目集成大数据第三方dolphinscheduler调度器
415 3
|
6月前
|
Java 关系型数据库 数据库连接
Spring Boot项目集成MyBatis Plus操作PostgreSQL全解析
集成 Spring Boot、PostgreSQL 和 MyBatis Plus 的步骤与 MyBatis 类似,只不过在 MyBatis Plus 中提供了更多的便利功能,如自动生成 SQL、分页查询、Wrapper 查询等。
710 3
|
6月前
|
Java 关系型数据库 MySQL
springboot项目集成dolphinscheduler调度器 实现datax数据同步任务
springboot项目集成dolphinscheduler调度器 实现datax数据同步任务
710 2
|
6月前
|
分布式计算 Java 大数据
springboot项目集成dolphinscheduler调度器 可拖拽spark任务管理
springboot项目集成dolphinscheduler调度器 可拖拽spark任务管理
398 2
|
6月前
|
Java 测试技术 Spring
简单学Spring Boot | 博客项目的测试
本内容介绍了基于Spring Boot的博客项目测试实践,重点在于通过测试驱动开发(TDD)优化服务层代码,提升代码质量和功能可靠性。案例详细展示了如何为PostService类编写测试用例、运行测试并根据反馈优化功能代码,包括两次优化过程。通过TDD流程,确保每项功能经过严格验证,增强代码可维护性与系统稳定性。
304 0
|
6月前
|
存储 Java 数据库连接
简单学Spring Boot | 博客项目的三层架构重构
本案例通过采用三层架构(数据访问层、业务逻辑层、表现层)重构项目,解决了集中式开发导致的代码臃肿问题。各层职责清晰,结合依赖注入实现解耦,提升了系统的可维护性、可测试性和可扩展性,为后续接入真实数据库奠定基础。
566 0
|
分布式计算 大数据 Java
springboot项目集成大数据第三方dolphinscheduler调度器 执行/停止任务
springboot项目集成大数据第三方dolphinscheduler调度器 执行/停止任务
158 0
|
分布式计算 Java 大数据
springboot项目集成dolphinscheduler调度器 项目管理
springboot项目集成dolphinscheduler调度器 项目管理
222 0
|
7月前
|
网络协议 Java
在SpringBoot项目中使用Netty实现远程调用
本文介绍了使用Netty解决网络连接性能问题的方法,重点讲解了Netty的NIO特性及其在SpringBoot中的应用。Netty作为高效的NIO框架,支持非阻塞IO,能通过单线程管理多个客户端连接,简化TCP/UDP套接字服务器开发。文章详细展示了Netty在SpringBoot中实现远程调用的过程,包括服务端与客户端代码实现、依赖配置及测试验证。通过示例代码,如`NettyServer`、`NettyClientUtil`等,清晰说明了Netty的工作原理和实际应用,解决了半包等问题,并提供了完整的测试结果。
839 3

热门文章

最新文章