Gson中的TypeToken與泛型擦除詳情
問(wèn)題
在Java的json框架中,Gson是使用得比較廣泛的一個(gè),其Gson類提供了toJson()與fromJson()方法,分別用來(lái)序列化與反序列化。
json序列化用得最多的場(chǎng)景是在調(diào)用外部服務(wù)接口時(shí),大致如下:
@Data
@AllArgsConstructor
public class Response<T>{
int code;
String message;
T body;
}
@Data
@AllArgsConstructor
public class PersonInfo{
long id;
String name;
int age;
}
/**
* 服務(wù)端
*/
public class Server {
public static String getPersonById(Long id){
PersonInfo personInfo = new PersonInfo(1234L, "zhangesan", 18);
Response<PersonInfo> response = new Response<>(200, "success", personInfo);
//序列化
return new Gson().toJson(response);
}
}
/**
* 客戶端
*/
public class Client {
public static void getPerson(){
String responseStr = Server.getPersonById(1234L);
//反序列化
Response<PersonInfo> response = new Gson().fromJson(responseStr, new TypeToken<Response<PersonInfo>>(){}.getType());
System.out.println(response);
}
}由于大多數(shù)接口設(shè)計(jì)中,都會(huì)有統(tǒng)一的響應(yīng)碼結(jié)構(gòu),因此大多項(xiàng)目都會(huì)像上面一樣,設(shè)計(jì)一個(gè)通用Response類來(lái)對(duì)應(yīng)這種統(tǒng)一響應(yīng)碼結(jié)構(gòu),是很常見(jiàn)的情況。
但會(huì)發(fā)現(xiàn),在反序列化過(guò)程中,傳入目標(biāo)類型時(shí),使用了一段很奇怪的代碼,即new TypeToken<Response<PersonInfo>>(){}.getType(),那它是什么?為啥要使用它?
TypeToken是什么
為什么要使用TypeToken呢?我們直接使用Response<PersonInfo>.class行不行?如下:

可以發(fā)現(xiàn),java并不允許這么使用!
那傳Response.class呢?如下:

可以發(fā)現(xiàn),代碼能跑起來(lái),但是Body變成了LinkedHashMap類型,這是因?yàn)閭鹘ogson的類型是Response.class,gson并不知道body屬性是什么類型,那它只能使用LinkedHashMap這個(gè)默認(rèn)的json對(duì)象類型了。
這就是TypeToken由來(lái)的原因,對(duì)于帶泛型的類,使用TypeToken才能得到準(zhǔn)確的類型信息,那TypeToken是怎么取到準(zhǔn)確的類型的呢?
首先,new TypeToken<Response<PersonInfo>>(){}.getType()實(shí)際上是定義了一個(gè)匿名內(nèi)部類的對(duì)象,然后調(diào)用了這個(gè)對(duì)象的getType()方法。
看看getType()的實(shí)現(xiàn),如下:

邏輯也比較簡(jiǎn)單,先通過(guò)getGenericSuperclass()獲取了此對(duì)象的父類,即TypeToken<Response<PersonInfo>>,然后又通過(guò)getActualTypeArguments()[0]獲取了實(shí)際類型參數(shù),即Response<PersonInfo>。
額,邏輯看起來(lái)說(shuō)得通,但不是說(shuō)Java泛型會(huì)擦除嗎?這里不會(huì)擦除?
從所周知,java泛型擦除發(fā)生在編譯期,ok,那我模擬上面的原理,寫個(gè)空類繼承TypeToken<Response<PersonInfo>>,然后編譯這個(gè)類之后再反編譯一下,看類型到底擦除沒(méi)!
public class PersonResponseTypeToken extends TypeToken<Response<PersonInfo>> {
}反編譯結(jié)果如下:

