一、背景
在Android项目中想封装retrofit提供get
、post
等公共的方法,如:
返回的统一数据格式:
{
"errCode":0,
"errMsg":"ok",
"data":...
}
data中的可能是以下情况:
- 基本数据类型的数据,如:
true
、1
、字符串
等 - json对象,如
{"key1":"value"}
- json数组,如
[{"key1":"value1"},{"key2":"value2"}]
这时候data的类型就不好确定了,在传统的Retrofit
方式使用时,可能是以下用法:
interface ApiService {
@GET
Observable> getxxx1(...)
@GET
Observable> getxxx2(...)
@GET
Observable> getxxx3(...)
}
class JsonClass {}
class ResponseEntity{
public int errCode;
public String errMsg;
public T data;
}
通过传入具体的ResponseEntity
中的泛型类,这样Retrofit
中就能通过GsonConverterFactory进行类型转换,但是这种就会存在以下的问题:⚠️
-
不能对返回的ResponseEntity的错误码进行统一处理
比如我们的业务中定义了一些固定的错误码,如errCode=0表示正常返回数据,errCode=1001表示token过期需要跳转到登录页面重新登录,errCode>xxx时需要弹toast提示等,这些如果不统一处理就需要在每个调用接口的
onSuccess
方法中做一下判断。 -
将整个返回的数据都暴露给了业务,但业务只关心的是
T
所代表的数据对于框架来说应该是给业务提供自己需要的数据就OK了,一些底层的判断还是能不给暴漏给业务就尽量不暴漏给业务
-
对于组件化的项目中存在多个ApiService
在这些年的面试过程中,发现有些面试者在组件化的项目中,每个组件中都存在一个ApiService,每个ApiService都承担着不同的业务
module
的业务接口数据请求,由于Java并不支持类别分类(即同一个类可以分散到不同的文件中,iOS中是Category,类似kotlin里面的扩展函数),所以会导致项目中有多个 Retrofit 实例
基于以上原因,我才想在我们的项目中对Retrofit
做进一步封装,来避免上述问题
首先我尝试这么写:
interface ApiService {
@GET
Observable get(Map params)
@POST
Observable> post(Map params)
}
会报错❌com.google.gson.internal.LinkedTreeMap cannot be cast to xxx
,究其主要原因是Java中存在泛型擦除,这就导致在GsonConverterFactory中进行转换时找不到 T 的真实类型,在编译后 ResponseEntity
可以想象成ResponseEntity
,这就引申出本篇文章的主题
二、Gson反序列化泛型数据的方法
如何将json字符串转换成带泛型的数据结构
class ResponseData{
private int errCode;
private String errMsg;
private T data;
// .... 省略get set
}
class Result{
private String name;
// .... 省略get set
}
public static void main(String[] args) {
Gson gson = new Gson();
String r1Str = "{\"errCode\":0,\"errMsg\":\"ok\",\"data\":{\"name\":\"result1\"}}";
ResponseData responseData = gson.fromJson(r1Str, ResponseData.class);
System.out.println(responseData.getData().getName());
}
报错❌:com.google.gson.internal.LinkedTreeMap cannot be cast to Result
,这里就是因为有泛型擦除的问题,在代码里不能写ResponseData
,所以转换过程中并不知道 T
的真实类型是什么,换一种方式:
public static void main(String[] args) {
Gson gson = new Gson();
String r1Str = "{\"errCode\":0,\"errMsg\":\"ok\",\"data\":{\"name\":\"result1\"}}";
ResponseData responseData = gson.fromJson(r1Str,new TypeToken>(){}.getType());
System.out.println(responseData.getData().getName());
}
使用 TypeToken 来做获取 T 的真实类型,那么TypeToken是如何实现的呢,下面我们来看看源码
protected TypeToken() {
this.type = getSuperclassTypeParameter(this.getClass());
this.rawType = Types.getRawType(this.type);
this.hashCode = this.type.hashCode();
}
static Type getSuperclassTypeParameter(Class> subclass) {
// getGenericSuperclass的作用: 返回此 Class 所表示的实体(类、接口、基本类型或 void)的直接超类的 Type。
Type superclass = subclass.getGenericSuperclass();
if (superclass instanceof Class) {
throw new RuntimeException("Missing type parameter.");
} else {
// ParameterizedType的作用:获得元数据中泛型签名类型(泛型真实类型)
ParameterizedType parameterized = (ParameterizedType)superclass;
// getActualTypeArguments的作用:获得真实类型参数
return Types.canonicalize(parameterized.getActualTypeArguments()[0]);
}
}
public final Type getType() {
return this.type;
}
这里核心是使用了getGenericSuperclass和ParameterizedType,下面一小节我们就详细了解下反射技术中的这几个api类的作用,了解是如何通过它去获取泛型的真实类型的。
这里还有一个细节需要注意的:
new TypeToken{}
?
TypeToken
的构造函数是protected的,表示本包以及子类可以使用,加一个 {} 就相当于继承了这个类,相当于:
class TypeToken$0 extends TypeToken>{}
TypeToken> sToken = new TypeToken$0();
三、如何通过反射获取泛型的真实类型?
当我们对一个泛型类进行反射时,需要到泛型中的真实数据类型,来完成如json反序列化的操作。此时需要通过 Type
体系来完成。 Type
接口包含了一个实现类(Class)和四个实现接口,他们分别是:
-
TypeVariable
泛型类型变量。可以获得泛型上下限等信息;
-
ParameterizedType
具体的泛型类型,可以获得元数据中泛型签名类型(泛型真实类型)
-
GenericArrayType
当需要描述的类型是泛型类的数组时,比如List[],Map[],此接口会作为Type的实现。
-
WildcardType
通配符泛型,获得上下限信息;
3.1、TypeVariable
public class TestType {
K key;
V value;
public static void main(String[] args) throws Exception {
// 获取字段的类型
Field fk = TestType.class.getDeclaredField("key");
Field fv = TestType.class.getDeclaredField("value");
// getGenericType
TypeVariable keyType = (TypeVariable)fk.getGenericType();
TypeVariable valueType = (TypeVariable)fv.getGenericType();
// getName 方法
System.out.println(keyType.getName());
System.out.println(valueType.getName());
// getGenericDeclaration 方法
System.out.println(keyType.getGenericDeclaration());
System.out.println(valueType.getGenericDeclaration());
// getBounds 方法
System.out.println("K 的上界:");
for (Type type : keyType.getBounds()) {
System.out.println(type);
}
System.out.println("V 的上界:");
for (Type type : valueType.getBounds()) {
System.out.println(type); }
}
}
3.2、ParameterizedType
public class TestType {
Map map;
public static void main(String[] args) throws Exception {
Field f = TestType.class.getDeclaredField("map");
System.out.println(f.getGenericType()); // java.util.Map
ParameterizedType pType = (ParameterizedType) f.getGenericType();
System.out.println(pType.getRawType()); // interface java.util.Map
for (Type type : pType.getActualTypeArguments()) {
System.out.println(type); // 打印两遍: class java.lang.String
}
}
}
3.3、GenericArrayType
public class TestType {
List[] lists;
public static void main(String[] args) throws Exception {
Field f = TestType.class.getDeclaredField("lists");
GenericArrayType genericType = (GenericArrayType) f.getGenericType();
System.out.println(genericType.getGenericComponentType());
}
}
3.4、WildcardType
public class TestType {
private List extends Number> a; // 上限
private List super String> b; //下限
public static void main(String[] args) throws Exception {
Field fieldA = TestType.class.getDeclaredField("a");
Field fieldB = TestType.class.getDeclaredField("b");
// 先拿到范型类型
ParameterizedType pTypeA = (ParameterizedType) fieldA.getGenericType();
ParameterizedType pTypeB = (ParameterizedType) fieldB.getGenericType();
// 再从范型里拿到通配符类型
WildcardType wTypeA = (WildcardType) pTypeA.getActualTypeArguments()[0];
WildcardType wTypeB = (WildcardType) pTypeB.getActualTypeArguments()[0];
// 方法测试
System.out.println(wTypeA.getUpperBounds()[0]); // class java.lang.Number
System.out.println(wTypeB.getLowerBounds()[0]); // class java.lang.String
// 看看通配符类型到底是什么, 打印结果为: ? extends java.lang.Number
System.out.println(wTypeA);
}
}
从上面四个类可以看出,要获取泛型的真实类型需要用到ParameterizedType
,通过 getActualTypeArguments 获取。
我们了解了这些API之后,那如何自己实现一个获取泛型类型的方法呢,下一小节我们就来看看
四、自定义TypeToken
public class MyTypeToken {
final Type type;
protected MyTypeToken() {
this.type = getSuperclassTypeParameter(this.getClass());
}
static Type getSuperclassTypeParameter(Class> subclass) {
Type superclass = subclass.getGenericSuperclass();
if (superclass instanceof Class) {
throw new RuntimeException("Missing type parameter.");
} else {
ParameterizedType parameterized = (ParameterizedType)superclass;
return parameterized.getActualTypeArguments()[0];
}
}
public Type getType() {
return type;
}
}
使用方式:
String r1Str = "{\"errCode\":0,\"errMsg\":\"ok\",\"data\":{\"name\":\"result1\"}}";
System.out.println(r1Str);
ResponseData responseData = gson.fromJson(r1Str,new MyTypeToken>(){}.getType());
System.out.println(responseData.getData().getName());
这里需要注意的是⚠️new MyTypeToken