Bootstrap

记一次FastJson中的Java泛型擦除问题排查


前言

公司的一个模块需求变更,经过评审要按照原来的设计进行修改,写出来的代码会非常恶心,估计下一个接任者心里会骂niang,于是决定对这块进行重构。
洋洋洒洒撸了几天代码,总算重构完毕,TestCase走起。哇哦,报错了,一看竟是ClassCastException,熟悉的味道,猜测是泛型擦除导致的,一经调试果然是这个原因。
但是既然编写代码的时候还是犯下了这个错误,故还是打算对这个基础知识进行一个梳理,既巩固了自己的技术储备,也能分享一些自己的小心得,一举两得。


问题起源

java.lang.ClassCastException: com.alibaba.fastjson.JSONObject cannot be cast to com.xx.xx.xx.xx.XXX

可是代码明明是定义了泛型的,代码类似于

public abstract class AbstractXXX<O, T> extends YYYHandler<T> {
	...
	@Override
    public void handle() throws Exception {
    	...
		Response<T> response = JSON.parseObject(test, new TypeReference<Response<T>>() {}.getType());
		...
	}
	...
}


//最终继承自上面的AbstractXXX
//此处的HHH类型对应上面的泛型O
public class ZZZHandler extends MMMHandler<HHH> {
	...
}

debug一看Response中的泛型竟然是JSONObject类型,W·T·M·F?
奇怪的类型

问题排查

既然出问题的是JSON.parseObject那我们就看看里面到底做了什么操作呗。

public abstract class JSON implements JSONStreamAware, JSONAware {
	
	...
	
    public static ParserConfig                              global                = new ParserConfig();

	...

	public static <T> T parseObject(String json, Type type, Feature... features) {
        return (T) parseObject(json, type, ParserConfig.global, DEFAULT_PARSER_FEATURE, features);
    }
	
	...
	
    public static <T> T parseObject(String input, Type clazz, ParserConfig config, ParseProcess processor,
                                          int featureValues, Feature... features) {
        ...

        DefaultJSONParser parser = new DefaultJSONParser(input, config, featureValues);

        ...

        T value = (T) parser.parseObject(clazz, null);

        ...

        return (T) value;
    }
    
    ...
    
}

可以看到,将Type作为参数传给DefaultJSONParser对象的parseObject方法,接下来看看该方法又干了什么

public class DefaultJSONParser implements Closeable {

	...

	public <T> T parseObject(Type type, Object fieldName) {
        ...

        ObjectDeserializer derializer = config.getDeserializer(type);

        try {
            return (T) derializer.deserialze(this, type, fieldName);
        } catch (JSONException e) {
            throw e;
        } catch (Throwable e) {
            throw new JSONException(e.getMessage(), e);
        }
    }

	...

}

这里直接使用config获取反序列化器,而config就是最早创建的默认配置,即ParserConfig

public class ParserConfig {

	...

	public ObjectDeserializer getDeserializer(Type type) {
        ...

        if (type instanceof ParameterizedType) {
            Type rawType = ((ParameterizedType) type).getRawType();
            if (rawType instanceof Class<?>) {
                return getDeserializer((Class<?>) rawType, type);
            } else {
                return getDeserializer(rawType);
            }
        }

        ...

        return JavaObjectDeserializer.instance;
    }

	...

	public ObjectDeserializer getDeserializer(Class<?> clazz, Type type) {
        ObjectDeserializer derializer = deserializers.get(type);
        
        ...

        if (clazz.isEnum()) {
            ...
            derializer = new EnumDeserializer(clazz);
        } else if (clazz.isArray()) {
            derializer = ObjectArrayCodec.instance;
        } else if (clazz == Set.class || clazz == HashSet.class || clazz == Collection.class || clazz == List.class
                   || clazz == ArrayList.class) {
            derializer = CollectionCodec.instance;
        } else if (Collection.class.isAssignableFrom(clazz)) {
            derializer = CollectionCodec.instance;
        } else if (Map.class.isAssignableFrom(clazz)) {
            derializer = MapDeserializer.instance;
        } else if (Throwable.class.isAssignableFrom(clazz)) {
            derializer = new ThrowableDeserializer(this, clazz);
        } else if (PropertyProcessable.class.isAssignableFrom(clazz)) {
            derializer = new PropertyProcessableDeserializer((Class<PropertyProcessable>)clazz);
        } else {
            derializer = createJavaBeanDeserializer(clazz, type);
        }

        putDeserializer(type, derializer);

        return derializer;
    }

