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

news2024/9/29 17:21:47

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自定义,推送告警,死信消息推送失败告警等)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/398650.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C/C++每日一练(20230309)

目录 1. 罗马数字转整数 ★ 2. 最大数 ★★ 3. 有效数字 ★★★ &#x1f31f; 每日一练刷题专栏 C/C 每日一练 ​专栏 Python 每日一练 ​专栏 1. 罗马数字转整数 罗马数字包含以下七种字符&#xff1a; I&#xff0c; V&#xff0c; X&#xff0c; L&#xff0c;C&…

达梦关系型数据库

达梦关系型数据库一、DM8 安装1. 安装包下载2. Docker 安装3. Linux 安装4. Windows 安装二、DM 管理工具三、命令行交互工具 DIsql四、DM8 SQL使用1. 创建模式2. 创建表3. 修改表4. 读写数据5. 查看库下所有的表名6. 查看表字段信息GitHub: link. 欢迎star国产自主研发的大型…

数据挖掘(2.2)--数据预处理

目录 二、数据描述 1.描述数据中心趋势 1.1平均值和截断均值 1.2加权平均值 1.3中位数&#xff08;Median&#xff09;和众数(Mode) 2.描述数据的分散程度 2.1箱线图 2.2方差和标准差 2.3正态分布 3.数据清洗 3.1数据缺失的处理 3.2数据清洗 二、数据描述 描述数…

自动化测试实战篇(9),jmeter常用断言方法,一文搞懂9种测试字段与JSON断言

Jmeter常用的断言主要有&#xff0c;JSON断言和响应断言这两种方式。 断言主要就是帮助帮助人工进行快速接口信息验证避免繁杂的重复的人工去验证数据 第一种响应断言Apply to&#xff1a;表示应用范围测试字段&#xff1a;针对响应数据进行不同的匹配响应文本响应代码响应信息…

【Go自学第一节】GoLang 数据类型

和Java类型&#xff0c;go拥有多种数据类型&#xff0c;可以把它分为四个大类基础类型、聚合类型、引用类型和接口类型 一、基本数据类型 基本数据类型又可以细分为&#xff1a;数字类型&#xff08;整型、浮点型&#xff09;、布尔类型、字符串类型 整型 Go 的整型分为有符号…

计算机网络的166个概念你知道几个 第十一部分

计算机网络数据链路层和物理层节点&#xff1a;一般指链路层协议中的设备。链路&#xff1a;一般把沿着通信路径连接相邻节点的通信信道称为链路。MAC 协议&#xff1a;媒体访问控制协议&#xff0c;它规定了帧在链路上传输的规则。奇偶校验位&#xff1a;一种差错检测方式&…

基于gin-vue-admin[gin+gorm]手动实现crud(全)

使用Gin-Vue- Admin框架手动实现crud 在gva框架下自己手动实现一个CRUD的操作&#xff0c;该操作将会结合gen进行探讨学习&#xff0c;具体实现可以看下面代码的实现&#xff0c;项目目录层级分为api层&#xff0c;service层&#xff0c;model层&#xff0c;common层&#xff…

1/4、1/2、整车悬架天棚主动控制仿真分析合集

目录 前言 1. 1/4悬架系统 1.1数学模型 1.2仿真分析 2. 1/2悬架系统 2.1数学模型 2.2仿真分析 3. 整车悬架系统 3.1数学模型 3.2仿真分析 4.总结 参考文献 前言 对于天棚控制相比大家不陌生&#xff0c;它是由美国的Karnopp提出&#xff0c;利用假设的与天棚固连…

【数据结构】链表相关题目(简单版)

&#x1f680;write in front&#x1f680; &#x1f4dc;所属专栏&#xff1a; 初阶数据结构 &#x1f6f0;️博客主页&#xff1a;睿睿的博客主页 &#x1f6f0;️代码仓库&#xff1a;&#x1f389;VS2022_C语言仓库 &#x1f3a1;您的点赞、关注、收藏、评论&#xff0c;是…

软件测试10

Linux和数据库 1.Linux命令&#xff1a;软件测试第一个任务&#xff0c;一般都需要进行环境搭建&#xff0c;一部分环境搭建内容是在服务器上实现的&#xff0c;跟服务器交互需要使用Linux命令。&#xff08;因为Linux没有图形化页面&#xff09; 2.数据库&#xff1a;所有的软…

