问题分析
最新使用Springboot redisTemplate遇到一个问题,自定义RedisSerializer中使用了fastjson进行序列化,调用JSON.toJSONString函数时添加了SerializerFeature.WriteClassName参数,导致反序列化的时候报错,JSONException: type not match.com.ruoyi.common.core.domain.entity.SysDictData -> com.ruoyi.system.domain.SysDictData
,原因就是反序列化指定的类类型与序列化的时候不一致,虽然类名一样,但是全路径不一样。通常在不同的系统间进行交互,使用同一个redis集群中的资源时会出该问题。
解决方法
第一种方式
两个系统的同名称的类放到同一个package下,这样fastjson类型校验就会通过。或者就是在自定义RedisSerializer的时候,序列化不添加SerializerFeature.WriteClassName参数,这样多个系统才能共用
第二种方式
源码分析
自定义AutoTypeCheckHandler,进行全局配置
fastjson在进行反序列化的时候,如果解析到有@type类型,会调用ParserConfig.checkAutoType()方法进行校验
protected <T> T deserialze(DefaultJSONParser parser, //
Type type, //
Object fieldName, //
Object object, //
int features, //
int[] setFlags) {
/** 此处一大堆代码省略 */
ObjectDeserializer deserializer = getSeeAlso(config, this.beanInfo, typeName);
Class<?> userType = null;
if (deserializer == null) {
Class<?> expectClass = TypeUtils.getClass(type);
// 默认全局类型校验
if (autoTypeCheckHandler != null) {
userType = autoTypeCheckHandler.handler(typeName, expectClass, lexer.getFeatures());
}
if (userType == null) {
if (typeName.equals("java.util.HashMap") || typeName.equals("java.util.LinkedHashMap")) {
if (lexer.token() == JSONToken.RBRACE) {
lexer.nextToken();
break;
}
continue;
}
}
if (userType == null) {
// 最终会调用checkAutoType方法
userType = config.checkAutoType(typeName, expectClass, lexer.getFeatures());
}
deserializer = parser.getConfig().getDeserializer(userType);
}
/** 此处一大堆代码省略 */
}
public class ParserConfig {
/** 类型校验 */
public Class<?> checkAutoType(String typeName, Class<?> expectClass, int features) {
if (typeName == null) {
return null;
}
// 运行自定义的AutoTypeCheckHandler
if (autoTypeCheckHandlers != null) {
for (AutoTypeCheckHandler h : autoTypeCheckHandlers) {
// typeName:json字符串中@type指定的类型名称 如com.ruoyi.common.core.domain.entity.SysDictData
// expectClass:parseObject方法中指定的类名称JSON.parseObject(jsonData, com.ruoyi.system.domain.SysDictData.class)
// 只需要再自定义AutoTypeCheckHandler返回@type指定的类型,只要type不为空,直接返回,下面的处理逻辑就不走了
Class<?> type = h.handler(typeName, expectClass, features);
if (type != null) {
return type;
}
}
}
final int safeModeMask = Feature.SafeMode.mask;
boolean safeMode = this.safeMode
|| (features & safeModeMask) != 0
|| (JSON.DEFAULT_PARSER_FEATURE & safeModeMask) != 0;
if (safeMode) {
throw new JSONException("safeMode not support autoType : " + typeName);
}
if (typeName.length() >= 192 || typeName.length() < 3) {
throw new JSONException("autoType is not support. " + typeName);
}
/** 下面一大堆校验代码省略 */
}
}
最终源码
import com.ruoyi.common.core.domain.entity.SysDictData;
public static void main(String[] args) {
// 自定义类型检验器,直接把@type指定的类全名称返回即可,这样反序列化就不会出现类名称校验不一致的情况
ParserConfig.getGlobalInstance().addAutoTypeCheckHandler((typeName, expectClass, features) -> {
try {
return Class.forName(typeName);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return expectClass;
});
SysDictData sysDictData = new SysDictData();
sysDictData.setDictLabel("测试");
sysDictData.setDictValue("1");
String jsonData = JSON.toJSONString(sysDictData, SerializerFeature.WriteClassName);
System.out.println(jsonData);
System.out.println(JSON.parseObject(jsonData, com.ruoyi.system.domain.SysDictData.class));
}