	...

    public ObjectDeserializer createJavaBeanDeserializer(Class<?> clazz, Type type) {
		
		...
		
		if (!asmEnable) {
            return new JavaBeanDeserializer(this, clazz, type);
        }

		...
	}

	...

}

到这里就成功构建了待反序列化对象的反序列化器,因为我们的对象不是基本数据类型,故最终构建的是JavaBeanDeserializer,再看这个反序列化器是怎么对字段和泛型进行处理的

public class JavaBeanDeserializer implements ObjectDeserializer {

	...
	
    protected final FieldDeserializer[] sortedFieldDeserializers;

	...

	public JavaBeanDeserializer(ParserConfig config, Class<?> clazz, Type type){
        this(config //
                , JavaBeanInfo.build(clazz, type, config.propertyNamingStrategy, config.fieldBased, config.compatibleWithJavaBean)
        );
    }
    
    public JavaBeanDeserializer(ParserConfig config, JavaBeanInfo beanInfo){
        
        ...
        
        sortedFieldDeserializers = new FieldDeserializer[beanInfo.sortedFields.length];

		...
		
	}

	...
	
}

这里只看它的构造方法即可,拿之前出问题的代码来举例,我们定义的clazz就是Response、type就是new出来的TypeReference,这些信息传入JavaBeanInfo后依次解析完所有字段的反序列化器,接下来再看这些信息是怎么被JavaBeanInfo用起来的

public class JavaBeanInfo {

	...
	
    public final FieldInfo[] sortedFields;
	
	...

    public JavaBeanInfo(Class<?> clazz, //
                        Class<?> builderClass, //
                        Constructor<?> defaultConstructor, //
                        Constructor<?> creatorConstructor, //
                        Method factoryMethod, //
                        Method buildMethod, //
                        JSONType jsonType, //
                        List<FieldInfo> fieldList) {

		//对fieldList进行排序,然后赋值给sortedFields
		//代码省略
		...
	
	}

	...
	
    public static JavaBeanInfo build(Class<?> clazz //
            , Type type //
            , PropertyNamingStrategy propertyNamingStrategy //
            , boolean fieldBased //
            , boolean compatibleWithJavaBean
    ) {
		
		...

		//默认配置不是基于字段,是基于方法的,即set/get方法,故会走到这里
		//若手动配置了基于字段,就会走前面的分支
        for (Method method : methods) { //
			...
			
			//但不论配置哪种方式,至少解析泛型的方式是一样的,都是通过FieldInfo来进行
			//add方法大意就是要将解析好的字段信息添加到fieldList中,代码略
			add(fieldList, new FieldInfo(propertyName, method, field, clazz, type, ordinal, serialzeFeatures, parserFeatures,
                    annotation, fieldAnnotation, null));
		}
		
		...

        return new JavaBeanInfo(clazz, builderClass, defaultConstructor, creatorConstructor, factoryMethod, buildMethod, jsonType, fieldList);
	}

	...

}

这里就是挨个通过FieldInfo来解析(包装)字段,并将解析好的字段添加到fieldList,接下来就看FieldInfo是怎么做的

public class FieldInfo implements Comparable<FieldInfo> {
	
	...

