Gson中的TypeToken與泛型擦除詳情
問(wèn)題
在Java的json框架中,Gson是使用得比較廣泛的一個(gè),其Gson
類(lèi)提供了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); } } /** * 客戶(hù)端 */ 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)來(lái)對(duì)應(yīng)這種統(tǒng)一響應(yīng)碼結(jié)構(gòu),是很常見(jiàn)的情況。
但會(huì)發(fā)現(xiàn),在反序列化過(guò)程中,傳入目標(biāo)類(lèi)型時(shí),使用了一段很奇怪的代碼,即new TypeToken<Response<PersonInfo>>(){}.getType()
,那它是什么?為啥要使用它?
TypeToken是什么
為什么要使用TypeToken呢?我們直接使用Response<PersonInfo>.class
行不行?如下:
可以發(fā)現(xiàn),java并不允許這么使用!
那傳Response.class
呢?如下:
可以發(fā)現(xiàn),代碼能跑起來(lái),但是Body變成了LinkedHashMap
類(lèi)型,這是因?yàn)閭鹘ogson的類(lèi)型是Response.class
,gson并不知道body屬性是什么類(lèi)型,那它只能使用LinkedHashMap
這個(gè)默認(rèn)的json對(duì)象類(lèi)型了。
這就是TypeToken由來(lái)的原因,對(duì)于帶泛型的類(lèi),使用TypeToken才能得到準(zhǔn)確的類(lèi)型信息,那TypeToken是怎么取到準(zhǔn)確的類(lèi)型的呢?
首先,new TypeToken<Response<PersonInfo>>(){}.getType()
實(shí)際上是定義了一個(gè)匿名內(nèi)部類(lèi)的對(duì)象,然后調(diào)用了這個(gè)對(duì)象的getType()
方法。
看看getType()
的實(shí)現(xiàn),如下:
邏輯也比較簡(jiǎn)單,先通過(guò)getGenericSuperclass()
獲取了此對(duì)象的父類(lèi),即TypeToken<Response<PersonInfo>>
,然后又通過(guò)getActualTypeArguments()[0]
獲取了實(shí)際類(lèi)型參數(shù),即Response<PersonInfo>
。
額,邏輯看起來(lái)說(shuō)得通,但不是說(shuō)Java泛型會(huì)擦除嗎?這里不會(huì)擦除?
從所周知,java泛型擦除發(fā)生在編譯期,ok,那我模擬上面的原理,寫(xiě)個(gè)空類(lèi)繼承TypeToken<Response<PersonInfo>>
,然后編譯這個(gè)類(lèi)之后再反編譯一下,看類(lèi)型到底擦除沒(méi)!
public class PersonResponseTypeToken extends TypeToken<Response<PersonInfo>> { }
反編譯結(jié)果如下:
也就是說(shuō),被繼承的父類(lèi)上的泛型是不擦除的。
其它使用場(chǎng)景
有時(shí)為了編程的方便,經(jīng)常會(huì)有框架將遠(yuǎn)程調(diào)用接口化,類(lèi)似下面這樣:
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)獲取返回類(lèi)型的,而返回類(lèi)型中的泛型會(huì)被擦除掉。
要解決這個(gè)問(wèn)題也很簡(jiǎn)單,和上面TypeToken一樣的道理,定義一個(gè)空類(lèi)PersonResponse
來(lái)繼承Response<PersonInfo>
,然后將返回類(lèi)型定義為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屬性的類(lèi)型了。
到此這篇關(guān)于Gson中的TypeToken與泛型擦除詳情的文章就介紹到這了,更多相關(guān)Gson TypeToken內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
關(guān)于JwtToken使用-重點(diǎn)看一下過(guò)期時(shí)間
這篇文章主要介紹了關(guān)于JwtToken使用-重點(diǎn)看一下過(guò)期時(shí)間,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07Java實(shí)現(xiàn)訂單超時(shí)未支付自動(dòng)取消的8種方法總結(jié)
這篇文章主要為大家介紹了Java實(shí)現(xiàn)訂單超時(shí)未支付自動(dòng)取消功能的8種不同方法,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2022-08-08redisson特性及優(yōu)雅實(shí)現(xiàn)示例
這篇文章主要為大家介紹了redisson特性及優(yōu)雅實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11Java Set集合的遍歷及實(shí)現(xiàn)類(lèi)的比較
這篇文章主要介紹了Java Set集合的遍歷及實(shí)現(xiàn)類(lèi)的比較的相關(guān)資料,需要的朋友可以參考下2017-03-03Spring中的@PostConstruct注解使用方法解析
這篇文章主要介紹了Spring中的@PostConstruct注解使用方法解析,@PostConstruct注解是用來(lái)處理在@Autowired注入屬性后init()方法之前,對(duì)一些零散的屬性進(jìn)行賦值的注解,需要的朋友可以參考下2023-11-11SpringBoot v2.2以上重復(fù)讀取Request Body內(nèi)容的解決方案
這篇文章主要介紹了SpringBoot v2.2以上重復(fù)讀取Request Body內(nèi)容的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10