fastjson反序列化多層嵌套泛型類與java中的Type類型


在使用springmvc時,我們通常會定義類似這樣的通用類與前端進行交互,以便於前端可以做一些統一的處理:

public class Result<T> {
    private int ret;
    private String msg;
    private T data;
    // 此處省略getter和setter方法
}

這樣的類序列化為json后,js反序列化處理起來毫無壓力。但是如果rest接口的消費端就是java呢,java泛型的類型擦除卻容易引入一些障礙。

一個反序列化的迭代

先定義一個類,后面的例子會用到:

public class Item {
    private String name;
    private String value;
    // 此處省略getter和setter方法
}

JSON數據:

{
	"data":{
		"name":"username",
		"value":"root"
	},
	"msg":"Success",
	"ret":0
}

當拿到上面的數據時,我們想到其對應的類型是Result<Item>,所以得想辦法將這個json數據反序列化為這個類型才行。

v1

JSONObject.parseObject(json, Result<Item>.class);,編譯器就報錯了Cannot select parameterized type

v2

JSONObject.parseObject(json, Result.class);,執行沒問題。但是沒有Item類型信息,fastjson不可能跟你心有靈犀一點通知道該把data轉為Item類型,result.getData().getClass()結果是com.alibaba.fastjson.JSONObject,也算是個妥善處理吧。

v3

找了一下前人的經驗,使用TypeReference來處理,JSONObject.parseObject(json, new TypeReference<Result<Item>>(){});,終於“完美”解決!

v4

有了v3的經驗,以為找到了通用處理的捷徑,遂封裝了一個處理這種類型的工具方法:

private static <T> Result<T> parseResultV1(String json) {
    return JSONObject.parseObject(json, new TypeReference<Result<T>>() {
    });
}

並且把采用v3的地方改用了此parseResult方法:

Result<Item> result = parseResultV1(json);

以為萬事大吉,連測都沒測試就把代碼提交了。測都不測試,當然難以有好結果了:

System.out.println(result.getData());
// java.lang.ClassCastException: com.alibaba.fastjson.JSONObject cannot be cast to Item

很顯然parseResultV1把Item的類型信息丟掉了。

{
	"data":"Hello,world!",
	"msg":"Success",
	"ret":0
}

試了一下Result 形式的,parseResultV1可以成功將其反序列化。推測(沒有看fastjson具體實現)是fastjson剛好檢測到data字段就是String類型,並將其賦值到data字段上了。仔細看parseObject並沒有報錯,而是在getData()時報錯的,聯系到java的泛型擦除,我們在用getData(),應該把data當作Object類型這么看:

String data = (String)result.getData();
System.out.println(data);

v5

原來TypeReference的構造器是可以傳入參數的,

private static <T> Result<T> parseResultV2(String json, Class<T> clazz) {
    return JSONObject.parseObject(json, new TypeReference<Result<T>>(clazz) {
    });
}

這個可以真的可以完美反序列化Result<Item>了。

v6

后來發現parseResultV2無法處理類似Result<List<T>>,原來TypeReference無法處理嵌套的泛型(這里指的是類型參數未確定,而不是類似Result<List<Item>>類型參數已經確定)。借用Fastjson解析多級泛型的幾種方式—使用class文件來解析多級泛型里的方法,新增加一個專門處理List類型的方法:

private static <T> Result<List<T>> parseListResult(String json, Class<T> clazz) {
    return JSONObject.parseObject(json, buildType(Result.class, List.class, Item.class));
}

private static Type buildType(Type... types) {
    ParameterizedTypeImpl beforeType = null;
    if (types != null && types.length > 0) {
        for (int i = types.length - 1; i > 0; i--) {
            beforeType = new ParameterizedTypeImpl(new Type[]{beforeType == null ? types[i] : beforeType}, null, types[i - 1]);
        }
    }
    return beforeType;
}

或者根據這里只有兩層,簡單如下:

private static <T> Result<List<T>> parseListResult(String json, Class<T> clazz) {
    ParameterizedTypeImpl inner = new ParameterizedTypeImpl(new Type[]{clazz}, null, List.class);
    ParameterizedTypeImpl outer = new ParameterizedTypeImpl(new Type[]{inner}, null, Result.class);
    return JSONObject.parseObject(json, outer);
}

