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. 小小改动,往往具有大大能量。对未知应具有敬畏之心,小心为之。
相关文章
|
18天前
|
XML Java 应用服务中间件
Spring Boot 两种部署到服务器的方式
本文介绍了Spring Boot项目的两种部署方式:jar包和war包。Jar包方式使用内置Tomcat,只需配置JDK 1.8及以上环境,通过`nohup java -jar`命令后台运行,并开放服务器端口即可访问。War包则需将项目打包后放入外部Tomcat的webapps目录,修改启动类继承`SpringBootServletInitializer`并调整pom.xml中的打包类型为war,最后启动Tomcat访问应用。两者各有优劣,jar包更简单便捷,而war包适合传统部署场景。需要注意的是,war包部署时,内置Tomcat的端口配置不会生效。
138 17
Spring Boot 两种部署到服务器的方式
|
12天前
|
监控 Java 应用服务中间件
SpringBoot是如何简化Spring开发的,以及SpringBoot的特性以及源码分析
Spring Boot 通过简化配置、自动配置和嵌入式服务器等特性,大大简化了 Spring 应用的开发过程。它通过提供一系列 `starter` 依赖和开箱即用的默认配置,使开发者能够更专注于业务逻辑而非繁琐的配置。Spring Boot 的自动配置机制和强大的 Actuator 功能进一步提升了开发效率和应用的可维护性。通过对其源码的分析,可以更深入地理解其内部工作机制,从而更好地利用其特性进行开发。
32 6
|
25天前
|
缓存 安全 Java
Spring Boot 3 集成 Spring Security + JWT
本文详细介绍了如何使用Spring Boot 3和Spring Security集成JWT,实现前后端分离的安全认证概述了从入门到引入数据库,再到使用JWT的完整流程。列举了项目中用到的关键依赖,如MyBatis-Plus、Hutool等。简要提及了系统配置表、部门表、字典表等表结构。使用Hutool-jwt工具类进行JWT校验。配置忽略路径、禁用CSRF、添加JWT校验过滤器等。实现登录接口,返回token等信息。
239 12
|
1月前
|
存储 安全 Java
Spring Boot 3 集成Spring AOP实现系统日志记录
本文介绍了如何在Spring Boot 3中集成Spring AOP实现系统日志记录功能。通过定义`SysLog`注解和配置相应的AOP切面,可以在方法执行前后自动记录日志信息,包括操作的开始时间、结束时间、请求参数、返回结果、异常信息等,并将这些信息保存到数据库中。此外,还使用了`ThreadLocal`变量来存储每个线程独立的日志数据,确保线程安全。文中还展示了项目实战中的部分代码片段,以及基于Spring Boot 3 + Vue 3构建的快速开发框架的简介与内置功能列表。此框架结合了当前主流技术栈,提供了用户管理、权限控制、接口文档自动生成等多项实用特性。
73 8
|
2月前
|
缓存 前端开发 Java
【Spring】——SpringBoot项目创建
SpringBoot项目创建,SpringBootApplication启动类,target文件,web服务器,tomcat,访问服务器
|
2月前
|
负载均衡 Java 开发者
深入探索Spring Cloud与Spring Boot:构建微服务架构的实践经验
深入探索Spring Cloud与Spring Boot:构建微服务架构的实践经验
190 5
|
6月前
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
3月前
|
JSON 数据格式 索引
Python中序列化/反序列化JSON格式的数据
【11月更文挑战第4天】本文介绍了 Python 中使用 `json` 模块进行序列化和反序列化的操作。序列化是指将 Python 对象(如字典、列表)转换为 JSON 字符串,主要使用 `json.dumps` 方法。示例包括基本的字典和列表序列化,以及自定义类的序列化。反序列化则是将 JSON 字符串转换回 Python 对象,使用 `json.loads` 方法。文中还提供了具体的代码示例,展示了如何处理不同类型的 Python 对象。
|
3月前
|
存储 安全 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第22天】在Java的世界里,对象序列化和反序列化是数据持久化和网络传输的关键技术。本文将带你了解如何在Java中实现对象的序列化与反序列化,并探讨其背后的原理。通过实际代码示例,我们将一步步展示如何将复杂数据结构转换为字节流,以及如何将这些字节流还原为Java对象。文章还将讨论在使用序列化时应注意的安全性问题,以确保你的应用程序既高效又安全。
|
4月前
|
存储 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第9天】在Java的世界里,对象序列化是连接数据持久化与网络通信的桥梁。本文将深入探讨Java对象序列化的机制、实践方法及反序列化过程,通过代码示例揭示其背后的原理。从基础概念到高级应用,我们将一步步揭开序列化技术的神秘面纱,让读者能够掌握这一强大工具,以应对数据存储和传输的挑战。