背景
后台请求某个接口,该接口返回的JSON数据中包含data属性,data属性会对应一个java实体,在使用FastJson反序列化的时候发现使用parseObject(String str, Class<T> objectClass)并不能将data正确反序列化成某个java实体,而是直接以JsonObject类型存在,伪代码如下:
请求接口方法
public class ConfirmationClient {
public Result<QrInfoResult> getQrInfo(String keyWord) {
Result<QrInfoResult> qrInfo = confirmationService.getQrInfo(keyWord);
// 这里会类型转换的错误:JsonObjec不能转为QrInfoResult
QrInfoResult data = qrInfo.getData();
return null;
}
}
public class ConfirmationService {
public Result<QrInfoResult> getQrInfo(String txHash) {
Asserts.notEmpty(txHash, "证书编号");
// 请求接口
String result = requestApi(QrinfoParam.builder().keyWord(txHash).build(), Constants.genUrl(Constants.URI_CONFIRM_QR));
// 序列化
return JSONObject.parseObject(result, Result.class);
}
}
Result类信息
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Result<T> implements Serializable {
private static final int SUCCESS_CODE = 0;
private Integer code;
private String message;
private T data;
private long timestamp;
public boolean success() {
return this.code == SUCCESS_CODE;
}
}
Result类中data属性对应的实体
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class QrInfoResult {
/**
* 编号
*/
private String confirmSeq;
/**
* 时间
*/
private String confirmTime;
}
debug调试图
可以看到,这样方式,data的类型为JSONObject,尝试获取data时:
原因
原因很简单,就是FastJson不知道应该把里边的元素序列化成什么类型只能转成JSONObject(鬼知道你Result类的data是什么玩意)。那为啥返回值是Result,而方法要求的返回值是Result<QrInfoResult>,在编译的时候没有报错呢?其实是因为泛型的类型擦除,Result<QrInfoResult>擦除完后类似于这样Result<Object>,所以编译并没有报错。
解决方法
FastJson提供了相应方法
public static <T> T parseObject(String str, TypeReference typeReference, com.alibaba.fastjson.parser.Feature... features) {
return parseObject(str, typeReference.getType(), features);
}
我们只要把代码做如下调整
public Result<QrInfoResult> getQrInfo(String txHash) {
Asserts.notEmpty(txHash, "证书编号");
// 请求接口
String result = requestApi(QrinfoParam.builder().keyWord(txHash).build(), Constants.genUrl(Constants.URI_CONFIRM_QR));
// 序列化
return JSONObject.parseObject(result, new TypeReference<Result<QrInfoResult>>() {
});
}
debug调试
再深一层
1. 匿名内部子类
new TypeReference<Result<QrInfoResult>>() {
}
这种方式乍一看比较陌生,那这个呢
new Runnable() {
@Override
public void run() {
// ...
}
};
是不是好一些了,这种方式是创建了一个匿名内部子类,这个类有几个特点(不全):
- 必须要继承一个父类或者实现一个接口,当然也仅能只继承一个父类或者实现一个接口
- 匿名内部类中是不能定义构造函数的
所以new TypeReference<Result<QrInfoResult>>() {}这种方式实际是会创建一个新的类的实例,这个新类是TypeReference的子类。再看FastJson的源码
protected TypeReference() {
Type superClass = this.getClass().getGenericSuperclass();
Type type = ((ParameterizedType)superClass).getActualTypeArguments()[0];
Type cachedType = (Type)classTypeCache.get(type);
if (cachedType == null) {
classTypeCache.putIfAbsent(type, type);
cachedType = (Type)classTypeCache.get(type);
}
this.type = cachedType;
}
构造函数是protected修饰的,其他包的类必须要成为其子类才能访问其类成员(protected 修饰的类成员可以被三种类所访问:该类自身、与它在同一个包中的其他类以及在其他包中的该类的子类)。
那创建子类实例的作用是什么呢?是为了能够获取泛型信息
2. 获取泛型信息
protected TypeReference() {
Type superClass = this.getClass().getGenericSuperclass();
Type type = ((ParameterizedType)superClass).getActualTypeArguments()[0];
Type cachedType = (Type)classTypeCache.get(type);
if (cachedType == null) {
classTypeCache.putIfAbsent(type, type);
cachedType = (Type)classTypeCache.get(type);
}
this.type = cachedType;
}
-
getGenericSuperclass()- 返回此
Class所表示的实体(类、接口、基本类型等)的超类的Type
- 返回此
-
((ParameterizedType)superClass)ParameterizedType是参数化类型,指的是声明时带有泛型的类型
-
getActualTypeArguments()- 返回此类型实际类型参数的
Type对象的数组 [0]可以获得第一个泛型
- 返回此类型实际类型参数的
deub看下:
那么getGenericSuperclass的泛型信息是如何获取的呢?
在class 文件中保存了一个 Signature 属性,里面存储了泛型参数信息, getGenericSuperclass 就是去读取了这里的信息。
Signature 是泛型类型的一个特有属性,存储了源码里声明的泛型类型,在编译期,如果源码里指明了类型的情况就可以存储这个指明的类型,如果没有指明类型,那存储的就是这个泛型擦除后的类型。
那么使用new TypeReference<Result<QrInfoResult>>() {}的方式会生成一个子类,大致如下:
class ZiTypeReference extends TypeReference<Result<QrInfoResult>>{}
这个类在源码中指明了泛型的信息,即Result<QrInfoResult>,那么就可以通过getGenericSuperclass获取到泛型的真正类型。
所以,其实 getGenericSuperclass 并没有获取运行时类型的魔力,它只是读取了编译期确定并存储在 Class 文件里的泛型类型而已。