ccf-csp 202112-3登机牌条码

题目背景 西西艾弗岛景色优美&#xff0c;游人如织。但是&#xff0c;由于和外界的交通只能靠渡船&#xff0c;交通的不便严重制约了岛上旅游业的发展。西西艾弗岛管委会经过努力&#xff0c;争取到了一笔投资&#xff0c;建设了一个通用航空机场。在三年紧锣密鼓的主体建设后…

Jetpack Compose 中的动态加载、插件化技术探索

在传统的 Android 开发模式中&#xff0c;由于界面过分依赖于 Activity、Fragment这样的组件&#xff0c;一个业务模块中往往会存在着大量的 Activity 类&#xff0c;因此诞生了很多的插件化框架&#xff0c;这些插件化框架基本都是想方设法的使用各种Hook/反射手段来解决使用未…

c++11auto

autoc11中auto并不代表一种实际的数据类型&#xff0c;它只是一个类型声明的占位符&#xff0c;auto也并不是再所有场景下都能推导出变量的实际类型&#xff0c;使用auto不需要进行初始化&#xff0c;让编译器推导出它的实际类型&#xff0c;再编译阶段将auto占位符替换为真正的…

没有比这更详细的-压测工具Jmeter介绍及使用了

一、压测工具选型 1.1、前言 压力测试是每一个Web应用程序上线之前都需要做的一个测试&#xff0c;他可以帮助我们发现系统中的瓶颈问题&#xff0c;减少发布到生产环境后出问题的几率&#xff1b;预估系统的承载能力&#xff0c;使我们能根据其做出一些应对措施。所以压力测…

【Linux基础篇】一、Linux入门基础命令

一、Linux基础命令 1、Linux的目录结构 /&#xff0c;根目录是最顶级的目录了Linux只有一个顶级目录&#xff1a;/路径描述的层次关系同样适用/来表示/home/itheima/a.txt&#xff0c;表示根目录下的home文件夹内有itheima文件夹&#xff0c;内有a.txt 2、ls命令 功能&#…

数字孪生GIS智慧风场Web3D可视化运维系统

随着国家双碳目标的实施&#xff0c;新能源发电方式逐渐代替了污染大气层的火力发电&#xff0c;其中风力发电相比于光伏发电具有能量密度高、发电小时数长、生命周期达20-25年之久等独特的优势。风能取之不尽、用之不竭&#xff0c;在新型能源互联网下&#xff0c;风力发电有可…

三、GC算法垃圾回收器

文章目录&#xff08;持续更新中... ...CMS and G1&#xff09;概述如何判断对象存活引用计数法根可达算法GC算法(垃圾回收算法)分代收集理论标记-清除算法复制算法标记-整理算法垃圾回收器概述Serial收集器Parallel收集器Parallel Old收集器CMS收集器G1收集器常用的收集器组合…

STM32实战项目-串口打印

前言&#xff1a; 本小结主要实现串口打印功能&#xff0c;主要将上一结的状态机运行次数&#xff0c;通过串口在串口终端上打印出来&#xff0c;硬件电路上主要是TTL转USB驱动电路&#xff0c;软件上主要有状态机函数&#xff0c;串口发送函数等调试工具是串口助手。 目录 1…

机器学习学习记录1:假设空间

我们可以把学习过程看作一个在所有假设组成的空间中进行搜索的过程&#xff0c;搜索目标是找到与训练集"匹配" 的假设&#xff0c;即能够将训练集中的瓜判断正确的假设.假设的表示一旦确定&#xff0c;假设空间及其规模大小就确定了.对于西瓜问题&#xff0c;这里我们…

[MySQL索引]2.索引的底层原理(一)

索引的底层原理&#xff08;一&#xff09;B-树索引B树索引tips: ​ 通过使用malloc/new来申请4字节的内存&#xff0c;但是操作系统不是说每一次用户申请4字节内存&#xff0c;我就只分配4字节&#xff0c;这样申请次数多了就要涉及频繁的用户态和内核态的切换&#xff0c;开销…