1. 程式人生 > >不學無數——Gson原始碼解析

不學無數——Gson原始碼解析

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

參考文章