泛型的類型擦除后fastjson反序列化時(shí)如何還原詳解
鋪墊
在前面的文章中,我們講過(guò)Java中泛型的類型擦除,不過(guò)有小伙伴在后臺(tái)留言提出了一個(gè)問(wèn)題,帶有泛型的實(shí)體的反序列化過(guò)程是如何實(shí)現(xiàn)的,今天我們就來(lái)看看這個(gè)問(wèn)題。
我們選擇fastjson來(lái)進(jìn)行反序列化的測(cè)試,在測(cè)試前先定義一個(gè)實(shí)體類:
@Data
public class Foo<T> {
private String val;
private T obj;
}
如果大家對(duì)泛型的類型擦除比較熟悉的話,就會(huì)知道在編譯完成后,其實(shí)在類中是沒(méi)有泛型的。我們還是用Jad反編譯一下字節(jié)碼文件,可以看到?jīng)]有類型限制的T會(huì)被直接替換為Object類型:

下面使用fastjson進(jìn)行反序列化,先不指定Foo中泛型的類型:
public static void main(String[] args) {
String jsonStr = "{\"obj\":{\"name\":\"Hydra\",\"age\":\"18\"},\"val\":\"str\"}";
Foo<?> foo = JSONObject.parseObject(jsonStr, Foo.class);
System.out.println(foo.toString());
System.out.println(foo.getObj().getClass());
}
查看執(zhí)行結(jié)果,很明顯fastjson不知道要把obj里的內(nèi)容反序列化成我們自定義的User類型,于是將它解析成了JSONObject類型的對(duì)象。
Foo(val=str, obj={"name":"Hydra","age":"18"})
class com.alibaba.fastjson.JSONObject
那么,如果想把obj的內(nèi)容映射為User實(shí)體對(duì)象應(yīng)該怎么寫(xiě)呢?下面先來(lái)示范幾種錯(cuò)誤寫(xiě)法。
錯(cuò)誤寫(xiě)法1
嘗試在反序列化時(shí),直接指定Foo中的泛型為User:
Foo<User> foo = JSONObject.parseObject(jsonStr, Foo.class); System.out.println(foo.toString()); System.out.println(foo.getObj().getClass());
結(jié)果會(huì)報(bào)類型轉(zhuǎn)換的錯(cuò)誤,JSONObject不能轉(zhuǎn)成我們自定義的User:
Exception in thread "main" java.lang.ClassCastException: com.alibaba.fastjson.JSONObject cannot be cast to com.hydra.json.model.User
at com.hydra.json.generic.Test1.main(Test1.java:24)
錯(cuò)誤寫(xiě)法2
再試試使用強(qiáng)制類型轉(zhuǎn)換:
Foo<?> foo =(Foo<User>) JSONObject.parseObject(jsonStr, Foo.class); System.out.println(foo.toString()); System.out.println(foo.getObj().getClass());
執(zhí)行結(jié)果如下,可以看到,泛型的強(qiáng)制類型轉(zhuǎn)換雖然不會(huì)報(bào)錯(cuò),但是同樣也沒(méi)有生效。
Foo(val=str, obj={"name":"Hydra","age":"18"})
class com.alibaba.fastjson.JSONObject
好了,現(xiàn)在請(qǐng)大家忘記上面這兩種錯(cuò)誤的使用方法,代碼中千萬(wàn)別這么寫(xiě),下面我們看正確的寫(xiě)法。
正確寫(xiě)法
在使用fastjson時(shí),可以借助TypeReference完成指定泛型的反序列化:
public class TypeRefTest {
public static void main(String[] args) {
String jsonStr = "{\"obj\":{\"name\":\"Hydra\",\"age\":\"18\"},\"val\":\"str\"}";
Foo foo2 = JSONObject.parseObject(jsonStr, new TypeReference<Foo<User>>(){});
System.out.println(foo2.toString());
System.out.println(foo2.getObj().getClass());
}
}
運(yùn)行結(jié)果:
Foo(val=str, obj=User(name=Hydra, age=18)) class com.hydra.json.model.User
Foo中的obj類型為User,符合我們的預(yù)期。下面我們就看看,fastjson是如何借助TypeReference完成的泛型類型擦除后的還原。
TypeReference
回頭再看一眼上面的代碼中的這句:
Foo foo2 = JSONObject.parseObject(jsonStr, new TypeReference<Foo<User>>(){});
重點(diǎn)是parseObject方法中的第二個(gè)參數(shù),注意在TypeReference<Foo<User>>()有一對(duì)大括號(hào){}。也就是說(shuō)這里創(chuàng)建了一個(gè)繼承了TypeReference的匿名類的對(duì)象,在編譯完成后的項(xiàng)目target目錄下,可以找到一個(gè)TypeRefTest$1.class字節(jié)碼文件,因?yàn)槟涿惖拿?guī)則就是主類名+$+(1,2,3……)。
反編譯這個(gè)文件可以看到這個(gè)繼承了TypeReference的子類:
static class TypeRefTest$1 extends TypeReference
{
TypeRefTest$1()
{
}
}
我們知道,在創(chuàng)建子類的對(duì)象時(shí),子類會(huì)默認(rèn)先調(diào)用父類的無(wú)參構(gòu)造方法,所以看一下TypeReference的構(gòu)造方法:
protected TypeReference(){
Type superClass = getClass().getGenericSuperclass();
Type type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
Type cachedType = classTypeCache.get(type);
if (cachedType == null) {
classTypeCache.putIfAbsent(type, type);
cachedType = classTypeCache.get(type);
}
this.type = cachedType;
}
其實(shí)重點(diǎn)也就是前兩行代碼,先看第一行:
Type superClass = getClass().getGenericSuperclass();
雖然這里是在父類中執(zhí)行的代碼,但是getClass()得到的一定是子類的Class對(duì)象,因?yàn)?code>getClass()方法獲取到的是當(dāng)前運(yùn)行的實(shí)例自身的Class,不會(huì)因?yàn)檎{(diào)用位置改變,所以getClass()得到的一定是TypeRefTest$1。
獲取當(dāng)前對(duì)象的Class后,再執(zhí)行了getGenericSuperclass()方法,這個(gè)方法與getSuperclass類似,都會(huì)返回直接繼承的父類。不同的是getSuperclas沒(méi)有返回泛型參數(shù),而getGenericSuperclass則返回了包含了泛型參數(shù)的父類。
再看第二行代碼:
Type type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
首先將上一步獲得的Type強(qiáng)制類型轉(zhuǎn)換為ParameterizedType參數(shù)化類型,它是泛型的一個(gè)接口,實(shí)例則是繼承了它的ParameterizedTypeImpl類的對(duì)象。
在ParameterizedType中定義了三個(gè)方法,上面代碼中調(diào)用的getActualTypeArguments()方法就用來(lái)返回泛型類型的數(shù)組,可能返回有多個(gè)泛型,這里的[0]就是取出了數(shù)組中的第一個(gè)元素。
驗(yàn)證
好了,明白了上面的代碼的作用后,讓我們通過(guò)debug來(lái)驗(yàn)證一下上面的過(guò)程,執(zhí)行上面TypeRefTest的代碼,查看斷點(diǎn)中的數(shù)據(jù):

這里發(fā)現(xiàn)一點(diǎn)問(wèn)題,按照我們上面的分析,講道理這里父類TypeReference的泛型應(yīng)該是Foo<User>啊,為什么會(huì)出現(xiàn)一個(gè)List<String>?
別著急,讓我們接著往下看,如果你在TypeReference的無(wú)參構(gòu)造方法中加了斷點(diǎn),就會(huì)發(fā)現(xiàn)代碼執(zhí)行中會(huì)再調(diào)用一次這個(gè)構(gòu)造方法。

好了,這次的結(jié)果和我們的預(yù)期相同,父類的泛型數(shù)組中存儲(chǔ)了Foo<User>,也就是說(shuō)其實(shí)TypeRefTest$1繼承的父類,完成的來(lái)說(shuō)應(yīng)該是TypeReference<Foo<User>>,但是我們上面反編譯的文件中因?yàn)椴脸脑驔](méi)有顯示。
那么還有一個(gè)問(wèn)題,為什么這個(gè)構(gòu)造方法會(huì)被調(diào)用了兩次呢?
看完了TypeReference的代碼,終于在代碼的最后一行讓我發(fā)現(xiàn)了原因,原來(lái)是在這里先創(chuàng)建了一個(gè)TypeReference匿名類對(duì)象!
public final static Type LIST_STRING
= new TypeReference<List<String>>() {}.getType();
因此整段代碼執(zhí)行的順序是這樣的:
- 先執(zhí)行父類中靜態(tài)成員變量的定義,在這里聲明并實(shí)例化了這個(gè)
LIST_STRING,所以會(huì)執(zhí)行一次TypeReference()構(gòu)造方法,這個(gè)過(guò)程對(duì)應(yīng)上面的第一張圖 - 然后在實(shí)例化子類的對(duì)象時(shí),會(huì)再執(zhí)行一次父類的構(gòu)造方法
TypeReference(),對(duì)應(yīng)上面的第二張圖 - 最后執(zhí)行子類的空構(gòu)造方法,什么都沒(méi)有干
至于在這里聲明的LIST_STRING,在其他地方也沒(méi)有被再使用過(guò),Hydra也不知道這行代碼的意義是什么,有明白的小伙伴可以留言告訴我。
這里在拿到了Foo中的泛型User后,后面就可以按照這個(gè)類型來(lái)反序列化了,對(duì)后續(xù)流程有興趣的小伙伴可以自己去啃啃源碼,這里就不展開(kāi)了。
擴(kuò)展
了解了上面的過(guò)程后,我們最后通過(guò)一個(gè)例子加深一下理解,以常用的HashMap作為例子:
public static void main(String[] args) {
HashMap<String,Integer> map=new HashMap<String,Integer>();
System.out.println(map.getClass().getSuperclass());
System.out.println(map.getClass().getGenericSuperclass());
Type[] types = ((ParameterizedType) map.getClass().getGenericSuperclass())
.getActualTypeArguments();
for (Type t : types) {
System.out.println(t);
}
}
執(zhí)行結(jié)果如下,可以看到這里取到的父類是HashMap的父類AbstractMap,并且取不到實(shí)際的泛型類型。
class java.util.AbstractMap java.util.AbstractMap<K, V> K V
修改上面的代碼,僅做一點(diǎn)小改動(dòng):
public static void main(String[] args) {
HashMap<String,Integer> map=new HashMap<String,Integer>(){};
System.out.println(map.getClass().getSuperclass());
System.out.println(map.getClass().getGenericSuperclass());
Type[] types = ((ParameterizedType) map.getClass().getGenericSuperclass())
.getActualTypeArguments();
for (Type t : types) {
System.out.println(t);
}
}
執(zhí)行結(jié)果大有不同,可以看到,只是在new HashMap<String,Integer>()的后面加了一對(duì)大括號(hào){},就可以取到泛型的類型了:
class java.util.HashMap java.util.HashMap<java.lang.String, java.lang.Integer> class java.lang.String class java.lang.Integer
因?yàn)檫@里實(shí)例化的是一個(gè)繼承了HashMap的匿名內(nèi)部類的對(duì)象,因此取到的父類就是HashMap,并可以獲取到父類的泛型類型。
其實(shí)也可以再換一個(gè)寫(xiě)法,把這個(gè)匿名內(nèi)部類換成顯示聲明的非匿名的內(nèi)部類,再修改一下上面的代碼:
public class MapTest3 {
static class MyMap extends HashMap<String,Integer>{}
public static void main(String[] args) {
MyMap myMap=new MyMap();
System.out.println(myMap.getClass().getSuperclass());
System.out.println(myMap.getClass().getGenericSuperclass());
Type[] types = ((ParameterizedType) myMap.getClass().getGenericSuperclass())
.getActualTypeArguments();
for (Type t : types) {
System.out.println(t);
}
}
}
運(yùn)行結(jié)果與上面完全相同:
class java.util.HashMap java.util.HashMap<java.lang.String, java.lang.Integer> class java.lang.String class java.lang.Integer
唯一不同的是顯式生成的內(nèi)部類與匿名類命名規(guī)則不同,這里生成的字節(jié)碼文件不是MapTest3$1.class,而是MapTest3$MyMap.class,在$符后面使用的是我們定義的類名。
以上就是泛型的類型擦除后fastjson反序列化時(shí)如何還原詳解的詳細(xì)內(nèi)容,更多關(guān)于fastjson反序列化還原的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Spring MVC登錄注冊(cè)以及轉(zhuǎn)換json數(shù)據(jù)
本文主要介紹了Spring MVC登錄注冊(cè)以及轉(zhuǎn)換json數(shù)據(jù)的相關(guān)知識(shí)。具有很好的參考價(jià)值。下面跟著小編一起來(lái)看下吧2017-04-04
淺談Java如何實(shí)現(xiàn)一個(gè)基于LRU時(shí)間復(fù)雜度為O(1)的緩存
這篇文章主要介紹了淺談Java如何實(shí)現(xiàn)一個(gè)基于LRU時(shí)間復(fù)雜度為O(1)的緩存,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08
教你1秒將本地SpringBoot項(xiàng)目jar包部署到Linux環(huán)境(超詳細(xì)!)
spring Boot簡(jiǎn)化了Spring應(yīng)用的開(kāi)發(fā)過(guò)程,遵循約定優(yōu)先配置的原則提供了各類開(kāi)箱即用(out-of-the-box)的框架配置,下面這篇文章主要給大家介紹了關(guān)于1秒將本地SpringBoot項(xiàng)目jar包部署到Linux環(huán)境的相關(guān)資料,超級(jí)詳細(xì),需要的朋友可以參考下2023-04-04
Java設(shè)計(jì)模式中的門(mén)面模式詳解
門(mén)面模式又叫外觀模式(Facade Pattern),主要用于隱藏系統(tǒng)的復(fù)雜性,并向客戶端提供了一個(gè)客戶端可以訪問(wèn)系統(tǒng)的接口,本文通過(guò)實(shí)例代碼給大家介紹下java門(mén)面模式的相關(guān)知識(shí),感興趣的朋友一起看看吧2022-09-09
Springboot項(xiàng)目通過(guò)redis實(shí)現(xiàn)接口的冪等性
這篇文章主要為大家介紹了Springboot項(xiàng)目通過(guò)redis實(shí)現(xiàn)接口的冪等性,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12
微服務(wù)架構(gòu)設(shè)計(jì)RocketMQ進(jìn)階事務(wù)消息原理詳解
這篇文章主要介紹了為大家介紹了微服務(wù)架構(gòu)中RocketMQ進(jìn)階層面事務(wù)消息的原理詳解,有需要的朋友可以借鑒參考下希望能夠有所幫助2021-10-10
Springdoc替換swagger的實(shí)現(xiàn)步驟分解
最近在spring看到的,spring要對(duì)api文檔動(dòng)手了,有些人說(shuō)swagger不好用,其實(shí)也沒(méi)那么不好用,有人說(shuō)代碼還是有點(diǎn)侵入性,這倒是真的,我剛試了springdoc可以說(shuō)還是有侵入性但是也可以沒(méi)有侵入性,這就看你對(duì)文檔有什么要求了2023-02-02

