美文网首页Android开发技术分享
Gson踩坑笔记:为什么对象的构造方法没有被执行?

Gson踩坑笔记:为什么对象的构造方法没有被执行?

作者: 珠穆朗玛小王子 | 来源:发表于2020-10-26 18:34 被阅读0次

    前言

    最近做项目遇到了一个很奇怪的问题,情况如下:

    创建对象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"
        }
    }
    

    相关文章

      网友评论

        本文标题:Gson踩坑笔记:为什么对象的构造方法没有被执行?

        本文链接:https://www.haomeiwen.com/subject/yrhzmktx.html