news 2026/9/26 18:25:04

SpringBoot3整合FastJSON2:configureMessageConverters配置实战与避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot3整合FastJSON2:configureMessageConverters配置实战与避坑指南

SpringBoot 3 项目升级后,原来在 SpringBoot 2 里用得挺顺的 FastJson 突然不干活了,接口要么返回一串 Jackson 的默认格式,要么直接报错,排查几圈下来发现核心问题就出在configureMessageConverters这个配置方法上。这篇内容就围绕 SpringBoot3 整合 FastJSON2 的完整配置方式展开,重点讲清楚configureMessageConverters怎么写、参数怎么调、踩过哪些坑,以及怎么和logback-spring.xml、log4j2这类日志配置和平共处,适合正在做 SpringBoot 版本升级、或者想把接口 JSON 序列化换成 FastJSON2 的朋友参考。

1. 换到SpringBoot3后,FastJSON2的集成方式为什么变了

1.1 从Spring5到Spring6,Fastjson扩展包必须跟着换

SpringBoot3 底层的 Spring 框架直接从 Spring5 跳到了 Spring6,包名从javax.*切成了jakarta.*,很多第三方组件的适配方式也跟着调整。Fastjson2 比较特殊,它把 Spring 的集成扩展包拆成了两套:

  • 给 Spring5 及以下用的:fastjson2-extension-spring5
  • 给 Spring6 / SpringBoot3 用的:fastjson2-extension-spring6

很多人升级以后只改了 fastjson2 的主包版本,把com.alibaba.fastjson2:fastjson2升到最新,结果FastJsonHttpMessageConverter一直包不存在或者方法签名不对,原因就是没有引入对应的 Spring6 扩展包。我在项目里的依赖是这样写的:

<dependency> <groupId>com.alibaba.fastjson2</groupId> <artifactId>fastjson2</artifactId> <version>2.0.53</version> </dependency> <dependency> <groupId>com.alibaba.fastjson2</groupId> <artifactId>fastjson2-extension-spring6</artifactId> <version>2.0.53</version> </dependency>

主包和扩展包的版本最好保持一致,避免出现莫名其妙的方法找不到问题。如果项目里还同时用了 fastjson1 的老代码,不要顺手再引fastjson那个老包,两套序列化器混在一起会让排查成本直接翻倍。

1.2 SpringBoot3自动配置不再照顾第三方JSON库

SpringBoot 2 时代用了spring.mvc.converters.preferred-json-mapper=fastjson这种配置方式,配合第三方 starter 还能勉强生效。到了 SpringBoot3,官方文档和自动配置逻辑里基本只保证 Jackson、Gson、JSON-B 这几类内置 JSON 方案,Fastjson2 没有被纳入HttpMessageConvertersAutoConfiguration的候选列表。

preferred-json-mapper这个配置项虽然还在,但对 Fastjson 这种非官方库来说基本等于摆设。真正可靠的做法是在WebMvcConfigurer里手动接管configureMessageConverters方法,把 FastJSON2 的转换器注册进去。

另外要提醒一点:如果实现了WebMvcConfigurer后又去继承WebMvcConfigurationSupport,SpringBoot 的 MVC 自动配置会整体失效。常见表现是静态资源访问不了、拦截器失效、接口路径规则变化。项目里只要配置了 FastJSON2,就不要再去碰WebMvcConfigurationSupport,两者叠加很容易把环境搞成“看似配置了但实际没生效”的诡异状态。

2. configureMessageConverters和extendMessageConverters,选错会踩大坑

2.1 两个方法的语义区别

WebMvcConfigurer里提供了两个可以注册消息转换器的方法:

  • configureMessageConverters:先由 SpringMVC 创建默认转换器列表,再交给这个方法处理。如果方法内部调用了super.configureMessageConverters(converters),默认列表会保留;如果不调用,列表就会被清空,完全由自己填充。
  • extendMessageConverters:在默认转换器列表已经构建完成之后追加自定义转换器,不会影响默认列表的完整性。

简单理解:configureMessageConverters是“总闸”,extendMessageConverters是“分流口”。如果只是想加一个 FastJSON2,不需要动默认逻辑,用extendMessageConverters更省事。但既然这篇是专门讲configureMessageConverters,就要搞清楚它背后的几个细节。

2.2 什么时候用configure,什么时候用extend

