不学无数——Gson源码解析

Gson

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

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的。例子以下app

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

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

而后调用反射赋值ide

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源码解析之旅oop

源码解析

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

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);
    }
  }

其中重要的就这两个方法ui

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

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

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();
      }
    }
  }

发现重要的部分是google

建立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的方法是同样的。

参考文章