v7

todo: 上面兩個方法已經可以滿足現有需要,有時間再看看能否將兩個方法統一為一個。

com.alibaba.fastjson.TypeReference

看看TypeReference的源碼:

protected TypeReference(Type... actualTypeArguments) {
    Class<?> thisClass = this.getClass();
    Type superClass = thisClass.getGenericSuperclass();
    ParameterizedType argType = (ParameterizedType)((ParameterizedType)superClass).getActualTypeArguments()[0];
    Type rawType = argType.getRawType();
    Type[] argTypes = argType.getActualTypeArguments();
    int actualIndex = 0;

    for(int i = 0; i < argTypes.length; ++i) {
        if (argTypes[i] instanceof TypeVariable) {
            argTypes[i] = actualTypeArguments[actualIndex++];
            if (actualIndex >= actualTypeArguments.length) {
                break;
            }
        }
    }

    Type key = new ParameterizedTypeImpl(argTypes, thisClass, rawType);
    Type cachedType = (Type)classTypeCache.get(key);
    if (cachedType == null) {
        classTypeCache.putIfAbsent(key, key);
        cachedType = (Type)classTypeCache.get(key);
    }

    this.type = cachedType;
}

實際上它首先獲取到了泛型的類型參數argTypes,然后遍歷這些類型參數,如果遇到是TypeVariable類型的則用構造函數傳入的Type將其替換,然后此處理后的argTypes基於ParameterizedTypeImpl構造出一個新的Type,這樣的新的Type就可以具備我們期待的Type的各個泛型類型參數的信息了。所以fastjson就能夠符合我們期望地反序列化出了Result<Item>

正是由於這個處理邏輯,所以對於v6里的Result<List<T>>就無法處理了,它只能處理單層多類型參數的情況,而無法處理嵌套的泛型參數。

沒找到TypeReference的有參構造函數用法的比較正式的文檔,但是基於源碼的認識,我們應該這么使用TypeReference的有參構造函數:

new TypeReference<Map<T1, T2>>(clazz1, clazz2){}
new TypeReference<Xxx<T1, T2, T3>>(clazz1, clazz2, clazz3){}

也就是構造器里的Type列表要與泛型類型參數一一對應。

com.alibaba.fastjson.util.ParameterizedTypeImpl

那至於ParameterizedTypeImpl怎么回事呢?

import java.lang.reflect.ParameterizedType;
// ...其他省略...

public class ParameterizedTypeImpl implements ParameterizedType {
    public ParameterizedTypeImpl(Type[] actualTypeArguments, Type ownerType, Type rawType){
        this.actualTypeArguments = actualTypeArguments;
        this.ownerType = ownerType;
        this.rawType = rawType;
    }
    // ...其他省略...
}

以前也沒了解過ParameterizedType,與它相關的還有

Type
所有已知子接口: 
GenericArrayType, ParameterizedType, TypeVariable<D>, WildcardType 
所有已知實現類: 
Class

先看看這次已經用到的ParameterizedType接口(下列注釋是從jdk中文文檔拷貝過來,不太好理解)

public interface ParameterizedType extends Type {
    //返回表示此類型實際類型參數的 Type 對象的數組。
    //注意,在某些情況下,返回的數組為空。如果此類型表示嵌套在參數化類型中的非參數化類型,則會發生這種情況。 
    Type[] getActualTypeArguments();
    //返回 Type 對象,表示此類型是其成員之一的類型。
    Type getOwnerType();
    //返回 Type 對象,表示聲明此類型的類或接口。
    Type getRawType();
}

結合ParameterizedTypeImpl(Type[] actualTypeArguments, Type ownerType, Type rawType)的例子來理解:
new ParameterizedTypeImpl(new Type[]{clazz}, null, List.class)用於構造List<T>

關於Type

泛型是Java SE 1.5的新特性,Type也是1.5才有的。它是在java加入泛型之后為了擴充類型引入的。與Type相關的一些類或者接口來表示與Class類似但是又因泛型擦除丟失的一些類型信息。

ParameterizedTypeImpl使用踩坑

詳見:fastjson反序列化使用不當導致內存泄露


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM