在使用springmvc時,咱們一般會定義相似這樣的通用類與前端進行交互,以便於前端能夠作一些統一的處理:html
public class Result<T> { private int ret; private String msg; private T data; // 此處省略getter和setter方法 }
這樣的類序列化爲json後,js反序列化處理起來毫無壓力。可是若是rest接口的消費端就是java呢,java泛型的類型擦除卻容易引入一些障礙。前端
先定義一個類,後面的例子會用到:java
public class Item { private String name; private String value; // 此處省略getter和setter方法 }
JSON數據:spring
{ "data":{ "name":"username", "value":"root" }, "msg":"Success", "ret":0 }
當拿到上面的數據時,咱們想到其對應的類型是Result<Item>
,因此得想辦法將這個json數據反序列化爲這個類型才行。json
JSONObject.parseObject(json, Result<Item>.class);
,編譯器就報錯了Cannot select parameterized type
。數組
JSONObject.parseObject(json, Result.class);
,執行沒問題。可是沒有Item類型信息,fastjson不可能跟你心有靈犀一點通知道該把data轉爲Item類型,result.getData().getClass()
結果是com.alibaba.fastjson.JSONObject
,也算是個妥善處理吧。mvc
找了一下前人的經驗,使用TypeReference來處理,JSONObject.parseObject(json, new TypeReference<Result<Item>>(){});
,終於「完美」解決!函數
有了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
String data = (String)result.getData(); System.out.println(data);
原來TypeReference的構造器是能夠傳入參數的,
private static <T> Result<T> parseResultV2(String json, Class<T> clazz) { return JSONObject.parseObject(json, new TypeReference<Result<T>>(clazz) { }); }
這個能夠真的能夠完美反序列化Result<Item>
了。
後來發現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); }
todo: 上面兩個方法已經能夠知足現有須要,有時間再看看可否將兩個方法統一爲一個。
看看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列表要與泛型類型參數一一對應。
那至於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>
。
泛型是Java SE 1.5的新特性,Type
也是1.5纔有的。它是在java加入泛型以後爲了擴充類型引入的。與Type相關的一些類或者接口來表示與Class相似可是又因泛型擦除丟失的一些類型信息。