1. 背景
昨天在写RPC的基础Demo的时候,使用JSON作为序列化方式,然后在序列化对象的时候,报错了。
我复现一下该报错:
public class GsonTest {
public static void main(String[] args) {
new Gson().toJson(String.class);
}
}
具体错误如下:
Exception in thread "main" java.lang.UnsupportedOperationException: Attempted to serialize java.lang.Class: java.lang.String. Forgot to register a type adapter?
at com.google.gson.internal.bind.TypeAdapters$1.write(TypeAdapters.java:73)
at com.google.gson.internal.bind.TypeAdapters$1.write(TypeAdapters.java:69)
at com.google.gson.TypeAdapter$1.write(TypeAdapter.java:191)
at com.google.gson.Gson.toJson(Gson.java:704)
at com.google.gson.Gson.toJson(Gson.java:683)
at com.google.gson.Gson.toJson(Gson.java:638)
at com.google.gson.Gson.toJson(Gson.java:618)
at chat.rpc.GsonTest.main(GsonTest.java:13)
2. 解决方法:
main {
Gson gson = new GsonBuilder()
.registerTypeAdapter(Class.class, new ClassTypeAdapter()).
create();
}
// 方式一:继承 TypeAdapter
class ClassTypeAdapter extends TypeAdapter<Class> {
@Override
public void write(JsonWriter out, Class value) throws IOException {
out.value(value.getName());
}
@Override
public Class read(JsonReader in) throws IOException {
String clazzName = in.nextString();
try {
return Class.forName(clazzName);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
// 方式二:实现两个接口,JsonSerializer和JsonDeserialize。
class ClassTypeAdapter2 implements JsonSerializer<Class>, JsonDeserializer<Class> {
@Override
public Class deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
String asString = json.getAsString();
try {
return Class.forName(asString);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
@Override
public JsonElement serialize(Class src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(src.getName());
}
}
3. 源码分析
-
老规矩,报错了,在日志中,点击TypeAdapters.java:73,下载源码,查看具体源代码
打印的日志是:试图去序列化这个Class,但是没有注册类型适配器。
啥是类型适配器?TypeAdapter? 什么是TypeAdapter啊,重写的write和read是干嘛用的,看一下这个类吧 -
查看这个TypeAdapter抽象类,注释写明是进行JSON和对象的转换的。write用于把对象转为JSON, read用于读取JSON并且转为对象。还说可以自定义实现,只要实现TypeAdapter接口就行了。不管了,先回过头看一下这个CLASS在哪里调用,看一下这个对象的引用吧
-
点击实现:
哦豁,是在创建TypeAdapterFactory工厂的时候创建的,看源码,还是老套路,Idea左侧的Structure打开(快速了解类的内部结构),卧槽了。好多的TypeAdapter和对应的TypeAdapterFactory。
-
回想一下,Gson本身是序列化的,那么就应该包含各种类型适配器,这没问题,对Java的各种类型进行序列化和反序列化。随便点开String的TypeAdapter看看。read和write都有实现啊,没有报错,那咋Class的TypeAdapter就直接在代码里面抛异常了嘞?(看第一个截图)
-
那我是不是得重新写一遍这个ClassTypeAdapter啊,把read和write实现一遍?这里有这么多的TypeAdapter,全给对应的TypeAdapterFactory拿去使用了,我需要在哪里接入这个ClassTypeAdapter呢?那我看看这个CLASS_FACTORY在哪里被调用。
-
哎玛,在Gson这个类这添加的,全部类型都在这加,并且最后Gson的对象this.factories指向了这个factories的集合。
-
看一下这些代码的位置,用来是构造器里面的代码。
既然是在构造器的时候构造的,那么Gson肯定是考虑到了拓展问题,看一下,参数里最后有3个TypeAdapterFactory的列表。第三个的名字可以啊,要添加的factories!, 有线索。
用户的类型适配器,是从构造器传进来的,再挖一下,构造器是在哪里使用。
-
继续看Gson构造器的调用方,看看能在哪里塞个自定义的TypeAdapter或者对应的工厂类进去。
只有两个地方调用了这个Gson。第一个Gson()的构造方法调用到的,但是这后面的三个参数都是空列表呀,还有一个是Gsonbuilder,builder一般用于构建对象并且加载组件用。看看这个GsonBuilder.create() -
确实,Gsonbuilder.create最后会调用new Gson() 返回一个Gson对象,里面factories是由588行,this.factories传入。
-
那接下来看看这个factories是在哪里被赋值的
好东西啊,有registerTypeAdapter和registerTypeAdapterFactory这两个方法add。还是两个public方法。
赶紧瞅一瞅。registerTypeAdaper。看注释第一句,配置自定义序列化和反序列化的。
下方的registerTypeAdaperFactory还得去构造个一个工厂,有点麻烦啊,先看看registerTypeAdaper方法能不能解决问题。
-
注释写的很清楚啊,配置Gson for 自定义序列化和反序列化,但是如果一个类型适配器在这之前被这种方式注册,那么就会被覆盖。那我那个ClassTypeAdaper不就可以在这重新自定义了吗!!!!而且好像有两种方式哦,第一种是实现JsonSerializer和JsonDeserializer。第二种是继承TypeAdaper。
-
那我先写一个咯。
还是报错。
好像有点问题,上面已经说了,如果一个类型适配器在这之前被这种方式注册,那么就会被覆盖。哦豁,那么我应该先注册自定义的factories,再注册Gson提供的factories。前面是有个builder.create的,里面是先加入这个factories,然后再new Gson()的。
并且这new Gson中,上来就把builder里面的自定义的factories先加入Gson的factories的
-
那我再试试。先别new Gson. 用builder试试。哦豁好起来了。
总结:
以上是代码分析流程。整体的逻辑deug过,这里不展开去梳理gson的代码逻辑了。我好奇为什么Class的TypeAdapter没有实现,而是抛异常,这是chatgpt给出的答案。
一般而已,哪里抛异常了,就在那里打个断点,然后debug可以看到完整的堆栈信息,记得先把源码下载下来,同时类的结构在idea中记得展开,多阅读注释信息。一般构造器都会提供口子给自定义组件支持自定义开发,如果没有,可以通过多态(会破坏设计原则)或者反射等方式去实现。比如spring-kafka.这两天写一篇spring-kafka兜底方案是如何实现的(重试如何告警,死信队列Topic自定义,推送告警,死信消息推送失败告警等)