Gson踩坑笔记:为什么对象的构造方法没有被执行?
前言
最近做项目遇到了一个很奇怪的问题,情况如下:
创建对象TestBean,其中type和name需要接口返回并解析,time字段需要客户端修改,做一些必要的记录,希望time的默认值为10:
val jsonStr ="{type: 99, name:\"superman\"}"data class TestBean(val type: Int, val name: String, var time: Long = 10)
在运行前,我认为这段代码非常完美,但是结果却很意外:
难道Gson把构造方法中的time设置成0了吗?,再次修改代码:
data class TestBean(val type: Int, val name: String) {var time: Long = 10override fun toString(): String {return "type:$type, name:$name, time:$time"}
}
默认情况下,data class的toString方法只会打印构造方法中的属性,所以还需要重写一下toString。我把time属性从构造方法中移出,这次应该是稳得一匹了吧:
what???,time的值并不是10,虽然没有找到原因,但是我还可以再改,男人不可以说不行:
data class TestBean(val type: Int, val name: String) {var time: Long = 10init {time = 10Log.e("lzp", "TestBean create")}override fun toString(): String {return "type:$type, name:$name, time:$time"}
}
这一次,我在init方法中,手动设置time=10,并且输出日志,对象创建时一定会执行init方法,绝对ojbk:
init方法也没执行???好的,让我静下心来仔细的找到问题到底出现在哪里。
正文
经过刚才的踩坑,可以肯定的是,Gson没有调用TestBean的构造方法。那么他是怎么创建TestBean的呢?一般来说创建对象有以下几种方式:
调用构造方法:TestBean() (new关键字或者反射最终都是使用了构造方法)
复制:Object.copy()
还是要从Gson的源码去分析这个问题:
val testBean = Gson().fromJson<TestBean>(jsonStr, TestBean::class.java)
我们对fromJson方法进行追踪,发现fromJson重写了好几个,最终会定位到:
public <T> T fromJson(JsonReader reader, Type typeOfT) throws JsonIOException, JsonSyntaxException {boolean isEmpty = true;boolean oldLenient = reader.isLenient();reader.setLenient(true);try {reader.peek();isEmpty = false;TypeToken<T> typeToken = (TypeToken<T>) TypeToken.get(typeOfT);// 获取具体解析的TypeAdapterTypeAdapter<T> typeAdapter = getAdapter(typeToken);// 返回解析的结果T object = typeAdapter.read(reader);return object;} catch (EOFException e) {...}
}
由于TypeAdapter的子类实在是太多了,如果要去仔细的翻源码太耗时间,直接断点:
我们要找的就是ReflectiveTypeAdapterFactory的内部类Adapter,直接找他的read方法:
@Override public T read(JsonReader in) throws IOException {if (in.peek() == JsonToken.NULL) {in.nextNull();return null;}// 这里创建对象了T instance = constructor.construct();// 解析对应的属性,忽略try {in.beginObject();while (in.hasNext()) {String name = in.nextName();BoundField field = boundFields.get(name);if (field == null || !field.deserialized) {in.skipValue();} else {field.read(in, instance);}}} catch (IllegalStateException e) {throw new JsonSyntaxException(e);} catch (IllegalAccessException e) {throw new AssertionError(e);}in.endObject();return instance;}
终于找到创建对象的代码,让我们看看这个constructor到底是个什么玩意,经过各种定位,最终我们找到了分析问题的最关键类:ConstructorConstructor。
public <T> ObjectConstructor<T> get(TypeToken<T> typeToken) {final Type type = typeToken.getType();final Class<? super T> rawType = typeToken.getRawType();// 优先使用Type获取对象的构造方法final InstanceCreator<T> typeCreator = (InstanceCreator<T>) instanceCreators.get(type);if (typeCreator != null) {return new ObjectConstructor<T>() {@Override public T construct() {return typeCreator.createInstance(type);}};}// 再次使用rawType获取对象的构造方法@SuppressWarnings("unchecked") // types must agreefinal InstanceCreator<T> rawTypeCreator =(InstanceCreator<T>) instanceCreators.get(rawType);if (rawTypeCreator != null) {return new ObjectConstructor<T>() {@Override public T construct() {return rawTypeCreator.createInstance(type);}};}/*** 分割线以上都是instanceCreators取出来的**/// 通过默认的无参构造方法,请注意是无参的构造方法ObjectConstructor<T> defaultConstructor = newDefaultConstructor(rawType);if (defaultConstructor != null) {return defaultConstructor;}// 以上三步仍没有找到构造方法时的处理ObjectConstructor<T> defaultImplementation = newDefaultImplementationConstructor(type, rawType);if (defaultImplementation != null) {return defaultImplementation;}// 通过不安全的形式创建对象???return newUnsafeAllocator(type, rawType);}
其中前两中方法都是我们通过配置Gson可以设置的,因为我们并没有设置,所以这里先跳过,我们需要关注后三步,第一步:找到无参的构造方法:
private <T> ObjectConstructor<T> newDefaultConstructor(Class<? super T> rawType) {try {// 反射无参的构造方法final Constructor<? super T> constructor = rawType.getDeclaredConstructor();if (!constructor.isAccessible()) {accessor.makeAccessible(constructor);}return new ObjectConstructor<T>() {@Override public T construct() {try {Object[] args = null;// 调用无参构造方法创建实例return (T) constructor.newInstance(args);} catch (Exception e) {...}}};// 没有无参构造方法直接返回null} catch (NoSuchMethodException e) {return null;}}
熟悉反射的话,这里都很好理解,如果还不太熟悉反射的话,可以先去查看一下反射的知识。如果没有无参的构造方法,会进入newDefaultImplementationConstructor:
private <T> ObjectConstructor<T> newDefaultImplementationConstructor(final Type type, Class<? super T> rawType) {if (Collection.class.isAssignableFrom(rawType)) {... 集合子类会有一些创建操作}if (Map.class.isAssignableFrom(rawType)) {... Map子类的创建操作}return null;}
newDefaultImplementationConstructor只对集合和Map的子类有创建操作,很明显TestBean只是一个普通对象,并不符合需求。经过以上四步,我们没有找到任何可以创建TestBean的方法,那么唯一的答案就只能是在newUnsafeAllocator(type, rawType)中了:
public static UnsafeAllocator create() {// 最关键try {// 反射找到sun.misc.Unsafe类Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");// 找到sun.misc.Unsafe类中的theUnsafe属性Field f = unsafeClass.getDeclaredField("theUnsafe");// 激活theUnsafe属性f.setAccessible(true);// 得到theUnsafe的对象final Object unsafe = f.get(null);// 反射allocateInstance方法final Method allocateInstance = unsafeClass.getMethod("allocateInstance", Class.class);return new UnsafeAllocator() {@Override@SuppressWarnings("unchecked")public <T> T newInstance(Class<T> c) throws Exception {assertInstantiable(c);// 调用allocateInstance方法,创建类型为c的对象return (T) allocateInstance.invoke(unsafe, c);}};} catch (Exception ignored) {}// 第二步的实现方案和第一步其实一样,所以忽略...// 第三步,在我的版本源码中ObjectInputStream找不到newInstance方法,所以忽略... }
还是反射相关的调用,理解起来并不难,重点是理解sun.misc.Unsafe类到底是个什么东西,可以无视对象的构造方法,创建新的对象,我从网上分享的源码中,截取一部分注释的描述一下它:
sun.misc.Unsafesh收集了很多底层的不安全的操作方法。尽管类和方法是开放的,但是要谨慎的使用它,因为只有信任的代码才可以使用它。
allocateInstance方法:创建一个实例,但是不运行它的构造方法,请手动初始化。
sun.misc.Unsafe还有很多其他的native方法,Google已经明确说明要谨慎使用,除非是极其特殊的情况,我们还是把它记在心里就好了。
总结
现在我们已经了解了Gson创建对象的过程,那么一开始的问题要怎么解决呢?经过分析源码我们有以下两种方案:
第一种方案::Gson配置TypeAdapter。
val testBean2 = GsonBuilder().registerTypeAdapter(TestBean::class.java, TestBeanTypeAdapter()).create().fromJson<TestBean>(jsonStr, TestBean::class.java)class TestBeanTypeAdapter : JsonSerializer<TestBean?>,JsonDeserializer<TestBean?> {@Throws(JsonParseException::class)override fun deserialize(json: JsonElement?, typeOfT: Type?,context: JsonDeserializationContext?): TestBean? {return if (json == null) {null} else {if (json is JsonObject) {return TestBean(json.get("type").asInt, json.get("name").asString)} else {return null}}}override fun serialize(src: TestBean?, typeOfSrc: Type?,context: JsonSerializationContext?): JsonElement? {return JsonPrimitive(Gson().toJson(src))}
}
第二种方案:为TypeAdapter,设置无参的构造方法:
class TestBean {var type: Int = 0var name: String = ""var time: Long = 10init {Log.e("lzp", "TestBean create")}constructor(type: Int, name: String){this.type = typethis.name = name}override fun toString(): String {return "type:$type, name:$name, time:$time"}
}
Gson踩坑笔记:为什么对象的构造方法没有被执行?相关推荐
- 「Java」基于Mirai的qq机器人开发踩坑笔记(其二)
目录 0. 配置机器人 1. onLoad方法 2. onEnable方法 3. 消息属性 4. 消息监听 I. 好友消息 II. 群聊消息 III. 无差别消息 5. 发送消息 I. 文本消息 II ...
- iphone se 一代 不完美越狱 14.6 视频壁纸教程(踩坑笔记)
iphone se 一代 不完美越狱 14.6 加 视频壁纸教程-踩坑笔记 越狱流程 1.爱思助手制作启动u盘 坑点: 2.越狱好后 视频壁纸软件 1.源 2.软件安装 越狱流程 1.爱思助手制作启动 ...
- Linux内核踩坑笔记
systemtap embedded C踩坑笔记戳这: https://blog.csdn.net/qq_41961459/article/details/103093912 task_struct的 ...
- 阿里云部署Tiny Tiny RSS踩坑笔记
阿里云部署Tiny Tiny RSS踩坑笔记 前言 入坑了RSS,之前的配置是阿里云部署RSSHub,配合Inoreader进行文章阅读,详情见RSS入坑指南.阿里云部署RSSHub踩坑笔记.在202 ...
- 「Java」基于Mirai的qq机器人开发踩坑笔记(其一)
目录 0. 前置操作 I. 安装MCL II. MCL自动登录配置 III. 安装IDEA插件 1. 新建Mirai项目 2. 编写主类 3. 添加外部依赖 4. IDEA运行 5. 插件打包 6. ...
- 昆仑通态触摸屏1003故障码,踩坑笔记
昆仑通态触摸屏1003故障码,踩坑笔记 第一次使用这个昆仑通态触摸屏,使用modbusRTU与金田变频器做通讯. 触摸屏在线后报1003通讯错误代码,现象是控制指令正常,但是读取不正常.读取变频器状态 ...
- EDUSOHO踩坑笔记之四十二:资讯
EDUSOHO踩坑笔记之四十二:资讯 获取资讯列表信息 GET /articles/{id} 权限 老API,需要认证 参数 字段 是否必填 描述 sort string 否 排序,'created' ...
- EDUSOHO踩坑笔记之三十三:班级
EDUSOHO踩坑笔记之三十三:班级 班级 班级 获取班级信息 获取班级列表 班级成员 获取班级计划 加入班级 营销平台加入班级 班级 班级 获取班级信息 GET /classrooms/{class ...
- uniapp引入vantweapp踩坑笔记
vue-cli创建uniapp项目引入vantweapp踩坑笔记 uni-app中引入vantweapp vue-cli创建uniapp项目引入vantweapp踩坑笔记 一.环境准备 二.项目搭建 ...
最新文章
- 【C++】 保存内容到文件工具
- 妙用QTP F1帮助功能
- 0902 - Preferences Permission
- JSP简单练习-包装类综合应用实例
- java.lang.IllegalStateException: ContainerBase.addChild: start
- mysql获取当前时间,及其相关操作
- [js] innerHTML与outerHTML有什么区别?
- 园龄一年了,可还未动笔.
- Stanford机器学习---第十一讲.异常检测
- Pspice 使用指南(中文)
- oracle连接no listener
- html里面判断字段显示,HTML特殊字符显示
- 支付宝 支付返回 4000 ,系统繁忙请稍后再试
- 安卓神器-kiwi browser
- 【Jaya算法解决柔性作业车间调度问题(附代码)】
- 算法【哈希】 | 哈希表
- 按摩界的“爱马仕”,拯救你的发际线,失眠、职业病通通消失,爽爆了!
- Revit 视图范围的知识总结
- 迅雷起死回生背后的男人,竟然是雷军
- 吴恩达深度学习卷积神经网络学习笔记(2)——经典神经网络