Gson序列化Class对象报错解决办法

在使用Gson进行RPC Demo时遇到序列化Class对象报错,问题源于未注册自定义的TypeAdapter。通过分析Gson源码,发现需要在GsonBuilder中注册自定义的TypeAdapterFactory,确保在Gson构造器中先添加自定义factories,再添加默认factories,从而成功解决报错问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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. 源码分析

  1. 老规矩,报错了,在日志中,点击TypeAdapters.java:73,下载源码,查看具体源代码在这里插入图片描述
    打印的日志是:试图去序列化这个Class,但是没有注册类型适配器。
    啥是类型适配器?TypeAdapter? 什么是TypeAdapter啊,重写的write和read是干嘛用的,看一下这个类吧

  2. 查看这个TypeAdapter抽象类,注释写明是进行JSON和对象的转换的。write用于把对象转为JSON, read用于读取JSON并且转为对象。还说可以自定义实现,只要实现TypeAdapter接口就行了。不管了,先回过头看一下这个CLASS在哪里调用,看一下这个对象的引用吧
    在这里插入图片描述
    在这里插入图片描述

  3. 点击实现:
    在这里插入图片描述
    哦豁,是在创建TypeAdapterFactory工厂的时候创建的,看源码,还是老套路,Idea左侧的Structure打开(快速了解类的内部结构),卧槽了。好多的TypeAdapter和对应的TypeAdapterFactory。
    在这里插入图片描述

  4. 回想一下,Gson本身是序列化的,那么就应该包含各种类型适配器,这没问题,对Java的各种类型进行序列化和反序列化。随便点开String的TypeAdapter看看。read和write都有实现啊,没有报错,那咋Class的TypeAdapter就直接在代码里面抛异常了嘞?(看第一个截图)
    在这里插入图片描述

  5. 那我是不是得重新写一遍这个ClassTypeAdapter啊,把read和write实现一遍?这里有这么多的TypeAdapter,全给对应的TypeAdapterFactory拿去使用了,我需要在哪里接入这个ClassTypeAdapter呢?那我看看这个CLASS_FACTORY在哪里被调用。
    在这里插入图片描述

  6. 哎玛,在Gson这个类这添加的,全部类型都在这加,并且最后Gson的对象this.factories指向了这个factories的集合。
    在这里插入图片描述

  7. 看一下这些代码的位置,用来是构造器里面的代码。
    在这里插入图片描述
    既然是在构造器的时候构造的,那么Gson肯定是考虑到了拓展问题,看一下,参数里最后有3个TypeAdapterFactory的列表。第三个的名字可以啊,要添加的factories!, 有线索。
    用户的类型适配器,是从构造器传进来的,再挖一下,构造器是在哪里使用。
    在这里插入图片描述

  8. 继续看Gson构造器的调用方,看看能在哪里塞个自定义的TypeAdapter或者对应的工厂类进去。
    在这里插入图片描述
    只有两个地方调用了这个Gson。第一个Gson()的构造方法调用到的,但是这后面的三个参数都是空列表呀,还有一个是Gsonbuilder,builder一般用于构建对象并且加载组件用。看看这个GsonBuilder.create()

  9. 确实,Gsonbuilder.create最后会调用new Gson() 返回一个Gson对象,里面factories是由588行,this.factories传入。
    在这里插入图片描述

  10. 那接下来看看这个factories是在哪里被赋值的
    在这里插入图片描述
    好东西啊,有registerTypeAdapter和registerTypeAdapterFactory这两个方法add。还是两个public方法。
    赶紧瞅一瞅。registerTypeAdaper。看注释第一句,配置自定义序列化和反序列化的。
    下方的registerTypeAdaperFactory还得去构造个一个工厂,有点麻烦啊,先看看registerTypeAdaper方法能不能解决问题。
    在这里插入图片描述

  11. 注释写的很清楚啊,配置Gson for 自定义序列化和反序列化,但是如果一个类型适配器在这之前被这种方式注册,那么就会被覆盖。那我那个ClassTypeAdaper不就可以在这重新自定义了吗!!!!而且好像有两种方式哦,第一种是实现JsonSerializer和JsonDeserializer。第二种是继承TypeAdaper。
    在这里插入图片描述

  12. 那我先写一个咯。
    在这里插入图片描述
    还是报错。
    好像有点问题,上面已经说了,如果一个类型适配器在这之前被这种方式注册,那么就会被覆盖。哦豁,那么我应该先注册自定义的factories,再注册Gson提供的factories。前面是有个builder.create的,里面是先加入这个factories,然后再new Gson()的。
    在这里插入图片描述
    并且这new Gson中,上来就把builder里面的自定义的factories先加入Gson的factories的
    在这里插入图片描述

  13. 那我再试试。先别new Gson. 用builder试试。哦豁好起来了。
    在这里插入图片描述

总结:

以上是代码分析流程。整体的逻辑deug过,这里不展开去梳理gson的代码逻辑了。我好奇为什么Class的TypeAdapter没有实现,而是抛异常,这是chatgpt给出的答案。
在这里插入图片描述

一般而已,哪里抛异常了,就在那里打个断点,然后debug可以看到完整的堆栈信息,记得先把源码下载下来,同时类的结构在idea中记得展开,多阅读注释信息。一般构造器都会提供口子给自定义组件支持自定义开发,如果没有,可以通过多态(会破坏设计原则)或者反射等方式去实现。比如spring-kafka.这两天写一篇spring-kafka兜底方案是如何实现的(重试如何告警,死信队列Topic自定义,推送告警,死信消息推送失败告警等)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值