Gson中的TypeToken与泛型擦除详情

 更新时间:2022年09月06日 08:32:15   作者:扣钉日记  
这篇文章主要介绍了Gson中的TypeToken与泛型擦除详情,其Gson类提供了toJson()与fromJson()方法,分别用来序列化与反序列化,更多相关内容需要的朋友可以参考一下

问题

在Java的json框架中,Gson是使用得比较广泛的一个,其Gson类提供了toJson()fromJson()方法,分别用来序列化与反序列化。

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

@Data
@AllArgsConstructor
public class Response<T>{
    int code;
    String message;
    T body;
}

@Data
@AllArgsConstructor
public 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属性的类型了。

到此这篇关于Gson中的TypeToken与泛型擦除详情的文章就介绍到这了,更多相关Gson TypeToken内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Spring实现泛型注入的示例详解

    Spring实现泛型注入的示例详解

    Spring 4.0版本中更新了很多新功能,其中比较重要的一个就是对带泛型的Bean进行依赖注入的支持。本文将通过实例详细讲讲Spring如何实现泛型注入,需要的可以参考一下
    2022-07-07
  • Java实现聊天机器人完善版

    Java实现聊天机器人完善版

    这篇文章主要为大家详细介绍了Java实现聊天机器人完善版,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-07-07
  • 关于JwtToken使用-重点看一下过期时间

    关于JwtToken使用-重点看一下过期时间

    这篇文章主要介绍了关于JwtToken使用-重点看一下过期时间,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-07-07
  • Java输入/输出流体系详解

    Java输入/输出流体系详解

    这篇文章主要介绍了Java输入/输出流体系详解,涉及字节流和字符流,输入输出体系,转换流,以及文件的读写等相关内容,具有一定参考价值,需要的朋友可以了解下。
    2017-11-11
  • Java实现订单超时未支付自动取消的8种方法总结

    Java实现订单超时未支付自动取消的8种方法总结

    这篇文章主要为大家介绍了Java实现订单超时未支付自动取消功能的8种不同方法,文中的示例代码讲解详细,感兴趣的小伙伴可以了解一下
    2022-08-08
  • 详解IntelliJ IDEA 自定义方法注解模板

    详解IntelliJ IDEA 自定义方法注解模板

    本篇文章主要介绍了IntelliJ IDEA 自定义方法注解模板,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-12-12
  • redisson特性及优雅实现示例

    redisson特性及优雅实现示例

    这篇文章主要为大家介绍了redisson特性及优雅实现示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-11-11
  • Java Set集合的遍历及实现类的比较

    Java Set集合的遍历及实现类的比较

    这篇文章主要介绍了Java Set集合的遍历及实现类的比较的相关资料,需要的朋友可以参考下
    2017-03-03
  • Spring中的@PostConstruct注解使用方法解析

    Spring中的@PostConstruct注解使用方法解析

    这篇文章主要介绍了Spring中的@PostConstruct注解使用方法解析,@PostConstruct注解是用来处理在@Autowired注入属性后init()方法之前,对一些零散的属性进行赋值的注解,需要的朋友可以参考下
    2023-11-11
  • SpringBoot v2.2以上重复读取Request Body内容的解决方案

    SpringBoot v2.2以上重复读取Request Body内容的解决方案

    这篇文章主要介绍了SpringBoot v2.2以上重复读取Request Body内容的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-10-10

最新评论