不學無數——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
然後呼叫相應 Adapter
的 read()
方法。
@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); } }
發現其最終是呼叫了 Field
的 set()
方法對相應的屬性值進行了賦值。
解析到了這相信應該還有一個疑問,Gson是如何將字串中的對應的 key
和 value
取出來的。
Gson如何取出對應的 Key
和 Value
解析
取出 Key
和 Value
都是在 Adapter
的 read()
方法中做的。其中取 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
的方法是一樣的。
參考文章
- ofollow,noindex">https://www.jianshu.com/p/789a1634b3c2
- https://www.jianshu.com/p/e38e7cb5076c
- https://juejin.im/post/5b8a2f5af265da43445f75ee