在用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
而後調用相應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
取出來的。
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[]
數組中,而後上面的取數據的過程能夠抽象以下圖
在剛開始的時候設置其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
之後的索引值以下
而後在進行取value
時調用了以下方法Object fieldValue = typeAdapter.read(reader);
而後在其內部對pos
進行了包裝此時的pos爲
其取value
的方法和取key
的方法是同樣的。