原创:扣钉日记(微信公众号ID:codelogs),欢送分享,转载请保留出处。

问题

在Java的json框架中,Gson是应用得比拟宽泛的一个,其Gson类提供了toJson()fromJson()办法,别离用来序列化与反序列化。

json序列化用得最多的场景是在调用内部服务接口时,大抵如下:

@Data@AllArgsConstructorpublic class Response<T>{    int code;    String message;    T body;}@Data@AllArgsConstructorpublic class PersonInfo{    long id;    String name;    int age;}/** * 服务端 */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);    }}

因为大多数接口设计中,都会有对立的响应码构造,因而大多我的项目都会像下面一样,设计一个通用Response类来对应这种对立响应码构造,是很常见的状况。

但会发现,在反序列化过程中,传入指标类型时,应用了一段很奇怪的代码,即new TypeToken<Response<PersonInfo>>(){}.getType(),那它是什么?为啥要应用它?

TypeToken是什么

为什么要应用TypeToken呢?咱们间接应用Response<PersonInfo>.class行不行?如下:

能够发现,java并不容许这么应用!

那传Response.class呢?如下:

能够发现,代码能跑起来,然而Body变成了LinkedHashMap类型,这是因为传给gson的类型是Response.class,gson并不知道body属性是什么类型,那它只能应用LinkedHashMap这个默认的json对象类型了。

这就是TypeToken由来的起因,对于带泛型的类,应用TypeToken能力失去精确的类型信息,那TypeToken是怎么取到精确的类型的呢?

首先,new TypeToken<Response<PersonInfo>>(){}.getType()实际上是定义了一个匿名外部类的对象,而后调用了这个对象的getType()办法。

看看getType()的实现,如下:

逻辑也比较简单,先通过getGenericSuperclass()获取了此对象的父类,即TypeToken<Response<PersonInfo>>,而后又通过getActualTypeArguments()[0]获取了理论类型参数,即Response<PersonInfo>

额,逻辑看起来说得通,但不是说Java泛型会擦除吗?这里不会擦除?

从所周知,java泛型擦除产生在编译期,ok,那我模仿下面的原理,写个空类继承TypeToken<Response<PersonInfo>>,而后编译这个类之后再反编译一下,看类型到底擦除没!

public class PersonResponseTypeToken extends TypeToken<Response<PersonInfo>> {}

反编译后果如下:

也就是说,被继承的父类上的泛型是不擦除的。

其它应用场景

有时为了编程的不便,常常会有框架将近程调用接口化,相似上面这样:

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();                //设置申请数据                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());                }                //获取响应数据                if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) {                    throw new RuntimeException("近程调用产生异样:url:" + conn.getURL() + ", requestBody:" + requestBody);                }                String responseStr = IOUtils.toString(conn.getInputStream(), StandardCharsets.UTF_8);                //响应后果反序列化为具体对象                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());    }}

这样做的益处是,开发人员不用再关怀如何发近程申请了,只须要定义与调用接口即可。

但下面调用过程中会有一个问题,就是获取的response对象中body属性是LinkedHashMap,起因是gson反序列化时是通过method.getReturnType()来获取返回类型的,而返回类型中的泛型会被擦除掉。

要解决这个问题也很简略,和下面TypeToken一样的情理,定义一个空类PersonResponse来继承Response<PersonInfo>,而后将返回类型定义为PersonResponse,如下:

public class PersonResponse extends Response<PersonInfo> {}public interface PersonApi {    @RequestMapping(path = "/person")    PersonResponse getPersonById(@RequestParam(name = "id") Long id);}

而后你就会发现,gson能够正确辨认到body属性的类型了。

往期内容

密码学入门
接口偶然超时,竟又是JVM进展的锅!
耗时几个月,终于找到了JVM进展十几秒的起因
mysql的timestamp会存在时区问题?
真正了解可反复读事务隔离级别
字符编码解惑