Fastjson 解析多级泛型时而失败的解决方案

Version

fastjson 解析多级泛型时而失败:

平时运行好好的,偶尔会反序列化泛型失败(实际是被转化成了JSONObject),堆栈如下

java.lang.ClassCastException: com.alibaba.fastjson.JSONObject cannot be cast to com.mljr.hr.bean.Position
    at com.mljr.hr.task.PositionSyncTask.batchNewData(PositionSyncTask.java:66)
    at com.mljr.hr.task.AbstractScheduleTask.startScheduleNewData(AbstractScheduleTask.java:66)
    at com.mljr.hr.task.PositionSyncTask.scheduleNewData(PositionSyncTask.java:56)
    at com.mljr.hr.task.PositionSyncTask$$FastClassBySpringCGLIB$$b4e853c8.invoke()
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:720)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
    at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:85)
    at com.github.blackshadowwalker.spring.distributelock.interceptor.LockAspectSupport$1.invoke(LockAspectSupport.java:46)
    at com.github.blackshadowwalker.spring.distributelock.interceptor.LockAspectSupport.execute(LockAspectSupport.java:89)
    at com.github.blackshadowwalker.spring.distributelock.interceptor.LockAspectSupport.execute(LockAspectSupport.java:73)
    at com.github.blackshadowwalker.spring.distributelock.interceptor.LockAspectSupport.around(LockAspectSupport.java:52)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:483)

外包装对象定义

class MyResponse<T> {

    Boolean success;
    Integer errCode;
    String  errDes;
    T       result;
}

声明:MyResponse<List<Dept>>

一、调试跟踪

调试分析

经过多次调试,问题出现在这里 DefaultFieldDeserializer#parseField

    @Override
    public void parseField(DefaultJSONParser parser, Object object, Type objectType, Map<String, Object> fieldValues) {
        if (fieldValueDeserilizer == null) {
            getFieldValueDeserilizer(parser.getConfig());
        }

if (objectType instanceof ParameterizedType) {
            ParseContext objContext = parser.getContext();
            objContext.type = objectType;
            fieldType = FieldInfo.getFieldType(this.clazz, objectType, fieldType);
 }

如我的对象类型 result 的类型是 List, 但是经过 getFieldValueDeserilizer(parser.getConfig()) 的处理后 fieldValueDeserilizer ==JavaObjectDeserializer, 然后在 68行 进行解析处理

68  value = fieldValueDeserilizer.deserialze(parser, fieldType, fieldInfo.name);

接着分析 JavaObjectDeserializer

public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName) {
        if (type instanceof GenericArrayType) {
            Type componentType = ((GenericArrayType) type).getGenericComponentType();
            if (componentType instanceof TypeVariable) {
                TypeVariable<?> componentVar = (TypeVariable<?>) componentType;
                componentType = componentVar.getBounds()[0];
            }

            List<Object> list = new ArrayList<Object>();
            parser.parseArray(componentType, list);
            Class<?> componentClass;
            if (componentType instanceof Class) {
                componentClass = (Class<?>) componentType;
                Object[] array = (Object[]) Array.newInstance(componentClass, list.size());
                list.toArray(array);
                return (T) array;
            } else {
                return (T) list.toArray();
            }

        }

        if (type instanceof Class && type != Object.class && type != Serializable.class) {
            return (T) parser.parseObject(type);    
        }

        return (T) parser.parse(fieldName);//最后到了这里,
       //而 parser == DefaultJSONParser (来源于 `DefaultJSONParser (614) return (T) derializer.deserialze(this, type, fieldName);`)
    }

然后进入 DefaultJSONParser 1280 行 parse(Object fieldName) ,在这里就将result的值解析成了 JSONArray对象,从而造成类型转换错误,没有得到预期的类型。

解决方案

回到来这里 DefaultFieldDeserializer#parseField

    @Override
    public void parseField(DefaultJSONParser parser, Object object, Type objectType, Map<String, Object> fieldValues) {
        if (fieldValueDeserilizer == null) {
            getFieldValueDeserilizer(parser.getConfig());
        }

if (objectType instanceof ParameterizedType) {
            ParseContext objContext = parser.getContext();
            objContext.type = objectType;
            fieldType = FieldInfo.getFieldType(this.clazz, objectType, fieldType);
 }

既然是多级泛型 ParameterizedType 并且解析除了泛型类型 fieldType ,为什么不加一行

fieldValueDeserilizer = parser.getConfig().getDeserializer(fieldType)

这样从我贴的数据中就得到了 fieldValueDeserilizer = CollectionCodec,这样就根据 fieldType获取到了正确的 fieldValueDeserilizer

二、复现

1. 复现

过程:

1.定义

mType1 = new TypeReference<MyResponse<List>>() {}.getType();

mType2 = new TypeReference<MyResponse<List<Dept>>>() {}.getType();

class MyResponse<T> {
    Boolean success;
    Integer errCode;
    String  errDes;
    T       result;
}

2.第一次调用

使用mType1

反序列化使用MyResponse<List>

JSON.parseObject(jsonData, mType1, configBug569, featureValues,
                features != null ? features : EMPTY_SERIALIZER_FEATURES);

结果:resp.getResult().getClass()==JSONArray.class,同时fastjson会缓存 MyResponse的解析器(即目的类型为JSONArray)

3.第二次调用

使用mType2

反序列化使用的 MyResponse<List<Dept>>

JSON.parseObject(jsonData, mType2, configBug569, featureValues, features != null ? features : EMPTY_SERIALIZER_FEATURES);

结果:resp.getResult().getClass()==JSONArray.class,与期望的List<Dept>不相符

2.分析

第一次调用反序列化,关键点: ParserConfig(460)

460 putDeserializer(type, derializer);

这里缓存了对于 MyResponse<List> 的解析器

第二次调用反序列化:

ParserConfig(352-354行)

 if (type instanceof WildcardType || type instanceof TypeVariable || type instanceof ParameterizedType) {
            derializer = derializers.get(clazz);
        }

这里经过代码(337 derializers.get(type))没有找到指定type的解析器,然后到352行判断如果type是泛型,就使用class代替type从缓存中获取解析器,这样就获取到了MyResponse的解析器,而并非是MyResponse<List<Dept>>的解析器。

结论:

解析器缓存导致,然而ParserConfig默认都是使用 的 ParserConfig.getGlobalInstance(),从而缓存造成了影响。

3. 解决方案

  • 方案一: 如上所写的解决方案:加一行fieldType = FieldInfo.getFieldType(this.clazz, objectType, fieldType);,已经提交pull request且已经合并到’1.2.18-SNAPSHOT’。
  • 方案二: 修改 ParserConfig 352行的判断,暂时还没找到合理方式。

Issues:
- https://github.com/alibaba/fastjson/issues/569

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值