Spring Boot项目从fastjson升级到fastjson2的实战指南
1. 从fastjson到fastjson2:一次必要的技术栈升级
最近在维护一个老项目,发现日志里时不时会冒出一些关于fastjson的警告,仔细一看,是那个老生常谈的“autoType is not support”问题。这让我意识到,是时候把项目里那个“历史悠久”的fastjson 1.x彻底升级到fastjson2了。这不仅仅是换个依赖版本号那么简单,尤其是在Spring Boot项目里,它涉及到整个JSON序列化与反序列化框架的切换,从配置到行为都可能发生变化。如果你也正面临类似的技术债,或者想在新项目中直接采用更安全、性能更好的fastjson2,那么这篇从踩坑到填坑的实战记录,或许能帮你省下不少折腾的时间。我们将深入探讨如何在Spring Boot中,将默认的Jackson或老版fastjson,平滑、正确地替换为fastjson2,并处理好那些容易忽略的细节。
2. 为什么必须升级:不仅仅是漏洞修复
在动手之前,我们得先搞清楚为什么要大费周章地升级。很多人可能只知道fastjson 1.x有安全漏洞,但升级的驱动力远不止于此。
2.1 安全风险的终结
fastjson 1.x系列,尤其是1.2.80及之前的版本,因其默认开启的autoType特性,成为了反序列化攻击的重灾区。攻击者可以构造恶意的JSON字符串,在目标服务器上执行任意代码(RCE)。尽管后续版本如1.2.83、1.2.84通过黑名单、安全模式等方式进行了修复,但“打补丁”式的安全策略始终让人心存疑虑。fastjson2从架构上就移除了有风险的autoType支持(在fastjson2-extension中提供了可控的安全白名单模式),并重写了大量代码,从根本上降低了此类风险。对于企业级应用,使用一个存在严重安全历史且修复机制复杂的库,本身就是一种技术负债。
2.2 性能与内存的显著提升
fastjson2并非简单的漏洞修复版,它是一次彻底的重构。根据官方基准测试,fastjson2在序列化(对象转JSON字符串)和反序列化(JSON字符串转对象)的性能上,相比fastjson 1.x有大幅提升,部分场景下甚至优于Jackson和Gson。更重要的是其内存占用优化。fastjson2采用了更高效的内部数据结构(如JSONObject、JSONArray的实现)和缓存策略,在长时间运行、高并发的服务中,能有效降低GC压力,这对于微服务架构和云原生应用至关重要。
2.3 API设计的现代化与兼容性考量
fastjson 1.x的API设计存在一些历史包袱,例如JSON.parseObject()方法的重载过多,容易导致混淆。fastjson2提供了更清晰、一致的API,并将核心功能(fastjson2)与扩展功能(如fastjson2-extension支持autoType、kotlin、springframework支持等)分离,让依赖更干净。同时,fastjson2提供了“兼容模式”,可以部分模拟fastjson 1.x的行为,这为老旧代码的迁移提供了缓冲,但我们的目标应该是最终移除对兼容模式的依赖,完全转向新API。
3. 依赖引入与基础环境配置
升级的第一步是处理好依赖。这里最容易出错的地方就是依赖冲突和版本选择。
3.1 清理旧依赖与引入新依赖
首先,在你的pom.xml中,必须彻底移除或排除所有fastjson 1.x的依赖。常见的groupId是com.alibaba,artifactId是fastjson。
<!-- 1. 移除或注释掉旧的fastjson依赖 --> <!-- <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.83</version> </dependency> --> <!-- 2. 引入fastjson2核心库 --> <dependency> <groupId>com.alibaba.fastjson2</groupId> <artifactId>fastjson2</artifactId> <version>2.0.64</version> <!-- 建议使用最新稳定版 --> </dependency> <!-- 3. 引入fastjson2的Spring Boot集成扩展(关键!) --> <dependency> <groupId>com.alibaba.fastjson2</groupId> <artifactId>fastjson2-extension-spring5</artifactId> <version>2.0.64</version> </dependency>为什么需要extension-spring5?这个模块提供了FastJsonHttpMessageConverter等类,是fastjson2与Spring MVC(Spring Boot Web)无缝集成的桥梁。没有它,你配置的HttpMessageConverter将无法生效,Spring Boot仍然会使用默认的Jackson。
注意:如果你的项目是Spring Boot 3.x,对应的扩展模块可能是
fastjson2-extension-spring6,需要根据Spring框架的主版本进行选择。版本号务必与核心库fastjson2保持一致,避免因版本不匹配导致奇怪的ClassNotFoundException或方法签名错误。
3.2 处理潜在的依赖冲突
升级后启动应用,要特别留意控制台是否有关于JSON库的冲突警告。Spring Boot默认捆绑了Jackson,如果你不需要它,可以将其排除,但这并非必须,因为我们可以通过配置优先级让fastjson2生效。更常见的问题是其他第三方库传递依赖了旧版fastjson。你可以使用Maven命令检查依赖树:
mvn dependency:tree -Dincludes=com.alibaba:fastjson如果发现还有fastjson 1.x的依赖,需要在引入该第三方库的<dependency>中将其排除:
<dependency> <groupId>some.third.party</groupId> <artifactId>some-library</artifactId> <exclusions> <exclusion> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> </exclusion> </exclusions> </dependency>4. 核心配置:替换Spring Boot的默认HttpMessageConverter
这是让fastjson2在Spring Boot中生效最关键的一步。我们需要告诉Spring MVC,在处理HTTP请求和响应时,使用fastjson2的转换器来替代默认的Jackson转换器。
4.1 通过配置类进行全局配置(推荐)
创建一个配置类,例如FastJson2Config,通过实现WebMvcConfigurer接口来添加自定义的HttpMessageConverter。
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 FastJson2Config implements WebMvcConfigurer { @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { // 1. 创建FastJsonHttpMessageConverter实例 FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter(); // 2. 创建FastJsonConfig并配置序列化规则 FastJsonConfig config = new FastJsonConfig(); config.setDateFormat("yyyy-MM-dd HH:mm:ss"); // 设置全局日期格式 config.setCharset(StandardCharsets.UTF_8); // 设置字符集 // 关键配置:设置序列化特性 // 这里关闭了循环引用检测(根据业务需要),并美化输出(仅开发环境建议) // config.setWriterFeatures(JSONWriter.Feature.PrettyFormat); // 如果字段为null,是否序列化:Feature.WriteMapNullValue -> 输出null;不设置 -> 忽略该字段 // config.setWriterFeatures(JSONWriter.Feature.WriteMapNullValue); converter.setFastJsonConfig(config); // 3. 设置该转换器支持的MediaType(必须!否则可能不生效) List<MediaType> supportedMediaTypes = new ArrayList<>(); supportedMediaTypes.add(MediaType.APPLICATION_JSON); supportedMediaTypes.add(MediaType.APPLICATION_JSON_UTF8); // 注意:Spring Boot 2.x后已弃用,但加上兼容性好 supportedMediaTypes.add(new MediaType("application", "*+json")); converter.setSupportedMediaTypes(supportedMediaTypes); // 4. 将fastjson2转换器添加到转换器列表的最前面,确保优先级最高 converters.add(0, converter); } }几个关键点解析:
converters.add(0, converter):将我们的转换器插入到列表头部。因为Spring会按顺序使用第一个能处理当前请求/响应的转换器。这样做可以确保fastjson2优先于默认的Jackson被使用。supportedMediaTypes:必须明确设置转换器支持的媒体类型。通常application/json就足够了,添加*+json是一种更宽松的匹配策略。缺少这个设置是导致配置不生效的常见原因之一。FastJsonConfig:这是配置fastjson2行为的核心。除了日期、字符集,更重要的是通过setWriterFeatures和setReaderFeatures来配置序列化(写)和反序列化(读)的规则。例如,是否输出值为null的字段、是否对字符串进行HTML转义、是否允许反序列化未知字段等。
4.2 针对特定场景的序列化配置
全局配置满足了大部分需求,但有时我们需要对某些特定类型的序列化做特殊处理。例如,在返回一个包含BigDecimal的财务数据时,我们可能希望强制保留两位小数,而不依赖字段本身的精度。
这时,我们可以利用fastjson2的@JSONField注解或者自定义ObjectWriter。
使用@JSONField注解:
import com.alibaba.fastjson2.annotation.JSONField; public class OrderVO { private String orderId; @JSONField(format = "#0.00") // 序列化时格式化为两位小数 private BigDecimal amount; // getters and setters }设置全局的ObjectWriter或ObjectReader:如果你希望对某种类型(如LocalDateTime)应用统一的格式,可以在配置类中通过FastJsonConfig进行全局注册。
@Configuration public class FastJson2Config implements WebMvcConfigurer { @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter(); FastJsonConfig config = new FastJsonConfig(); // 注册一个全局的ObjectWriter,用于处理LocalDateTime类型 config.setWriterFilters((writer, object, field, value, format) -> { if (value instanceof LocalDateTime) { LocalDateTime dateTime = (LocalDateTime) value; writer.writeString(dateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); return false; // 表示已处理,不再执行默认序列化 } return true; // 继续执行默认序列化 }); // ... 其他配置 converter.setFastJsonConfig(config); converters.add(0, converter); } }5. 升级过程中的典型“坑”与解决方案
直接替换依赖和配置后,应用往往不会一帆风顺地跑起来。下面是我在升级过程中遇到的一些典型问题及其解决方法。
5.1 日期格式序列化不一致导致前端解析失败
这是最常见的问题。fastjson 1.x的默认日期格式可能是yyyy-MM-dd HH:mm:ss,而fastjson2或你之前配置的Jackson可能是时间戳或另一种格式。前后端交互时,日期字段的字符串形式不匹配,就会导致前端框架(如JavaScript的Date对象)解析失败。
解决方案:
- 在
FastJsonConfig中明确设置全局日期格式,如上文示例中的config.setDateFormat("yyyy-MM-dd HH:mm:ss")。这是最推荐的做法,保证输出一致性。 - 如果不同的接口需要不同的格式,可以在实体类的字段上使用
@JSONField(format = "yyyy/MM/dd")进行个性化设置。 - 在前端,如果无法控制后端格式,可以考虑使用更灵活的日期解析库(如
moment.js或day.js),或者让后端在返回前将日期格式化为明确的字符串。
5.2 属性丢失:字段名映射问题
有同学反馈,升级后某些字段在序列化成JSON时不见了(“属性丢失”),或者反序列化时无法赋值。
可能的原因和排查步骤:
- Getter/Setter方法命名不规范:fastjson2默认通过Getter/Setter方法探测属性,如果方法名不符合JavaBean规范(例如
getURL()对应的属性名是URL而不是url),可能会导致问题。检查并确保方法命名正确。 - 字段可见性:fastjson2默认可能无法序列化私有字段(除非有public的getter)。确保需要序列化的字段有对应的public getter方法。
- 使用
@JSONField注解时名称错误:检查注解上的name值是否与期望的JSON键名一致。 - 开启了
Feature.SupportNonPublicField:在fastjson 1.x中,你可能配置了此特性来序列化非公有字段。在fastjson2中,如果需要此功能,需在FastJsonConfig中配置readerFeatures或writerFeatures。但更推荐的做法是遵循JavaBean规范,暴露公有方法。 - 遇到了fastjson2的bug:在极少数情况下,可能是特定版本的bug。可以尝试升级到fastjson2的最新版本,或者在社区搜索相关issue。
5.3 从fastjson 1.x的“兼容模式”迁移
为了平滑迁移,fastjson2提供了兼容模式,可以通过在启动JVM参数或代码中设置系统属性fastjson2.compatibleMode=true来开启。这个模式会尝试模拟fastjson 1.x的一些行为(如部分API)。
但是,请注意:
- 兼容模式是临时性的解决方案,不应长期使用。它可能无法100%模拟,且可能带来性能开销。
- 开启兼容模式后,应系统性地对代码进行扫描,将使用fastjson 1.x特定API(如
JSON.parseObject(String, Class, Feature[]))的地方,逐步替换为fastjson2的等效API。 - 最终目标是在测试充分后,关闭兼容模式,完全使用fastjson2的原生模式运行。
5.4 与第三方库(如Swagger/Knife4j、RedisTemplate)的集成问题
很多库内部依赖了特定的JSON处理器。
- Swagger/Knife4j文档异常:这些API文档工具在渲染模型示例时,可能仍然使用Jackson的注解(如
@ApiModelProperty)或默认的Jackson实例。这可能导致文档中显示的JSON示例与你的实际接口返回格式不一致。通常,这不影响接口功能,只影响文档展示。可以尝试在Spring Boot配置中明确指定用于文档的ObjectMapper为fastjson2的实例,但这通常比较麻烦。一个更简单的做法是接受这种不一致,或者转而使用基于OpenAPI 3.0且对JSON库无强依赖的文档工具(如springdoc-openapi)。 - Spring Boot RedisTemplate序列化器:如果你在Redis中存储了由fastjson 1.x序列化的对象,升级到fastjson2后,由于序列化算法不同,直接反序列化会失败。你需要统一Redis的序列化器。通常,我们会为
RedisTemplate配置一个通用的StringRedisSerializer(键)和GenericJackson2JsonRedisSerializer(值)。如果你想完全使用fastjson2,可以自定义一个FastJsonRedisSerializer。更务实的建议是:对于缓存数据,升级时可以考虑清空相关缓存,让数据按新格式重新写入,避免复杂的兼容处理。
6. 功能对等与进阶配置检查
配置完成后,需要验证功能是否与升级前对等,并考虑一些进阶优化。
6.1 序列化/反序列化特性对照
在fastjson 1.x中,你可能通过SerializerFeature和ParseFeature配置了许多特性。需要找到它们在fastjson2中的对应项。fastjson2中使用的是JSONWriter.Feature和JSONReader.Feature。
常见特性迁移对照示例:
| fastjson 1.x (SerializerFeature/ParseFeature) | fastjson2 (JSONWriter.Feature/JSONReader.Feature) | 作用 |
|---|---|---|
SerializerFeature.PrettyFormat | JSONWriter.Feature.PrettyFormat | 美化输出(缩进) |
SerializerFeature.WriteMapNullValue | JSONWriter.Feature.WriteNulls | 输出值为null的字段 |
SerializerFeature.WriteDateUseDateFormat | (通过setDateFormat配置) | 使用配置的日期格式 |
SerializerFeature.DisableCircularReferenceDetect | JSONWriter.Feature.DisableCircularReferenceDetect | 禁用循环引用检测 |
ParseFeature.IgnoreNotMatch | JSONReader.Feature.IgnoreNoneSerializable | 忽略反序列化时无法匹配的字段 |
在你的FastJsonConfig中,通过config.setWriterFeatures(...)和config.setReaderFeatures(...)来设置这些特性。
6.2 自定义序列化与反序列化器
对于复杂对象(如枚举、自定义的ValueObject),你可能需要定制序列化逻辑。在fastjson2中,可以通过实现ObjectWriter和ObjectReader接口,并注册到JSONFactory中来实现。
// 1. 为MySpecialEnum编写一个自定义的Writer public class MyEnumWriter implements ObjectWriter<MySpecialEnum> { @Override public void write(JSONWriter jsonWriter, Object object, Object fieldName, Type fieldType, long features) { MySpecialEnum e = (MySpecialEnum) object; jsonWriter.writeString(e.getCode()); // 将枚举序列化为其code字段 } } // 2. 在应用启动时(如@PostConstruct方法中)注册 @PostConstruct public void registerCustomWriter() { JSONFactory.getDefaultObjectWriterProvider().register(MySpecialEnum.class, new MyEnumWriter()); }这种方式提供了极大的灵活性,但也要谨慎使用,避免过度定制导致维护复杂度增加。
7. 性能测试与监控建议
升级完成后,不能仅仅满足于功能正常,还需要关注性能表现和稳定性。
- 基准测试:使用JMH(Java Microbenchmark Harness)或简单的单元测试,对比升级前后关键接口的序列化/反序列化性能。重点关注平均响应时间和P99延迟是否有劣化。
- 内存监控:通过JVM监控工具(如VisualVM, JProfiler)或APM(应用性能管理)系统,观察升级后应用的堆内存使用情况和GC频率。fastjson2的内存优化特性应该带来积极影响。
- 全链路测试:进行全面的集成测试和回归测试,确保所有依赖JSON交互的上下游系统(前端、其他微服务)都能正常工作。特别注意那些对JSON字段顺序、格式有强依赖的“脆弱”接口。
- 灰度发布:如果条件允许,采用灰度发布策略,先将流量导入部分升级后的实例,观察日志和监控指标,确认无误后再全量升级。
将Spring Boot项目的JSON处理器从fastjson 1.x或Jackson迁移到fastjson2,是一个系统性工程,涉及依赖管理、核心配置、行为兼容和性能验证。整个过程的核心思路是:先保证功能对等,再追求优化与安全。通过清晰的配置、对常见问题的预判以及细致的测试,这次升级不仅能消除安全漏洞,还能为应用带来性能提升和更现代的代码依赖。最关键的是,在配置FastJsonHttpMessageConverter时,务必记得设置supportedMediaTypes并将其放在转换器列表首位,这是许多配置失效问题的根源。在实际操作中,建议建立一个与生产环境相似的测试环境进行充分验证,毕竟JSON处理是Web应用的血液,它的稳定性至关重要。