    public FieldInfo(String name, // 
                     Method method, // 
                     Field field, // 
                     Class<?> clazz, // 
                     Type type, // 
                     int ordinal, // 
                     int serialzeFeatures, // 
                     int parserFeatures, //
                     JSONField fieldAnnotation, // 
                     JSONField methodAnnotation, //
                     String label){

		...

		Type fieldType;
        Class<?> fieldClass;
        if (method != null) {
        	Class<?>[] types;
            if ((types = method.getParameterTypes()).length == 1) {
                fieldClass = types[0];
                fieldType = method.getGenericParameterTypes()[0];
            }
            
            ...
            
        }

		...

		Type genericFieldType = fieldType;
        
        if (!(fieldType instanceof Class)) {
			genericFieldType = getFieldType(clazz, type != null ? type : clazz, fieldType);

			...
			
		}

		...

	}

	...
	
    public static Type getFieldType(final Class<?> clazz, final Type type, Type fieldType) {

		...
		
		if (fieldType instanceof ParameterizedType) {
            ParameterizedType parameterizedFieldType = (ParameterizedType) fieldType;

            Type[] arguments = parameterizedFieldType.getActualTypeArguments();
            TypeVariable<?>[] typeVariables;
            ParameterizedType paramType;
            if (type instanceof ParameterizedType) {
                paramType = (ParameterizedType) type;
                typeVariables = clazz.getTypeParameters();
            }
	
			...

            boolean changed = getArgument(arguments, typeVariables, paramType.getActualTypeArguments());
            if (changed) {
                fieldType = new ParameterizedTypeImpl(arguments, parameterizedFieldType.getOwnerType(),
                                                      parameterizedFieldType.getRawType());
                return fieldType;
            }
        }

        return fieldType;
	}

    private static boolean getArgument(Type[] typeArgs, TypeVariable[] typeVariables, Type[] arguments) {
		
		...
		
		boolean changed = false;
        for (int i = 0; i < typeArgs.length; ++i) {
            Type typeArg = typeArgs[i];
            if (typeArg instanceof ParameterizedType) {
                ...
            } else if (typeArg instanceof TypeVariable) {
                for (int j = 0; j < typeVariables.length; ++j) {
                    if (typeArg.equals(typeVariables[j])) {
                        typeArgs[i] = arguments[j];
                        changed = true;
                    }
                }
            }
        }

        return changed;
	}

	...
	
}

大意就是获取字段的泛型标识符和实际的泛型参数,然后对比泛型标识符,如果是一样的,就把泛型的实际类型赋值给该变量,只不过我们的实际泛型类型仍然是泛型,所以就会被处理成JSONObject(为什么会被处理成JSONObject后文会提)
转换后仍然是泛型变量
至此就完成了反序列化器的构建,之后就是实际执行反序列化操作derializer.deserialze,由前文可知实际构建的是JavaBeanDeserializer,接下来就简单看看是如何实际反序列化的

public class JavaBeanDeserializer implements ObjectDeserializer {
	
	...
	
    public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName, int features) {
        return deserialze(parser, type, fieldName, null, features, null);
    }

	...
	
    protected <T> T deserialze(DefaultJSONParser parser, // 
                               Type type, // 
                               Object fieldName, // 
                               Object object, //
                               int features, //
                               int[] setFlags) {
		
		...
        
        for (int fieldIndex = 0;; fieldIndex++) {
            String key = null;
            FieldDeserializer fieldDeser = null;
            FieldInfo fieldInfo = null;
            Class<?> fieldClass = null;
            JSONField feildAnnotation = null;
            if (fieldIndex < sortedFieldDeserializers.length) {
                fieldDeser = sortedFieldDeserializers[fieldIndex];
                fieldInfo = fieldDeser.fieldInfo;
                fieldClass = fieldInfo.fieldClass;
                feildAnnotation = fieldInfo.getAnnotation();
            }

			...

			if (matchField) {
	            if (!valueParsed) {
	                fieldDeser.parseField(parser, object, type, fieldValues);
	            }
	            
	            ...
	            
	        }
		
		...
		
	}

	...
	
}

