阅读 138

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 = 10

    override 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 = 10

    init {
        time = 10
        Log.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);
      // 获取具体解析的TypeAdapter
      TypeAdapter<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 agree
    final 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 = 0

    var name: String = ""

    var time: Long = 10

    init {
        Log.e("lzp", "TestBean create")
    }

    constructor(type: Int, name: String){
        this.type = type
        this.name = name
    }

    override fun toString(): String {
        return "type:$type, name:$name, time:$time"
    }
}

作者:珠穆朗玛小王子

原文链接:https://www.jianshu.com/p/fbe8ff200b71

文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