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

public class Result<T> {
private int ret;
private String msg;
private T data;
// 此处省略getter和setter方法
}

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

一个反序列化的迭代

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

public class Item {
private String name;
private String value;
// 此处省略getter和setter方法
}

JSON数据:

{
"data":{
"name":"username",
"value":"root"
},
"msg":"Success",
"ret":0
}

当拿到上面的数据时,我们想到其对应的类型是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的经验,以为找到了通用处理的捷径,遂封装了一个处理这种类型的工具方法:

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

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

Result<Item> result = parseResultV1(json);

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

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

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

{
"data":"Hello,world!",
"msg":"Success",
"ret":0
}

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

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

v5

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

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

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

v6

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

private static <T> Result<List<T>> parseListResult(String json, Class<T> clazz) {
return JSONObject.parseObject(json, buildType(Result.class, List.class, Item.class));
} private static Type buildType(Type... types) {
ParameterizedTypeImpl beforeType = null;
if (types != null && types.length > 0) {
for (int i = types.length - 1; i > 0; i--) {
beforeType = new ParameterizedTypeImpl(new Type[]{beforeType == null ? types[i] : beforeType}, null, types[i - 1]);
}
}
return beforeType;
}

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

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

v7

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

com.alibaba.fastjson.TypeReference

看看TypeReference的源码:

protected TypeReference(Type... actualTypeArguments) {
Class<?> thisClass = this.getClass();
Type superClass = thisClass.getGenericSuperclass();
ParameterizedType argType = (ParameterizedType)((ParameterizedType)superClass).getActualTypeArguments()[0];
Type rawType = argType.getRawType();
Type[] argTypes = argType.getActualTypeArguments();
int actualIndex = 0; for(int i = 0; i < argTypes.length; ++i) {
if (argTypes[i] instanceof TypeVariable) {
argTypes[i] = actualTypeArguments[actualIndex++];
if (actualIndex >= actualTypeArguments.length) {
break;
}
}
} Type key = new ParameterizedTypeImpl(argTypes, thisClass, rawType);
Type cachedType = (Type)classTypeCache.get(key);
if (cachedType == null) {
classTypeCache.putIfAbsent(key, key);
cachedType = (Type)classTypeCache.get(key);
} this.type = cachedType;
}

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

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

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

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

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

com.alibaba.fastjson.util.ParameterizedTypeImpl

那至于ParameterizedTypeImpl怎么回事呢?

import java.lang.reflect.ParameterizedType;
// ...其他省略... public class ParameterizedTypeImpl implements ParameterizedType {
public ParameterizedTypeImpl(Type[] actualTypeArguments, Type ownerType, Type rawType){
this.actualTypeArguments = actualTypeArguments;
this.ownerType = ownerType;
this.rawType = rawType;
}
// ...其他省略...
}

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

Type
所有已知子接口:
GenericArrayType, ParameterizedType, TypeVariable<D>, WildcardType
所有已知实现类:
Class

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

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

结合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类似但是又因泛型擦除丢失的一些类型信息。

ParameterizedTypeImpl使用踩坑

详见:fastjson反序列化使用不当导致内存泄露

fastjson反序列化多层嵌套泛型类与java中的Type类型的更多相关文章

  1. Java中的Type

    Type是Java 编程语言中所有类型的公共高级接口(官方解释),也就是Java中所有类型的“爹”:其中,“所有类型”的描述尤为值得关注.它并不是我们平常工作中经常使用的 int.String.Lis ...

  2. java中的null类型---有关null的9件事

    摘自 https://blog.csdn.net/qq_25077777/article/details/80174763 今天听到一个问题,java中的null类型,null竟然是一种类型 java ...

  3. Java中primitive type的线程安全性

    Java中primite type,如char,integer,bool之类的,它们的读写操作都是atomic的,但是有几个例外: long和double类型不是atomic的,因为long和doub ...

  4. Java中的Bigdecimal类型运算

    Java中的Bigdecimal类型运算 双精度浮点型变量double可以处理16位有效数.在实际应用中,需要对更大或者更小的数进行运算和处理.Java在java.math包中提 供的API类BigD ...

  5. 理解Java中的字符串类型

    1.Java内置对字符串的支持: 所谓的内置支持,即不用像C语言通过char指针实现字符串类型,并且Java的字符串编码是符合Unicode编码标准,这也意味着不用像C++那样通过使用string和w ...

  6. Java中的枚举类型详解

    枚举类型介绍 枚举类型(Enumerated Type) 很早就出现在编程语言中,它被用来将一组类似的值包含到一种类型当中.而这种枚举类型的名称则会被定义成独一无二的类型描述符,在这一点上和常量的定义 ...

  7. 关于java中的事件类型

    java中的Date是为了证明:天才的程序员也会犯错: java中的Calendar是为了证明:普通的程序员也会犯错. ———————————————————— stackoverflow上大部分都推 ...

  8. Java中的集合类型的继承关系图

    Java中的集合类型的继承关系图

  9. java中,字符串类型的时间数据怎样转换成date类型。

    将字符串类型的时间转换成date类型可以使用SimpleDateFormat来转换,具体方法如下:1.定义一个字符串类型的时间:2.创建一个SimpleDateFormat对象并设置格式:3.最后使用 ...

随机推荐

  1. Android--iOS抓取崩溃日志

    android闪退获取日志方法: 1.下载adb工具包 2.注意事项 请确保电脑上只连接了一台手机设备(最好只连接一条USB线),同时确保手机已开启USB调试模式(可通过手机助手查看连接状态) 3.A ...

  2. Opengl中的GLUT下的回调函数

    void glutDisplayFunc(void (*func)(void)); 注册当前窗口的显示回调函数 参数: func:形为void func()的函数,完成具体的绘制操作 这个函数告诉GL ...

  3. MIDA Converter Basic patched for RAD Studio 10.1.2 Berlin (VCL转换到FMX)

    Mida is the only way to try to convert your project from VCL to FireMonkey. Version after version, M ...

  4. .net 线程基础 ThreadPool 线程池

    1. ThreadPool 线程池异步: //public static bool QueueUserWorkItem(WaitCallback callBack); //public static ...

  5. Android-监听操作系统短信

    想要访问Android操作系统的ContentProvider就需要明白以下原理: 在Android操作系统里面的 /packsges/目录: apps: 很多的系统应用,例如:联系人,浏览器,音乐播 ...

  6. Hadoop 文件命令

    * 文件操作 * 查看目录文件 * $ hadoop dfs -ls /user/cl * * 创建文件目录 * $ hadoop dfs -mkdir /user/cl/temp * * 删除文件  ...

  7. Excel一对多查询(index+small+if)

    一.学习 一对多查询模式化数组公式: =INDEX(区域,SMALL(IF(条件,行号数组,4^8),ROW(A1))) 三键齐按(ctrl+shift+回车) 在具有多个符合条件的情况下,提取和匹配 ...

  8. 《Think in Java》20 21(并发)

    chapter 20 注解 三种标准注解和四种元注解: 编写注解处理器 chapter 21 并发 基本的线程机制 定义任务 package cn.test; public class LiftOff ...

  9. 《锋利的jQuery 第二版》chapter 1~

    chapter 1 认识 jQuery jquery.js(开发版),jquery.min.js(生产版) window.onload 与 $(document).ready() 的对比: jquer ...

  10. gitlab迁移版本库(保留原版本库的所有内容)

    如果你想从别的 Git 托管服务那里复制一份源代码到新的 Git 托管服务器上的话,可以通过以下步骤来操作. 1) 从原地址克隆一份裸版本库,比如原本托管于 GitHub git clone --ba ...