可以看到是依次遍历字段并取字段的反序列化器来进行解析,由上文可知sortedFieldDeserializers就是前面构造反序列化器时遍历字段时解析得到的,其内容就是将泛型标识符进行实际传参替换后的Type。接下来看看这些字段反序列化器又是如何进行反序列化的

public class ArrayListTypeFieldDeserializer extends FieldDeserializer {
	
	...

    @Override
    public void parseField(DefaultJSONParser parser, Object object, Type objectType, Map<String, Object> fieldValues) {
		
		...
		
        parseArray(parser, objectType, list);
		
		...
	
	}

    @SuppressWarnings({ "unchecked", "rawtypes" })
    public final void parseArray(DefaultJSONParser parser, Type objectType, Collection array) {
        
        ...
		
		//就是将前面解析得到的字段类型然后找到对应的反序列化器
		//得到反序列化器又进行解析,如此往复,直到所有字段均反序列化完毕
        itemTypeDeser = deserializer = parser.getConfig().getDeserializer(itemType);
		
		...

	}
	
	...
	
}

得到反序列化器的逻辑前面已经贴过了,如果是泛型标识符,则会最终返回JavaObjectDeserializer,这下明白为什么最终会被解析成JSONObject了吧 😃

注:这里含泛型的是List,如果是普通类型走其他字段反序列化器,道理是一样,都会走前面得到反序列化器的逻辑!

问题解决

其实经过上一节的排查,相信聪明的你已经知道如何解决这个问题了,我想到的大致有3种解决思路:

  1. 定义抽象方法强制子类将指定了实际泛型参数的Type传过来;
  2. 将反序列化操作定义为抽象方法交给子类去完成;
  3. 获取Class上的实际泛型参数然后传递过去;

法1和法2很像,解决起来不那么优雅,本来就使用了泛型还要手动指定Type,本来就应该放到父类的公共方法还要专门放到子类去编写高度重复的代码。法3看起来就挺不错的,我实际上也选择法3进行解决的,实际执行起来也挺简单的。

ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();
Type[] actualTypeArguments = pt.getActualTypeArguments();

//Response<T> response = JSON.parseObject(test, new TypeReference<Response<T>>() {}.getType());
Response<T> response = JSON.parseObject(test, new TypeReference<Response<T>>(actualTypeArguments[0]) {}.getType());

只需要将获取到的实际泛型Type作为参数传到TypeReference的构造方法中即可(具体代码就不贴了)
是不是感觉就挺秃然的,排查问题废了那么大的篇幅,解决问题就这么点。但其实还真是,实际遇到bug时,当你能摸透该bug产生的原因,就已经解决了一大半了,剩下的就只是目标清晰的解决即可。

问题后记

前面提到的什么参数化类型ParameterizedType,其实都继承自Type,这个概念是JDK1.5引入的。其中Class就实现了该接口,也就说Type是用于描述类上的一些信息的。
下面针对前文提到最多的ParameterizedType进行一个简单的说明,也算是带大家复习下Java反射的一点小基础知识吧。

public interface ParameterizedType extends Type {
    /**
     * 返回对象实际设置的泛型类型集合,比如Map<String, Integer>,这里就分别返回String和Integer
     */
    Type[] getActualTypeArguments();

    /**
     * 返回对象本身的类型,比如Map<String, Integer>,这里就返回Map
     */
    Type getRawType();

    /**
     * 返回类型的拥有者类型,比如Map.Entry<String, Integer>,返回Map
     * 可以简单理解为是否内部类/接口(不知是否准确,望指正),若不是返回null,若是返回定义该内部类/接口的类型
     */
    Type getOwnerType();
}

um… that’s all ,下回见啦~
感谢各位大佬能看到这里,若发现文章中的任何谬误,欢迎留言指正,谢谢支持啦~

转载请注明出处:记一次FastJson中的Java泛型擦除问题排查

;