也就是說(shuō),被繼承的父類上的泛型是不擦除的。
其它使用場(chǎng)景
有時(shí)為了編程的方便,經(jīng)常會(huì)有框架將遠(yuǎn)程調(diào)用接口化,類似下面這樣:
public class RemoteUtil {
private static final ConcurrentMap<Class, Object> REMOTE_CACHE = new ConcurrentHashMap<>();
public static <T> T get(Class<T> clazz) {
return clazz.cast(REMOTE_CACHE.computeIfAbsent(clazz, RemoteUtil::getProxyInstance));
}
private static Object getProxyInstance(Class clazz) {
return Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{clazz}, (proxy, method, args) -> {
Gson gson = new Gson();
String path = method.getAnnotation(RequestMapping.class).path()[0];
HttpURLConnection conn = null;
try {
conn = (HttpURLConnection) new URL("http://localhost:8080/" + path).openConnection();
conn.setRequestMethod("POST");
conn.setDoOutput(true);
conn.setDoInput(true);
conn.connect();
//設(shè)置請(qǐng)求數(shù)據(jù)
JsonObject requestBody = new JsonObject();
try (Writer out = new OutputStreamWriter(conn.getOutputStream(), StandardCharsets.UTF_8)) {
int i = 0;
for (Parameter parameter : method.getParameters()) {
String name = parameter.getAnnotation(RequestParam.class).name();
requestBody.add(name, gson.toJsonTree(args[i]));
i++;
}
out.write(requestBody.toString());
}
//獲取響應(yīng)數(shù)據(jù)
if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) {
throw new RuntimeException("遠(yuǎn)程調(diào)用發(fā)生異常:url:" + conn.getURL() + ", requestBody:" + requestBody);
}
String responseStr = IOUtils.toString(conn.getInputStream(), StandardCharsets.UTF_8);
//響應(yīng)結(jié)果反序列化為具體對(duì)象
return gson.fromJson(responseStr, method.getReturnType());
} finally {
if (conn != null) {
conn.disconnect();
}
}
});
}
}
public interface PersonApi {
@RequestMapping(path = "/person")
Response<PersonInfo> getPersonById(@RequestParam(name = "id") Long id);
}
public class Client {
public static void getPerson() {
Response<PersonInfo> response = RemoteUtil.get(PersonApi.class).getPersonById(1234L);
System.out.println(response.getBody());
}
}這樣做的好處是,開(kāi)發(fā)人員不必再關(guān)心如何發(fā)遠(yuǎn)程請(qǐng)求了,只需要定義與調(diào)用接口即可。
但上面調(diào)用過(guò)程中會(huì)有一個(gè)問(wèn)題,就是獲取的response對(duì)象中body屬性是LinkedHashMap,原因是gson反序列化時(shí)是通過(guò)method.getReturnType()來(lái)獲取返回類型的,而返回類型中的泛型會(huì)被擦除掉。
要解決這個(gè)問(wèn)題也很簡(jiǎn)單,和上面TypeToken一樣的道理,定義一個(gè)空類PersonResponse來(lái)繼承Response<PersonInfo>,然后將返回類型定義為PersonResponse
如下:
public class PersonResponse extends Response<PersonInfo> {
}
public interface PersonApi {
@RequestMapping(path = "/person")
PersonResponse getPersonById(@RequestParam(name = "id") Long id);
}然后你就會(huì)發(fā)現(xiàn),gson可以正確識(shí)別到body屬性的類型了。
到此這篇關(guān)于Gson中的TypeToken與泛型擦除詳情的文章就介紹到這了,更多相關(guān)Gson TypeToken內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot使用注解進(jìn)行分頁(yè)的實(shí)現(xiàn)示例
分頁(yè)使用可以說(shuō)非常普遍了,有時(shí)候會(huì)需要非常靈活的方式去開(kāi)啟或關(guān)閉分頁(yè),嘗試使用一下注解的方式來(lái)進(jìn)行分頁(yè),本文主要介紹了SpringBoot使用注解進(jìn)行分頁(yè)的實(shí)現(xiàn)示例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03
Java?數(shù)據(jù)庫(kù)連接池Druid?的介紹
這篇文章主要給大家分享的是?Java?數(shù)據(jù)庫(kù)連接池Druid?的介紹,Druid是一個(gè)JDBC組件,它包括三部分:?DruidDriver?代理Driver,能夠提供基于Filter-Chain模式的插件體系。?DruidDataSource?高效可管理的數(shù)據(jù)庫(kù)連接池,下面來(lái)看看文中的詳細(xì)內(nèi)容,需要的朋友也可以參考一下2021-11-11
WPF實(shí)現(xiàn)自定義一個(gè)自刪除的多功能ListBox
這篇文章主要為大家詳細(xì)介紹了如何利用WPF實(shí)現(xiàn)自定義一個(gè)自刪除的多功能ListBox,文中示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2022-12-12
java 自己實(shí)現(xiàn)DataSource實(shí)現(xiàn)實(shí)例
這篇文章主要介紹了java 自己實(shí)現(xiàn)DataSource實(shí)現(xiàn)代碼的相關(guān)資料,需要的朋友可以參考下2017-05-05
Java設(shè)計(jì)實(shí)現(xiàn)一個(gè)針對(duì)各種類型的緩存
這篇文章主要為大家詳細(xì)介紹了Java如何設(shè)計(jì)實(shí)現(xiàn)一個(gè)針對(duì)各種類型的緩存,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,感興趣的小伙伴可以了解一下2023-11-11
Spring Boot和Vue跨域請(qǐng)求問(wèn)題原理解析
這篇文章主要介紹了Spring Boot和Vue跨域請(qǐng)求問(wèn)題原理解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-12-12
Spring AOP的底層實(shí)現(xiàn)方式-代理模式
這篇文章主要介紹了Spring AOP的底層實(shí)現(xiàn)方式-代理模式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11

