不學無數——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[]
陣列中,然後上面的取資料的過程可以抽象如下圖
在剛開始的時候設定其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
的方法是一樣的。