不学无数——Gson源码解析

Gson源码解析

Posted by 不学无数 on November 1, 2018

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的方法是一样的。

参考文章