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. 小小改动,往往具有大大能量。对未知应具有敬畏之心,小心为之。
相关文章
|
22天前
|
存储 运维 安全
Spring运维之boot项目多环境(yaml 多文件 proerties)及分组管理与开发控制
通过以上措施,可以保证Spring Boot项目的配置管理在专业水准上,并且易于维护和管理,符合搜索引擎收录标准。
36 2
|
2月前
|
设计模式 前端开发 Java
Spring MVC——项目创建和建立请求连接
MVC是一种软件架构设计模式,将应用分为模型、视图和控制器三部分。Spring MVC是基于MVC模式的Web框架,通过`@RequestMapping`等注解实现URL路由映射,支持GET和POST请求,并可传递参数。创建Spring MVC项目与Spring Boot类似,使用`@RestController`注解标记控制器类。
39 1
Spring MVC——项目创建和建立请求连接
|
2月前
|
Java 关系型数据库 MySQL
Maven——创建 Spring Boot项目
Maven 是一个项目管理工具,通过配置 `pom.xml` 文件自动获取所需的 jar 包,简化了项目的构建和管理过程。其核心功能包括项目构建和依赖管理,支持创建、编译、测试、打包和发布项目。Maven 仓库分为本地仓库和远程仓库,远程仓库包括中央仓库、私服和其他公共库。此外,文档还介绍了如何创建第一个 SpringBoot 项目并实现简单的 HTTP 请求响应。
140 1
Maven——创建 Spring Boot项目
|
2月前
|
Java 关系型数据库 MySQL
如何使用 maven 创建一个 Spring Boot项目
Maven 是一个强大的项目管理工具,通过配置 `pom.xml` 文件自动获取所需的 jar 包,提高开发效率。其核心功能包括项目构建和依赖管理。项目构建支持编译、测试、打包和发布等流程,而依赖管理则通过中央仓库、本地仓库和私有服务器获取和管理项目依赖。示例中展示了如何创建第一个 SpringBoot 项目并实现简单接口。
38 1
如何使用 maven 创建一个 Spring Boot项目
|
2月前
|
Java 应用服务中间件 Android开发
Eclipse创建Spring项目
本文介绍了在Eclipse中创建Spring项目的步骤,包括如何配置Tomcat服务器、创建项目、部署项目到Tomcat以及添加Spring框架所需的JAR包。
69 1
Eclipse创建Spring项目
|
2月前
|
Java Apache Maven
Java/Spring项目的包开头为什么是com?
本文介绍了 Maven 项目的初始结构,并详细解释了 Java 包命名惯例中的域名反转规则。通过域名反转(如 `com.example`),可以确保包名的唯一性,避免命名冲突,提高代码的可读性和逻辑分层。文章还讨论了域名反转的好处,包括避免命名冲突、全球唯一性、提高代码可读性和逻辑分层。最后,作者提出了一个关于包名的问题,引发读者思考。
Java/Spring项目的包开头为什么是com?
|
2月前
|
前端开发 Java 应用服务中间件
【Spring】Spring MVC的项目准备和连接建立
【Spring】Spring MVC的项目准备和连接建立
60 2
|
3月前
|
SQL 监控 druid
springboot-druid数据源的配置方式及配置后台监控-自定义和导入stater(推荐-简单方便使用)两种方式配置druid数据源
这篇文章介绍了如何在Spring Boot项目中配置和监控Druid数据源,包括自定义配置和使用Spring Boot Starter两种方法。
|
2月前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
193 2
|
26天前
|
缓存 IDE Java
SpringBoot入门(7)- 配置热部署devtools工具
SpringBoot入门(7)- 配置热部署devtools工具
38 2
 SpringBoot入门(7)- 配置热部署devtools工具