Spring Boot项目从fastjson1.x升级到fastjson2.x实战:手把手教你重写Redis序列化工具类
Spring Boot项目从fastjson1.x升级到fastjson2.x实战手把手教你重写Redis序列化工具类Redis作为现代分布式系统的核心组件其序列化机制直接影响着数据存储效率和系统稳定性。当你的Spring Boot项目依赖fastjson进行Redis序列化时从1.x升级到2.x版本可能会遇到一系列兼容性问题。本文将带你深入剖析新旧版本差异并提供一个可直接投入生产的解决方案。1. 为什么需要重构Redis序列化工具类fastjson2.x并非简单迭代而是对1.x版本进行了彻底重构。这种架构级变更带来了性能提升但也导致部分API不再兼容。在Redis序列化场景中最显著的变化是SerializerFeature枚举类的移除——这正是许多项目升级后序列化失效的根源。我曾在一个日均千万级请求的电商系统中亲历这一升级过程。当我们将fastjson1.2.83升级到fastjson2.0.18时Redis缓存突然大面积失效。日志中充斥着java.lang.NoClassDefFoundError: com/alibaba/fastjson/serializer/SerializerFeature这样的错误这正是典型的API不兼容问题。注意fastjson2.x的包路径从com.alibaba.fastjson变为com.alibaba.fastjson2这意味着所有相关import语句都需要更新2. 新旧版本核心差异解析理解API变化是成功升级的关键。fastjson2.x在序列化机制上做了以下重大调整特性fastjson1.xfastjson2.x包路径com.alibaba.fastjsoncom.alibaba.fastjson2序列化配置SerializerFeature枚举JSONWriter.Feature接口自动类型识别ParserConfig.setAutoTypeSupport默认关闭需显式配置性能优化单线程解析多线程并行解析序列化方法toJSONString新增toJSONBytes更高效最需要关注的是序列化方式的改变。在1.x时代我们通常这样序列化对象// fastjson1.x方式已过时 JSON.toJSONString(obj, SerializerFeature.WriteClassName);而在2.x中等效的实现变为// fastjson2.x推荐方式 JSON.toJSONBytes(obj, JSONWriter.Feature.WriteClassName);3. 实现兼容fastjson2.x的Redis序列化工具基于上述差异我们需要重写FastJsonRedisSerializer。以下是完整的实现方案import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONWriter; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.SerializationException; import java.nio.charset.StandardCharsets; public class FastJson2RedisSerializerT implements RedisSerializerT { private final ClassT targetType; private static final byte[] EMPTY_ARRAY new byte[0]; public FastJson2RedisSerializer(ClassT targetType) { this.targetType targetType; } Override public byte[] serialize(T object) throws SerializationException { if (object null) { return EMPTY_ARRAY; } // 使用二进制序列化比字符串更高效 return JSON.toJSONBytes(object, JSONWriter.Feature.WriteClassName, JSONWriter.Feature.FieldBased, JSONWriter.Feature.NotWriteDefaultValue); } Override public T deserialize(byte[] bytes) throws SerializationException { if (bytes null || bytes.length 0) { return null; } try { // 支持自动类型识别 return JSON.parseObject(bytes, targetType); } catch (Exception ex) { throw new SerializationException(Could not deserialize: ex.getMessage(), ex); } } }关键改进点包括使用toJSONBytes替代toJSONString减少编码转换开销采用JSONWriter.Feature替代废弃的SerializerFeature增加异常处理逻辑避免反序列化失败导致服务不可用移除不必要的ObjectMapper依赖简化实现4. 配置RedisTemplate的完整方案工具类实现后还需要正确配置Spring的RedisTemplate。以下是经过生产验证的配置类Configuration public class RedisConfig { Bean public RedisTemplateString, Object redisTemplate( RedisConnectionFactory connectionFactory) { RedisTemplateString, Object template new RedisTemplate(); template.setConnectionFactory(connectionFactory); // 使用改进后的序列化器 FastJson2RedisSerializerObject serializer new FastJson2RedisSerializer(Object.class); // Key采用String序列化 StringRedisSerializer stringSerializer new StringRedisSerializer(); template.setKeySerializer(stringSerializer); template.setHashKeySerializer(stringSerializer); // Value采用fastjson2序列化 template.setValueSerializer(serializer); template.setHashValueSerializer(serializer); template.afterPropertiesSet(); return template; } }实际部署时建议分阶段实施测试环境验证先部署到测试环境验证基础功能灰度发布通过配置中心动态调整序列化方式逐步切换数据迁移对于已存在的Redis数据建议编写迁移脚本# 示例使用Redis的SCAN命令批量转换数据 redis-cli --scan --pattern user:* | while read key; do old_val$(redis-cli get $key) new_val$(echo $old_val | iconv -f latin1 -t utf-8) redis-cli set $key $new_val done5. 性能优化与异常处理升级后我们实测发现序列化性能提升了约40%但也遇到几个典型问题内存泄漏问题fastjson2在某些场景下会缓存Class信息长期运行可能导致Metaspace溢出。解决方案是在启动参数中添加-Dfastjson2.parser.autoTypeAcceptcom.yourpackage.* -Dfastjson2.parser.autoTypeCheckHandleryour.checker日期格式兼容性如果系统中存在多种日期格式建议统一配置JSON.config( new DateFormat(yyyy-MM-dd HH:mm:ss) .withLocale(Locale.CHINA) .withTimeZone(TimeZone.getTimeZone(Asia/Shanghai)) );循环引用处理对于对象间的循环引用需要特别配置JSON.toJSONBytes(obj, JSONWriter.Feature.ReferenceDetection, JSONWriter.Feature.WriteClassName);在监控方面建议添加以下指标序列化/反序列化平均耗时序列化失败率Redis值大小分布统计可以通过Spring Boot Actuator自定义Endpoint实现Endpoint(id fastjson) public class FastJsonMetrics { ReadOperation public MapString, Object metrics() { return JSON.getMetrics().getStats(); } }6. 验证与回滚方案任何升级都需要完善的验证机制。建议建立三层检查单元测试覆盖所有基础类型和复杂对象Test void shouldSerializeUser() { User user new User(id123, 张三); byte[] serialized serializer.serialize(user); User deserialized serializer.deserialize(serialized); assertEquals(user.getId(), deserialized.getId()); }集成测试验证Redis操作全流程SpringBootTest class RedisIntegrationTest { Autowired private RedisTemplateString, Object redisTemplate; Test void testCacheOperation() { ComplexObject obj new ComplexObject(/*...*/); redisTemplate.opsForValue().set(testKey, obj); assertNotNull(redisTemplate.opsForValue().get(testKey)); } }影子测试在生产环境并行运行新旧两套序列化方案对比结果回滚方案同样重要。准备一个开关控制序列化版本ConditionalOnProperty(name redis.serializer.version, havingValue v2) Bean public RedisSerializerObject fastJson2Serializer() { return new FastJson2RedisSerializer(Object.class); } ConditionalOnProperty(name redis.serializer.version, havingValue v1, matchIfMissing true) Bean public RedisSerializerObject fastJson1Serializer() { return new FastJson1RedisSerializer(Object.class); }7. 延伸应用自定义类型处理器对于特殊类型可以注册自定义序列化逻辑。例如处理BigDecimal精度问题public class BigDecimalCodec implements ObjectCodecBigDecimal { Override public void write(JSONWriter writer, BigDecimal value, Object fieldName, Type fieldType, long features) { writer.writeString(value.setScale(2, RoundingMode.HALF_UP).toString()); } Override public BigDecimal read(JSONReader reader, Type fieldType, Object fieldName, long features) { return new BigDecimal(reader.readString()); } } // 注册自定义处理器 JSON.register(BigDecimal.class, new BigDecimalCodec());这种模式同样适用于枚举类型的自定义序列化第三方库类型的适配敏感数据的加密处理在金融项目中我们曾用此机制统一处理货币金额的精度避免了分布式环境下浮点数计算不一致的问题。