美文网首页Java技术分享程序员
不学无数——Gson源码解析

不学无数——Gson源码解析

作者: 不学无数的程序员 | 来源:发表于2018-10-17 17:54 被阅读18次

    Gson

    在用Gson解析时传过来的Json串时,如果将其解析为对象A,而这个对象A继承了对象B。这两个对象都有属性名为name的值,那么在进行解析的时候就会报如下错误。

    Exception in thread "main" java.lang.IllegalArgumentException: class Practice.Day12.Student2 declares multiple JSON fields named name
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:172)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:102)
        at com.google.gson.Gson.getAdapter(Gson.java:458)
        at com.google.gson.Gson.fromJson(Gson.java:926)
        at com.google.gson.Gson.fromJson(Gson.java:892)
        at com.google.gson.Gson.fromJson(Gson.java:841)
        at com.google.gson.Gson.fromJson(Gson.java:813)
        at Practice.Day12.TestSetField.main(TestSetField.java:39)
    
    

    报错的代码如下

    String str = "{\"name\":\"BuXueWuShu\"}";
    Gson gson = new Gson();
    gson.fromJson(str,Student2.class);
    
    

    经过查询在将其解析为对象的时候用的Java技术是反射,以为是反射的原因不能赋值,然后写了如下的小例子发现是通过反射能够将值赋给name的。例子如下

    public class Student{
        private String name ;
        -----get.set方法
    }
    
    public class Student2 extends Student{
        private String name ;
        -----get.set方法
    }
    
    

    然后调用反射赋值

    Field name = cl.getDeclaredField("name");
    Student2 student = (Student2) cl.newInstance();
    name.setAccessible(true);
    name.set(student,"bb");
    System.out.println(student.getName());
    

    发现确实能够取到name的值。

    不是反射的原因,那么就是Gson在进行解析的时候做了处理,所以也就开启这一次的Gson源码解析之旅

    源码解析

    直接Debug从gson.fromJson(str,Student2.class);中进去,发现前面有好多的重载函数,但是最终都是调用了

    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<T> typeAdapter = getAdapter(typeToken);
          T object = typeAdapter.read(reader);
          return object;
        } catch (EOFException e) {
          /*
           * For compatibility with JSON 1.5 and earlier, we return null for empty
           * documents instead of throwing.
           */
          if (isEmpty) {
            return null;
          }
          throw new JsonSyntaxException(e);
        } catch (IllegalStateException e) {
          throw new JsonSyntaxException(e);
        } catch (IOException e) {
          // TODO(inder): Figure out whether it is indeed right to rethrow this as JsonSyntaxException
          throw new JsonSyntaxException(e);
        } catch (AssertionError e) {
          throw new AssertionError("AssertionError (GSON " + GsonBuildConfig.VERSION + "): " + e.getMessage(), e);
        } finally {
          reader.setLenient(oldLenient);
        }
      }
    

    其中重要的就这两个方法

    TypeAdapter<T> typeAdapter = getAdapter(typeToken);
    T object = typeAdapter.read(reader);
    
    

    上面的getAdapter是获取合适的Adapter。点进去以后发现

     public <T> TypeAdapter<T> getAdapter(TypeToken<T> type) {
        TypeAdapter<?> cached = typeTokenCache.get(type == null ? NULL_KEY_SURROGATE : type);
        if (cached != null) {
          return (TypeAdapter<T>) cached;
        }
    
        Map<TypeToken<?>, FutureTypeAdapter<?>> threadCalls = calls.get();
        boolean requiresThreadLocalCleanup = false;
        if (threadCalls == null) {
          threadCalls = new HashMap<TypeToken<?>, FutureTypeAdapter<?>>();
          calls.set(threadCalls);
          requiresThreadLocalCleanup = true;
        }
    
        // the key and value type parameters always agree
        FutureTypeAdapter<T> ongoingCall = (FutureTypeAdapter<T>) threadCalls.get(type);
        if (ongoingCall != null) {
          return ongoingCall;
        }
    
        try {
          FutureTypeAdapter<T> call = new FutureTypeAdapter<T>();
          threadCalls.put(type, call);
    
          for (TypeAdapterFactory factory : factories) {
            TypeAdapter<T> candidate = factory.create(this, type);
            if (candidate != null) {
              call.setDelegate(candidate);
              typeTokenCache.put(type, candidate);
              return candidate;
            }
          }
          throw new IllegalArgumentException("GSON (" + GsonBuildConfig.VERSION + ") cannot handle " + type);
        } finally {
          threadCalls.remove(type);
    
          if (requiresThreadLocalCleanup) {
            calls.remove();
          }
        }
      }
    
    

    发现重要的部分是

    创建TypeAdapterFactory的集合。每种TypeAdapterFactory实例包含能处理的Type类型和Type类型的TypeAdapter,不能处理的Type类型返回的TypeAdapter为null

      for (TypeAdapterFactory factory : factories) {
        TypeAdapter<T> candidate = factory.create(this, type);
        if (candidate != null) {
          call.setDelegate(candidate);
          typeTokenCache.put(type, candidate);
          return candidate;
        }
      }
    
    

    其中TypeAdapterFactory是在Gson的构造函数块中直接加载好的,代码如下

    Gson() {
          List<TypeAdapterFactory> factories = new ArrayList<TypeAdapterFactory>();
    
        // built-in type adapters that cannot be overridden
        factories.add(TypeAdapters.JSON_ELEMENT_FACTORY);
        factories.add(ObjectTypeAdapter.FACTORY);
    
        // the excluder must precede all adapters that handle user-defined types
        factories.add(excluder);
    
        // users' type adapters
        factories.addAll(factoriesToBeAdded);
    
        // type adapters for basic platform types
        factories.add(TypeAdapters.STRING_FACTORY);
        factories.add(TypeAdapters.INTEGER_FACTORY);
        factories.add(TypeAdapters.BOOLEAN_FACTORY);
        factories.add(TypeAdapters.BYTE_FACTORY);
        --------中间太多省略
        factories.add(TimeTypeAdapter.FACTORY);
        factories.add(SqlDateTypeAdapter.FACTORY);
        factories.add(TypeAdapters.TIMESTAMP_FACTORY);
        factories.add(ArrayTypeAdapter.FACTORY);
        factories.add(TypeAdapters.CLASS_FACTORY);
    
        this.factories = Collections.unmodifiableList(factories);
      }
    
    

    只要是转化为对象的,那么其Adapter就是ReflectiveTypeAdapterFactory,然后看其creat()方法

    @Override public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) {
        Class<? super T> raw = type.getRawType();
    
        if (!Object.class.isAssignableFrom(raw)) {
          return null; // it's a primitive!
        }
    
        ObjectConstructor<T> constructor = constructorConstructor.get(type);
        return new Adapter<T>(constructor, getBoundFields(gson, type, raw));
      }
    
    

    重要部分在return中的getBoundFields()方法。点进去代码如下

    private Map<String, BoundField> getBoundFields(Gson context, TypeToken<?> type, Class<?> raw) {
        Map<String, BoundField> result = new LinkedHashMap<String, BoundField>();
        if (raw.isInterface()) {
          return result;
        }
    
        Type declaredType = type.getType();
        while (raw != Object.class) {
          --通过反射获得所有的属性值
          Field[] fields = raw.getDeclaredFields();
          --遍历所有的属性值
          for (Field field : fields) {
            --这两个字段能否被序列化和反序列化,如果都不行就没有必要处理该字段,主要通过注解和排除器(Excluder)进行判断。
            boolean serialize = excludeField(field, true);
            boolean deserialize = excludeField(field, false);
            if (!serialize && !deserialize) {
              continue;
            }
            accessor.makeAccessible(field);
            Type fieldType = $Gson$Types.resolve(type.getType(), raw, field.getGenericType());
            --遍历出所有的属性名称
            List<String> fieldNames = getFieldNames(field);
            BoundField previous = null;
            --开始装饰返回结果result
            for (int i = 0, size = fieldNames.size(); i < size; ++i) {
              String name = fieldNames.get(i);
              if (i != 0) serialize = false; // only serialize the default name
              BoundField boundField = createBoundField(context, field, name,
                  TypeToken.get(fieldType), serialize, deserialize);
              BoundField replaced = result.put(name, boundField);
              if (previous == null) previous = replaced;
            }
            if (previous != null) {
              throw new IllegalArgumentException(declaredType
                  + " declares multiple JSON fields named " + previous.name);
            }
          }
          --这两句是获得其父类的Class对象
          type = TypeToken.get($Gson$Types.resolve(type.getType(), raw, raw.getGenericSuperclass()));
          raw = type.getRawType();
        }
        return result;
      }
    
    

    然后在这里面发现了所抛异常的地方。首先说一下这个方法是干什么用的,然后再分析为什么会抛异常。

    getBoundFields()方法返回的主要就是当前类的属性和属性类型,这些数据就是用于后续Json数据解析的。

    其中raw != Object.class在while循环中只要不是Object类那么就一直解析,即将子类的父类全部解析出来除了Object类。避免遗漏父类的属性值。此时发现报错的关键点在于下面这一句。

    BoundField replaced = result.put(name, boundField);
    
    

    result是一个Map<String, BoundField>Map对象。而调用put()方法会有一个返回值。如果在Map中根据name找不到值,那么就返回null,如果在Map中根据name能找到值,那么就返回此值。举个例子

     Map<String ,String > map = new HashMap<>();
    String str1 = map.put("1","1");
    String str2 = map.put("1","2");
    System.out.println(str1);
    System.out.println(str2);
    
    

    输出为

    null
    1
    

    所以报异常的原因就找到了。在第二次父类在往result中放值的时候已经有了name的值,所以就会返回子类的BoundField并且将其赋给previous此时在后面判断previous不为null所以就抛异常了。所以就知道如果将解析换为如下就会成功。将其类型传入为父类。

     gson.fromJson(str,Student.class);
    

    然后我们接着往下查看Gson是如何进行解析的。在上面我们提到了有两句是重要的

    TypeAdapter<T> typeAdapter = getAdapter(typeToken);
    T object = typeAdapter.read(reader);
    

    第一句已经解析完了,获得相应的Adapter然后调用相应Adapterread()方法。

    @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()) {
              -- 获得Json中相应key值
              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;
    }
    
    

    然后在field.read(in, instance);中代码如下

    @Override 
    void read(JsonReader reader, Object value)
              throws IOException, IllegalAccessException {
            Object fieldValue = typeAdapter.read(reader);
            if (fieldValue != null || !isPrimitive) {
              field.set(value, fieldValue);
            }
          }
    

    发现其最终是调用了Fieldset()方法对相应的属性值进行了赋值。

    解析到了这相信应该还有一个疑问,Gson是如何将字符串中的对应的keyvalue取出来的。

    Gson如何取出对应的KeyValue解析

    取出KeyValue都是在Adapterread()方法中做的。其中取key是如下的方法做的

     String name = in.nextName();
    
    

    然后最后调用了如下方法。

     private String nextQuotedValue(char quote) throws IOException {
        // Like nextNonWhitespace, this uses locals 'p' and 'l' to save inner-loop field access.
        char[] buffer = this.buffer;
        StringBuilder builder = null;
        while (true) {
          int p = pos;
          int l = limit;
          /* the index of the first character not yet appended to the builder. */
          int start = p;
          while (p < l) {
            int c = buffer[p++];
    
            if (c == quote) {
              pos = p;
              int len = p - start - 1;
              if (builder == null) {
                return new String(buffer, start, len);
              } else {
                builder.append(buffer, start, len);
                return builder.toString();
              }
            } else if (c == '\\') {
              pos = p;
              int len = p - start - 1;
              if (builder == null) {
                int estimatedLength = (len + 1) * 2;
                builder = new StringBuilder(Math.max(estimatedLength, 16));
              }
              builder.append(buffer, start, len);
              builder.append(readEscapeCharacter());
              p = pos;
              l = limit;
              start = p;
            } else if (c == '\n') {
              lineNumber++;
              lineStart = p;
            }
          }
    
          if (builder == null) {
            int estimatedLength = (p - start) * 2;
            builder = new StringBuilder(Math.max(estimatedLength, 16));
          }
          builder.append(buffer, start, p - start);
          pos = p;
          if (!fillBuffer(1)) {
            throw syntaxError("Unterminated string");
          }
        }
      }
    
    

    我们发现Gson将Json串放入了char[]数组中,然后上面的取数据的过程可以抽象如下图

    1

    在刚开始的时候设置其start索引为2,limit为字符串的长度

    然后通过int c = buffer[p++];不断获得值。获取值以后进行判断if (c == quote)其中的quote"的值,如果索引走到了"引号处,那么就进行

    pos = p;
    int len = p - start - 1;
    if (builder == null) {
    return new String(buffer, start, len);
    }
    

    就在此进行字符串的截取操作,将其返回,然后记录其索引值,取完key以后的索引值如下

    2

    然后在进行取value时调用了如下方法Object fieldValue = typeAdapter.read(reader);然后在其内部对pos进行了包装此时的pos为

    3

    其取value的方法和取key的方法是一样的。

    参考文章

    相关文章

      网友评论

        本文标题:不学无数——Gson源码解析

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