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. 小小改动,往往具有大大能量。对未知应具有敬畏之心,小心为之。
相关文章
|
5月前
|
前端开发 Java 应用服务中间件
《深入理解Spring》 Spring Boot——约定优于配置的革命者
Spring Boot基于“约定优于配置”理念,通过自动配置、起步依赖、嵌入式容器和Actuator四大特性,简化Spring应用的开发与部署,提升效率,降低门槛,成为现代Java开发的事实标准。
|
5月前
|
前端开发 Java 微服务
《深入理解Spring》:Spring、Spring MVC与Spring Boot的深度解析
Spring Framework是Java生态的基石,提供IoC、AOP等核心功能;Spring MVC基于其构建,实现Web层MVC架构;Spring Boot则通过自动配置和内嵌服务器,极大简化了开发与部署。三者层层演进,Spring Boot并非替代,而是对前者的高效封装与增强,适用于微服务与快速开发,而深入理解Spring Framework有助于更好驾驭整体技术栈。
|
5月前
|
XML Java 应用服务中间件
【SpringBoot(一)】Spring的认知、容器功能讲解与自动装配原理的入门,带你熟悉Springboot中基本的注解使用
SpringBoot专栏开篇第一章,讲述认识SpringBoot、Bean容器功能的讲解、自动装配原理的入门,还有其他常用的Springboot注解!如果想要了解SpringBoot,那么就进来看看吧!
597 2
|
6月前
|
人工智能 Java 机器人
基于Spring AI Alibaba + Spring Boot + Ollama搭建本地AI对话机器人API
Spring AI Alibaba集成Ollama,基于Java构建本地大模型应用,支持流式对话、knife4j接口可视化,实现高隐私、免API密钥的离线AI服务。
5044 2
基于Spring AI Alibaba + Spring Boot + Ollama搭建本地AI对话机器人API
存储 JSON Java
751 0
|
6月前
|
人工智能 Java 开发者
【Spring】原理解析:Spring Boot 自动配置
Spring Boot通过“约定优于配置”的设计理念,自动检测项目依赖并根据这些依赖自动装配相应的Bean,从而解放开发者从繁琐的配置工作中解脱出来,专注于业务逻辑实现。
2130 0
|
11月前
|
前端开发 Java Maven
Spring 和 Spring Boot 之间的比较
本文对比了标准Spring框架与Spring Boot的区别,重点分析两者在模块使用(如MVC、Security)上的差异。Spring提供全面的Java开发基础设施支持,包含依赖注入和多种开箱即用的模块;而Spring Boot作为Spring的扩展,通过自动配置、嵌入式服务器等功能简化开发流程。文章还探讨了两者的Maven依赖、Mvc配置、模板引擎配置、启动方式及打包部署等方面的异同,展示了Spring Boot如何通过减少样板代码和配置提升开发效率。总结指出,Spring Boot是Spring的增强版,使应用开发、测试与部署更加便捷高效。
1439 11
|
12月前
|
安全 Java Apache
微服务——SpringBoot使用归纳——Spring Boot中集成 Shiro——Shiro 身份和权限认证
本文介绍了 Apache Shiro 的身份认证与权限认证机制。在身份认证部分,分析了 Shiro 的认证流程,包括应用程序调用 `Subject.login(token)` 方法、SecurityManager 接管认证以及通过 Realm 进行具体的安全验证。权限认证部分阐述了权限(permission)、角色(role)和用户(user)三者的关系,其中用户可拥有多个角色,角色则对应不同的权限组合,例如普通用户仅能查看或添加信息,而管理员可执行所有操作。
584 0
|
12月前
|
安全 Java 数据安全/隐私保护
微服务——SpringBoot使用归纳——Spring Boot中集成 Shiro——Shiro 三大核心组件
本课程介绍如何在Spring Boot中集成Shiro框架,主要讲解Shiro的认证与授权功能。Shiro是一个简单易用的Java安全框架,用于认证、授权、加密和会话管理等。其核心组件包括Subject(认证主体)、SecurityManager(安全管理员)和Realm(域)。Subject负责身份认证,包含Principals(身份)和Credentials(凭证);SecurityManager是架构核心,协调内部组件运作;Realm则是连接Shiro与应用数据的桥梁,用于访问用户账户及权限信息。通过学习,您将掌握Shiro的基本原理及其在项目中的应用。
431 0

热门文章

最新文章