zoukankan      html  css  js  c++  java
  • fastjson反序列化多层嵌套泛型类与java中的Type类型

    在使用springmvc时,我们通常会定义类似这样的通用类与前端进行交互,以便于前端可以做一些统一的处理:

    1.  
      public class Result<T> {
    2.  
      private int ret;
    3.  
      private String msg;
    4.  
      private T data;
    5.  
      // 此处省略getter和setter方法
    6.  
      }

    这样的类序列化为json后,js反序列化处理起来毫无压力。但是如果rest接口的消费端就是java呢,java泛型的类型擦除却容易引入一些障碍。

    一个反序列化的迭代

    先定义一个类,后面的例子会用到:

    1.  
      public class Item {
    2.  
      private String name;
    3.  
      private String value;
    4.  
      // 此处省略getter和setter方法
    5.  
      }

    JSON数据:

    1.  
      {
    2.  
      "data":{
    3.  
      "name":"username",
    4.  
      "value":"root"
    5.  
      },
    6.  
      "msg":"Success",
    7.  
      "ret":0
    8.  
      }

    当拿到上面的数据时,我们想到其对应的类型是Result<Item>,所以得想办法将这个json数据反序列化为这个类型才行。

    v1

    JSONObject.parseObject(json, Result<Item>.class);,编译器就报错了Cannot select parameterized type

    v2

    JSONObject.parseObject(json, Result.class);,执行没问题。但是没有Item类型信息,fastjson不可能跟你心有灵犀一点通知道该把data转为Item类型,result.getData().getClass()结果是com.alibaba.fastjson.JSONObject,也算是个妥善处理吧。

    v3

    找了一下前人的经验,使用TypeReference来处理,JSONObject.parseObject(json, new TypeReference<Result<Item>>(){});,终于“完美”解决!

    v4

    有了v3的经验,以为找到了通用处理的捷径,遂封装了一个处理这种类型的工具方法:

    1.  
      private static <T> Result<T> parseResultV1(String json) {
    2.  
      return JSONObject.parseObject(json, new TypeReference<Result<T>>() {
    3.  
      });
    4.  
      }

    并且把采用v3的地方改用了此parseResult方法:

    Result<Item> result = parseResultV1(json);

    以为万事大吉,连测都没测试就把代码提交了。测都不测试,当然难以有好结果了:

    1.  
      System.out.println(result.getData());
    2.  
      // java.lang.ClassCastException: com.alibaba.fastjson.JSONObject cannot be cast to Item

    很显然parseResultV1把Item的类型信息丢掉了。

    1.  
      {
    2.  
      "data":"Hello,world!",
    3.  
      "msg":"Success",
    4.  
      "ret":0
    5.  
      }

    试了一下Result形式的,parseResultV1可以成功将其反序列化。推测(没有看fastjson具体实现)是fastjson刚好检测到data字段就是String类型,并将其赋值到data字段上了。仔细看parseObject并没有报错,而是在getData()时报错的,联系到java的泛型擦除,我们在用getData(),应该把data当作Object类型这么看:

    1.  
      String data = (String)result.getData();
    2.  
      System.out.println(data);

    v5

    原来TypeReference的构造器是可以传入参数的,

    1.  
      private static <T> Result<T> parseResultV2(String json, Class<T> clazz) {
    2.  
      return JSONObject.parseObject(json, new TypeReference<Result<T>>(clazz) {
    3.  
      });
    4.  
      }

    这个可以真的可以完美反序列化Result<Item>了。

    v6

    后来发现parseResultV2无法处理类似Result<List<T>>,原来TypeReference无法处理嵌套的泛型(这里指的是类型参数未确定,而不是类似Result<List<Item>>类型参数已经确定)。借用Fastjson解析多级泛型的几种方式—使用class文件来解析多级泛型里的方法,新增加一个专门处理List类型的方法:

    1.  
      private static <T> Result<List<T>> parseListResult(String json, Class<T> clazz) {
    2.  
      return JSONObject.parseObject(json, buildType(Result.class, List.class, Item.class));
    3.  
      }
    4.  
       
    5.  
      private static Type buildType(Type... types) {
    6.  
      ParameterizedTypeImpl beforeType = null;
    7.  
      if (types != null && types.length > 0) {
    8.  
      for (int i = types.length - 1; i > 0; i--) {
    9.  
      beforeType = new ParameterizedTypeImpl(new Type[]{beforeType == null ? types[i] : beforeType}, null, types[i - 1]);
    10.  
      }
    11.  
      }
    12.  
      return beforeType;
    13.  
      }

    或者根据这里只有两层,简单如下:

    1.  
      private static <T> Result<List<T>> parseListResult(String json, Class<T> clazz) {
    2.  
      ParameterizedTypeImpl inner = new ParameterizedTypeImpl(new Type[]{clazz}, null, List.class);
    3.  
      ParameterizedTypeImpl outer = new ParameterizedTypeImpl(new Type[]{inner}, null, Result.class);
    4.  
      return JSONObject.parseObject(json, outer);
    5.  
      }

    v7

    todo: 上面两个方法已经可以满足现有需要,有时间再看看能否将两个方法统一为一个。

    com.alibaba.fastjson.TypeReference

    看看TypeReference的源码:

    1.  
      protected TypeReference(Type... actualTypeArguments) {
    2.  
      Class<?> thisClass = this.getClass();
    3.  
      Type superClass = thisClass.getGenericSuperclass();
    4.  
      ParameterizedType argType = (ParameterizedType)((ParameterizedType)superClass).getActualTypeArguments()[0];
    5.  
      Type rawType = argType.getRawType();
    6.  
      Type[] argTypes = argType.getActualTypeArguments();
    7.  
      int actualIndex = 0;
    8.  
       
    9.  
      for(int i = 0; i < argTypes.length; ++i) {
    10.  
      if (argTypes[i] instanceof TypeVariable) {
    11.  
      argTypes[i] = actualTypeArguments[actualIndex++];
    12.  
      if (actualIndex >= actualTypeArguments.length) {
    13.  
      break;
    14.  
      }
    15.  
      }
    16.  
      }
    17.  
       
    18.  
      Type key = new ParameterizedTypeImpl(argTypes, thisClass, rawType);
    19.  
      Type cachedType = (Type)classTypeCache.get(key);
    20.  
      if (cachedType == null) {
    21.  
      classTypeCache.putIfAbsent(key, key);
    22.  
      cachedType = (Type)classTypeCache.get(key);
    23.  
      }
    24.  
       
    25.  
      this.type = cachedType;
    26.  
      }

    实际上它首先获取到了泛型的类型参数argTypes,然后遍历这些类型参数,如果遇到是TypeVariable类型的则用构造函数传入的Type将其替换,然后此处理后的argTypes基于ParameterizedTypeImpl构造出一个新的Type,这样的新的Type就可以具备我们期待的Type的各个泛型类型参数的信息了。所以fastjson就能够符合我们期望地反序列化出了Result<Item>

    正是由于这个处理逻辑,所以对于v6里的Result<List<T>>就无法处理了,它只能处理单层多类型参数的情况,而无法处理嵌套的泛型参数。

    没找到TypeReference的有参构造函数用法的比较正式的文档,但是基于源码的认识,我们应该这么使用TypeReference的有参构造函数:

    1.  
      new TypeReference<Map<T1, T2>>(clazz1, clazz2){}
    2.  
      new TypeReference<Xxx<T1, T2, T3>>(clazz1, clazz2, clazz3){}

    也就是构造器里的Type列表要与泛型类型参数一一对应。

    com.alibaba.fastjson.util.ParameterizedTypeImpl

    那至于ParameterizedTypeImpl怎么回事呢?

    1.  
      import java.lang.reflect.ParameterizedType;
    2.  
      // ...其他省略...
    3.  
       
    4.  
      public class ParameterizedTypeImpl implements ParameterizedType {
    5.  
      public ParameterizedTypeImpl(Type[] actualTypeArguments, Type ownerType, Type rawType){
    6.  
      this.actualTypeArguments = actualTypeArguments;
    7.  
      this.ownerType = ownerType;
    8.  
      this.rawType = rawType;
    9.  
      }
    10.  
      // ...其他省略...
    11.  
      }

    以前也没了解过ParameterizedType,与它相关的还有

    1.  
      Type
    2.  
      所有已知子接口:
    3.  
      GenericArrayType, ParameterizedType, TypeVariable<D>, WildcardType
    4.  
      所有已知实现类:
    5.  
      Class

    先看看这次已经用到的ParameterizedType接口(下列注释是从jdk中文文档拷贝过来,不太好理解)

    1.  
      public interface ParameterizedType extends Type {
    2.  
      //返回表示此类型实际类型参数的 Type 对象的数组。
    3.  
      //注意,在某些情况下,返回的数组为空。如果此类型表示嵌套在参数化类型中的非参数化类型,则会发生这种情况。
    4.  
      Type[] getActualTypeArguments();
    5.  
      //返回 Type 对象,表示此类型是其成员之一的类型。
    6.  
      Type getOwnerType();
    7.  
      //返回 Type 对象,表示声明此类型的类或接口。
    8.  
      Type getRawType();
    9.  
      }

    结合ParameterizedTypeImpl(Type[] actualTypeArguments, Type ownerType, Type rawType)的例子来理解:
    new ParameterizedTypeImpl(new Type[]{clazz}, null, List.class)用于构造List<T>

    关于Type

    泛型是Java SE 1.5的新特性,Type也是1.5才有的。它是在java加入泛型之后为了扩充类型引入的。与Type相关的一些类或者接口来表示与Class类似但是又因泛型擦除丢失的一些类型信息。

    转帖:https://www.cnblogs.com/liqipeng/p/9148545.html

  • 相关阅读:
    解析网页源码方式
    vue踩坑--细节决定成败
    fallowing-travelvue
    学会不怕
    eslint代码规范检测
    三次握手+四次挥手
    小白的学习笔记
    es6数组
    css知识整理
    JavaScript之事件循环,宏任务与微任务
  • 原文地址:https://www.cnblogs.com/ceshi2016/p/11880220.html
Copyright © 2011-2022 走看看