不學無數——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的方法是同樣的。

參考文章

相關文章
相關標籤/搜索