我实际项目中遇到的情况是:项目里接入了很多老接口,有的依赖 FastJson 的@JSONField注解,有的依赖自定义ValueFilter,还有的依赖 long 转 string 的全局规则。这些需求用extendMessageConverters也能做,但如果默认 Jackson 转换器排在前面,SpringMVC 根据请求的Accept头和返回类型选择转换器时,可能优先选中 Jackson,FastJSON2 的全局配置就完全不会生效。

所以“想彻底替换默认 JSON 行为”的场景,必须走configureMessageConverters,并且在方法里先调用super.configureMessageConverters(converters),再把 FastJSON2 转换器放到列表首位。这样既保留原有转换器兜底,又能让 FastJSON2 拿到最高优先级。

@Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { super.configureMessageConverters(converters); FastJsonHttpMessageConverter fastJsonConverter = new FastJsonHttpMessageConverter(); FastJsonConfig config = new FastJsonConfig(); // ... 配置参数 fastJsonConverter.setFastJsonConfig(config); converters.add(0, fastJsonConverter); }

如果只是某个模块或者某个接口想用 FastJSON2,其他地方保持默认,那用extendMessageConverters加在列表最后就够了,影响范围更小,也方便以后单独下线。总之,默认行为与全局需求冲突时优先configureMessageConverters,局部增强需求优先extendMessageConverters。

3. SpringBoot3整合FastJSON2配置实操

3.1 完整配置类:configureMessageConverters写法

先给出一份可以直接复制到项目里的完整配置类。这份配置包含了字符集、日期格式、空值输出、支持的 MediaType 等几项核心参数:

import com.alibaba.fastjson2.JSONReader; import com.alibaba.fastjson2.JSONWriter; import com.alibaba.fastjson2.support.config.FastJsonConfig; import com.alibaba.fastjson2.support.spring.http.converter.FastJsonHttpMessageConverter; import org.springframework.context.annotation.Configuration; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; @Configuration public class WebJsonConfig implements WebMvcConfigurer { @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { // 保留默认转换器,避免覆盖后丢失其他类型处理能力 super.configureMessageConverters(converters); FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter(); FastJsonConfig fastJsonConfig = new FastJsonConfig(); // 全局日期格式,覆盖 java.util.Date、LocalDate、LocalDateTime fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss"); fastJsonConfig.setCharset(StandardCharsets.UTF_8); // 序列化时输出为空的字段,如果不想输出 null 字段可以去掉这一行 fastJsonConfig.setWriterFeatures( JSONWriter.Feature.WriteMapNullValue ); // 反序列化时忽略未知字段,避免接口传入多余字段时报错 fastJsonConfig.setReaderFeatures( JSONReader.Feature.IgnoreNoneSerializable, JSONReader.Feature.FieldBased ); converter.setFastJsonConfig(fastJsonConfig); converter.setDefaultCharset(StandardCharsets.UTF_8); // 明确支持的 MediaType,防止响应 Content-Type 出现乱码 List<MediaType> mediaTypes = new ArrayList<>(); mediaTypes.add(MediaType.APPLICATION_JSON); mediaTypes.add(MediaType.valueOf("application/json;charset=UTF-8")); mediaTypes.add(MediaType.valueOf("application/x-www-form-urlencoded")); converter.setSupportedMediaTypes(mediaTypes); // 放到最前面,覆盖 Jackson 的优先匹配 converters.add(0, converter); } }

这份配置有几个细节需要特别说明:

super.configureMessageConverters(converters)这一步不能省。如果不调用 super,SpringMVC 默认的StringHttpMessageConverter、ByteArrayHttpMessageConverter、MappingJackson2HttpMessageConverter等都会被丢掉,接口返回 String 类型时可能直接变成 JSON 字符串带引号,或者出现“返回的内容被二次序列化”的怪事。

converters.add(0, converter)也很关键。FastJSON2 转换器虽然支持application/json,但 SpringMVC 会按列表顺序匹配转换器,默认 Jackson 排在前面的话,FastJSON2 的全局特性就用不上。把它放在索引 0,就能保证优先匹配。

3.2 配置参数细节:日期、空值、Long精度

FastJSON2 的FastJsonConfig提供了setWriterFeatures和setReaderFeatures两个入口,分别控制序列化和反序列化行为。实际项目里最常用的几个特性如下:

特性作用常见用法
JSONWriter.Feature.WriteMapNullValue序列化时输出值为 null 的字段前端需要判断字段存在性时使用
JSONWriter.Feature.WriteNullStringAsEmptynull 字符串输出为空串对老接口兼容性要求高时使用
JSONWriter.Feature.WriteNullListAsEmptynull 列表输出为空数组返回列表字段时建议开启
JSONWriter.Feature.PrettyFormat格式化 JSON 输出调试阶段方便看结构
JSONReader.Feature.IgnoreNoneSerializable忽略没有实现 Serializable 的字段处理第三方类时防止序列化失败
JSONReader.Feature.FieldBased基于字段反序列化,不依赖 setter处理只有 getter 的只读对象时很香

日期格式方面,fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss")是全局日期格式,但如果某些字段需要返回时间戳或者yyyy-MM-dd,可以用@JSONField(format = "yyyy-MM-dd")单独覆盖:

import com.alibaba.fastjson2.annotation.JSONField; public class UserVO { @JSONField(format = "yyyy-MM-dd") private LocalDate birthday; @JSONField(format = "yyyy-MM-dd HH:mm:ss") private LocalDateTime createTime; }

这里要特别提醒:FastJSON2 对LocalDateTime和Date的处理方式不太一样。只设置全局 dateFormat 时,LocalDateTime有可能被序列化成数组格式或者 ISO 字符串,最好在实体字段上显式加@JSONField(format = "yyyy-MM-dd HH:mm:ss"),省得联调时被前端追问“为什么日期变成了一串数字”。

Long 精度丢失是另一个高频问题。前端 JavaScript 的 Number 类型只能安全表示2^53以内的整数,后端主键如果用了雪花算法生成 Long 类型,转成 JSON 后前端会拿到一个不精确的数值。FastJSON2 里可以通过ValueFilter把超过安全范围的 Long 转成字符串:

import com.alibaba.fastjson2.filter.ValueFilter; public class LongToStringFilter implements ValueFilter { private static final long MAX_JS_SAFE_NUMBER = 9007199254740991L; @Override public Object process(Object object, String name, Object value) { if (value instanceof Long) { Long longValue = (Long) value; if (longValue > MAX_JS_SAFE_NUMBER || longValue < -MAX_JS_SAFE_NUMBER) { return longValue.toString(); } } return value; } }

然后在配置类里把 filter 挂到FastJsonConfig:

fastJsonConfig.setWriterFilters(new LongToStringFilter());

这样全局生效,不需要在每个实体类的主键字段上手动加注解,省心很多。

3.3 局部使用FastJSON2的两种姿势

如果不想全局替换 Jackson,只想在某些接口里用 FastJSON2,有两种轻量方式。

第一种是在 Controller 方法上直接返回 String,然后用 FastJSON2 工具类序列化对象:

@GetMapping("/user") public String getUser() { UserVO user = userService.getById(1L); return JSON.toJSONString(user); }

这种方式简单直接,但丢失了@JSONField之外的全局配置,日期和空值策略都需要自己处理。适合临时验证或个别接口特殊处理。

第二种是使用@JSONField注解配合局部HttpMessageConverter。比如某个接口的 DTO 只希望在当前 Controller 内使用 FastJSON2 序列化,可以在配置类里注册一个局部变量,或者直接通过MappingJacksonValue手动指定序列化器。实际项目中用第一种方式更多,因为大部分团队更看重统一规范和排查效率。

3.4 配置后如何验证是否生效

配置完成后,最直接的验证方式是在任意 Controller 里返回一个包含 null 字段和 LocalDateTime 字段的对象,看返回 JSON 是否符合预期。

@RestController public class TestController { @GetMapping("/test") public Map<String, Object> test() { Map<String, Object> result = new LinkedHashMap<>(); result.put("name", "张三"); result.put("age", null); result.put("now", LocalDateTime.now()); return result; } }

如果配置生效,返回结果应该是name和now有值,age字段也会在启用了WriteMapNullValue的情况下出现,日期格式是yyyy-MM-dd HH:mm:ss。如果返回内容还是 Jackson 风格(例如now变成时间戳数组),说明转换器优先级没调整对,检查converters.add(0, converter)是否真的执行了。

4. 我踩过的几个坑,照着查能省半天时间

4.1 配置完不生效

最常见的原因有三个:

第一,项目里存在多个配置类同时实现了WebMvcConfigurer,后加载的配置类把先加载的转换器列表覆盖了。这种情况可以通过在configureMessageConverters方法里加日志输出来排查,看看执行顺序和最终列表里的转换器个数。

第二,WebMvcConfigurationSupport被某个配置类继承了,导致自动配置失效。需要全局搜索extends WebMvcConfigurationSupport的类,确认是不是以前的旧代码遗留下来的。

第三,SpringBoot 3 里如果使用了spring-boot-starter-webflux(响应式 Web)而不是spring-boot-starter-web,WebMvcConfigurer这套配置根本不生效,因为 WebFlux 走的是WebFluxConfigurer和ReactiveHttpMessageConverter那套机制。项目如果同时引入了 web 和 webflux,配置方向要确认清楚。

4.2 LocalDateTime序列化成奇怪格式

FastJSON2 对 Java8 时间类型的支持依赖fastjson2主包里的Jdk8TimeModule,但该模块在某些版本里默认不自动注册,需要手动激活:

FastJsonConfig config = new FastJsonConfig(); config.setWriterFeatures(JSONWriter.Feature.WriteClassName);

不过更稳妥的做法是实体字段上加@JSONField(format = "yyyy-MM-dd HH:mm:ss")。全局配置和字段注解同时存在时,字段注解优先级更高,这样就不会因为某个 LocalDateTime 字段没加注解而出现格式混乱。

4.3 Long精度丢失

前面提到了LongToStringFilter,但如果项目里某个字段只是偶尔超长,也可以直接在字段上加注解:

@JSONField(serializeUsing = ToStringSerializer.class) private Long orderId;

ToStringSerializer类在 fastjson2 的扩展包里,包路径是com.alibaba.fastjson2.support.spring.http.converter下面的工具类,实际使用时要引入fastjson2-extension-spring6依赖。对于只需要处理个别字段的场景,局部注解比全局 filter 影响范围小,推荐优先考虑。

4.4 null字段凭空消失

FastJSON2 默认对 null 字段的输出策略和 FastJSON1 不完全一样。FastJSON1 需要WriteMapNullValue才能输出 null,FastJSON2 在某些配置组合下即使设置了WriteMapNullValue,Map 里的 null 值和 Bean 里的 null 值行为也可能不同。实际联调时如果发现前端拿不到某些字段,优先检查setWriterFeatures里有没有JSONWriter.Feature.WriteMapNullValue,以及该字段是不是 Map 类型。

另外,如果 bean 里设置了@JSONField(serialize = false)的字段,这类字段无论怎么配都不会输出,不要误认为是转换器配置问题。

4.5 升级后日志配置一起坏掉的连带问题

标题里提到springboot3 log4j2和springboot3 logback-spring.xml,这个搜索热度不是没道理的。升级 SpringBoot3 时,很多人会顺手调整日志框架,最常见的问题是logback-spring.xml里的 Spring 占位符失效,或者引入了 log4j2 依赖后日志完全静默。

我用 FastJSON2 做接口序列化的项目里就遇到过:日志框架切换时,因为 pom 里没有排除spring-boot-starter-logging,导致 logback 的配置根本不加载。处理方式很简单,在引入 log4j2 时显式排除掉默认 logback:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j2</artifactId> </dependency>

如果继续使用 logback,则要检查logback-spring.xml里是否引用了application.yml中不存在的属性。SpringBoot3 里部分配置项名字做了调整,比如logging.level相关配置没有变,但server.port在 logback 里通过${server.port}引用时可能拿不到值,需要改成${SERVER_PORT}环境变量或者硬编码测试端口。

日志配置和 JSON 配置本身没有直接关系,但经常在同一轮升级中一起被改坏,所以排查问题时把日志和消息转换器分开看,不要在一个问题上钻牛角尖。

4.6 配置与Swagger接口文档冲突

项目里如果使用了springdoc-openapi这类接口文档组件,替换掉 Jackson 转换器后,Swagger 的 JSON 响应可能格式异常,甚至接口文档加载不出来。这是因为springdoc内部也依赖 Jackson 来序列化文档对象。

我的处理方案是:configureMessageConverters里保留super的默认转换器,同时把 FastJSON2 放到最前面。这样业务接口走 FastJSON2,而 springdoc 内部如果显式注入了 Jackson 转换器,还能继续工作。如果业务接口确实需要 FastJSON2,而文档接口需要 Jackson,还可以在 springdoc 的配置类里单独指定springdoc.api-docs.path对应的转换器,避免全局互相干扰。

5. 几种替代方案对比,为什么我最终留在fastjson2

5.1 与Jackson对比

Jackson 是 SpringBoot 默认方案,复杂对象和泛型处理能力很强,生态也最完整。但 Jackson 的ObjectMapper配置往往需要在多个地方定制,尤其是 LocalDateTime 序列化、null 值处理、字段命名策略等。FastJSON2 的优势在于 API 风格比较集中,FastJsonConfig一个类就能搞定大部分全局配置,加上JSONReader.Feature.FieldBased这类快速增强特性,在处理老系统遗留的 getter/setter 不规范的类时很顺手。

性能上两者在实际业务中差别不大,除非是超大量 JSON 吞吐的网关或者性能敏感场景,否则不需要单纯为了性能从 Jackson 切换到 FastJSON2。

5.2 与Gson/JSON-B对比

Gson 轻量但对 Java8 时间类型支持不够完整,JSON-B 在 SpringBoot 里的自动配置并不成熟。FastJSON2 的社区文档和中文资料更多,遇到问题搜索方案也比较快,这是国内团队选择它的一个实际理由。

5.3 我的选择标准

FastJSON2 适合以下情况:

  • 项目里已经有大量@JSONField注解和自定义 filter 的老代码
  • 需要全局统一日期格式、null 输出策略、Long 精度处理
  • 团队成员对 Fastjson 系列 API 更熟悉,维护成本更低
  • 需要针对第三方的只读对象或者不规范 bean 做反序列化

如果项目从零开始,团队对 Jackson 也很熟,那优先用 SpringBoot3 自带 Jackson 完全没问题。我这个项目之所以坚持 FastJSON2,主要是因为历史 DTO 里的@JSONField注解太多,迁移成本高,而且部分接口依赖 Fastjson2 对Map和JSONObject的特殊处理。

6. 实际项目中的一点额外建议

在 SpringBoot3 里配置 FastJSON2 这步做完以后,我建议顺手做两件小事:

第一,统一封装一个FastJsonConfig工具方法。因为项目里可能不止 WebMvc 在用 FastJSON2,日志打印、定时任务、MQ 消息体序列化都可能用到。把这部分配置抽成一个静态方法,返回配置好的FastJsonConfig,WebMvc 和业务代码共用一套配置,避免出现“接口返回的日期格式和日志打印的日期格式不一致”这种问题。

第二,加一个集成测试来防止回归。比如写一个@SpringBootTest,通过MockMvc请求一个返回LocalDateTime和null字段的接口,断言 JSON 里的关键字段。以后有人不小心改了转换器配置或者调整了依赖顺序,测试能第一时间暴露问题,不用等前端联调才发现。

我自己的实操体会是,configureMessageConverters这块配置本身不难,真正消耗时间的是理解 SpringMVC 消息转换器的工作机制,以及和 SpringBoot3 自动配置之间的优先级关系。先把转换器列表的匹配顺序吃透,再动手配置 FastJSON2,基本能把 90% 的兼容性问题挡在门外。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/26 18:22:56

Kimi为何把8+9算成18?大语言模型的数学盲点与避坑指南

1. 一次让人怀疑人生的Kimi计算翻车1.1 现场还原&#xff1a;我问了什么&#xff0c;它回了什么前两天我在电脑前整理一份数据表&#xff0c;顺手想用Kimi验证一个非常简单的算式&#xff1a;8加9到底等于多少。问题刚敲出去我就觉得自己有点多余&#xff0c;这种题连小学生都能…

作者头像 李华
网站建设 2026/9/26 18:22:00

S7-1500编程语言选型指南:LAD、FBD、SCL如何选择与混用

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/26 18:21:56

SQL 2000.zip 处理指南:识别、安装、恢复与迁移

简介&#xff1a;该压缩包为SQL Server 2000相关资源包&#xff0c;面向需要维护旧系统或学习早期数据库技术的IT人员、DBA及数据库初学者。包体约400.83MB&#xff0c;采用zip格式封装&#xff0c;便于离线保存与查阅。目前已有140人学习。资源内容聚焦SQL 2000的核心功能体系…

作者头像 李华
网站建设 2026/9/26 18:20:16

C++编译器优化策略:从优化档位到未定义行为与性能实践

刚开始入行的时候&#xff0c;我以为“编译器优化策略”就是编译时多开几个优化选项&#xff0c;比如默认的 -O2、猛一点的 -O3&#xff0c;事情就这么简单。直到后来在项目里遇到一个诡异问题&#xff1a;Debug 版一切正常&#xff0c;Release 版却偶尔崩溃&#xff0c;而且崩…

作者头像 李华