一個容錯的Gson新世界

1004145468發表於2018-09-01

1. 闖入背景:

公司專案中使用Gson框架對伺服器傳過來的Json資料進行解析,而伺服器後臺資料很大程度上是通過運營後臺人員配置。由於各種原因運營可能將某一欄位型別配置錯誤,比如集合型別配置成字串型別。雖然業務層會進行異常的捕獲,但是僅因為一個欄位的錯誤,導致整個Json資料失效,因小失大,甚至可能會造成重大損失,比如直播間禮物牆,因為一個禮物的某一個欄位的錯誤,導致整個禮物牆展示為空,線上上環境這個算是重大事故了。於是,一個對基本型別容錯的Gson改造庫的需求油然而生,對於錯誤的資料以預設值填充。

乾貨地址:型別容錯的Gson

2. Gson官方庫地址:

Github地址

3. 前提說明

a. 當前分析的Gson版本號為2.8.1。
b. Gson的處理過程主要分為兩個流向,一個是序列化,將javabean物件轉化為json字串;另一個是反序列化,將json字串對映成javabean物件。
c. 這兩個流向處理前都有一個共同的操作,從傳入的java例項物件或者位元組碼物件中獲取 TypeAdapter,對於序列化就通過Jsonwriter進行寫,對於反序列化就通過JsonReader進行讀,所以此篇只分析Gson讀的過程,寫處理操作流程一樣。

4. Gson 關鍵列的梳理

  • Gson 開發者直接使用的類,只對輸入和輸出負責。
  • TypeToken 封裝“操作類”(Gson.fromJson(json,People.class、Gson.toJson(new People)) 兩處的People都是操作類)的型別。
  • TypeAdapter 直接操作序列化與反序列化的過程,所以該抽象類中存在read()和write方法。
  • TypeAdapterFactory 用於生產TypeAdapter的工廠類。
  • GsonReader和GsonWriter是Gson處理內容的包裝流,核心的操作有:
    • peek() 流中下一個需要處理的內容
    • nextName() 讀取json的key
    • nextString() 讀取一個String型別的value
    • nextInt() 讀取一個String型別的value
    • nextBoolean() 讀取一個Boolean型別的value
    • ...

5. 原始碼分析。

從Gson.from(json, People.class) 突入

  fromJson(json,Peolple.class)的呼叫鏈
  
  public <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException {
    Object object = fromJson(json, (Type) classOfT);
    return Primitives.wrap(classOfT).cast(object);
  }
  
  public <T> T fromJson(String json, Type typeOfT) throws JsonSyntaxException {
    if (json == null) {
      return null;
    }
    StringReader reader = new StringReader(json);
    T target = (T) fromJson(reader, typeOfT);
    return target;
  }
  
  public <T> T fromJson(Reader json, Type typeOfT) throws JsonIOException, JsonSyntaxException {
    JsonReader jsonReader = newJsonReader(json);
    T object = (T) fromJson(jsonReader, typeOfT);
    assertFullConsumption(object, jsonReader);
    return object;
  }
  
  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;
    } ...
  }
複製程式碼

上面是從fromJson(String json, Class classOfT)切入,亦或者是從fromJson(JsonElement json, Class classOfT)也好,最終都是由 fromJson(JsonReader reader, Type typeOfT)處理。

整個Json的解析過程分三步過程:

  • TypeToken物件的獲取
  • 根據TypeToken獲取TypeAdapter物件
  • 由TypeAdapter物件解析json字串

根據以上的三步,我們逐一突破


我們先從簡單的入手,請記住我們的例子:

gson.fromJson("hello gson",String.class)

1. TypeToken的獲取

 public static TypeToken<?> get(Type type) {
    return new TypeToken<Object>(type);
  }
複製程式碼

沒什麼好瞅的~ 看new吧!

 TypeToken(Type type) {
    this.type = $Gson$Types.canonicalize($Gson$Preconditions.checkNotNull(type));
    this.rawType = (Class<? super T>) $Gson$Types.getRawType(this.type);
    this.hashCode = this.type.hashCode();
  }
複製程式碼

採用契約式對傳入的type判空處理,然後獲取type的(type、rawType和hashcode),分別看看type和rawtype的獲取流程

1. type的獲取(type的華麗包裝)
public static Type canonicalize(Type type) {
    if (type instanceof Class) {
      Class<?> c = (Class<?>) type;
      return c.isArray() ? new GenericArrayTypeImpl(canonicalize(c.getComponentType())) : c;

    } else if (type instanceof ParameterizedType) {
      ParameterizedType p = (ParameterizedType) type;
      return new ParameterizedTypeImpl(p.getOwnerType(),
          p.getRawType(), p.getActualTypeArguments());

    } else if (type instanceof GenericArrayType) {
      GenericArrayType g = (GenericArrayType) type;
      return new GenericArrayTypeImpl(g.getGenericComponentType());

    } else if (type instanceof WildcardType) {
      WildcardType w = (WildcardType) type;
      return new WildcardTypeImpl(w.getUpperBounds(), w.getLowerBounds());

    } else {
      // type is either serializable as-is or unsupported
      return type;
    }
複製程式碼

進入條件的篩選,第一個if還是好理解,後面的是什麼鬼? 不用著急,待我給施主梳理,之前Gson.from(json, People.class)的呼叫鏈中有一個fromJson(Reader json, Type typeOfT) ,使用者使用時的切入點如果是它就可能是篩選情況的其他條件,此返回的type相對於對傳入的java型別進行的型別的重新包裝。

2. rawType的獲取(type的簡單粗暴說明)
public static Class<?> getRawType(Type type) {
    if (type instanceof Class<?>) {
      // type is a normal class.
      return (Class<?>) type;

    } else if (type instanceof ParameterizedType) {
      ParameterizedType parameterizedType = (ParameterizedType) type;

      // I'm not exactly sure why getRawType() returns Type instead of Class.
      // Neal isn't either but suspects some pathological case related
      // to nested classes exists.
      Type rawType = parameterizedType.getRawType();
      checkArgument(rawType instanceof Class);
      return (Class<?>) rawType;

    } else if (type instanceof GenericArrayType) {
      Type componentType = ((GenericArrayType)type).getGenericComponentType();
      return Array.newInstance(getRawType(componentType), 0).getClass();

    } else if (type instanceof TypeVariable) {
      // we could use the variable's bounds, but that won't work if there are multiple.
      // having a raw type that's more general than necessary is okay
      return Object.class;

    } else if (type instanceof WildcardType) {
      return getRawType(((WildcardType) type).getUpperBounds()[0]);

    } else {
      String className = type == null ? "null" : type.getClass().getName();
      throw new IllegalArgumentException("Expected a Class, ParameterizedType, or "
          + "GenericArrayType, but <" + type + "> is of type " + className);
    }
  }
複製程式碼

兩處對比的看,其實type和rawtype很相似,type通過類來包裝說明,而rawtype脫去華麗的衣服。type為GenericArrayType的,把衣服一脫,赤身裸體的一看,擦,原來是個array陣列,這就是rawtype。

2. TypeAdapter的獲取。

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 cannot handle " + type);
    }
複製程式碼

如果快取中沒有該Type對應TypeAdapter,就建立TypeAdapter。前面提過TypeAdapter是由TypeAdapterFactory建立的,所以有程式碼:

 for (TypeAdapterFactory factory : factories) {
        TypeAdapter<T> candidate = factory.create(this, type);
        if (candidate != null) {
          call.setDelegate(candidate);
          typeTokenCache.put(type, candidate);
          return candidate;
        }
      }
複製程式碼

遍歷所有的TypeAdapterFactory,如果該工廠能建立該Type的TypeAdapter就返回該TypeAdapter物件。

那麼重點來了,factories這麼多的TypeAdapterFactory是怎麼來了的?

在我們new Gson的時候,就往factories中塞入了不同型別的TypeAdapterFactory,包括StringTypeAdapterFactory等等,程式碼如下:

    public Gson(xxx)
    {
        ...
        factories.add(TypeAdapters.STRING_FACTORY);
        factories.add(TypeAdapters.STRING_FACTORY);
        factories.add(TypeAdapters.INTEGER_FACTORY);
        factories.add(TypeAdapters.BOOLEAN_FACTORY);
        factories.add(TypeAdapters.BYTE_FACTORY);
        factories.add(TypeAdapters.SHORT_FACTORY);
        ...
    }
   
複製程式碼

在遍歷factories過程中通過create(this,type)方法來生成TypeAdapter。

我們就以第一個STRING_FACTORY為例先進行說明。
public static final TypeAdapterFactory STRING_FACTORY = newFactory(String.class, STRING);
複製程式碼

接著往下看

  public static <TT> TypeAdapterFactory newFactory(
      final Class<TT> type, final TypeAdapter<TT> typeAdapter) {
    return new TypeAdapterFactory() {
      @SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal
      @Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
        return typeToken.getRawType() == type ? (TypeAdapter<T>) typeAdapter : null;
      }
      @Override public String toString() {
        return "Factory[type=" + type.getName() + ",adapter=" + typeAdapter + "]";
      }
    };
  }
複製程式碼

STRING_FACTORY = newFactory(String.class, STRING)的時候,STRING就是處理String型別的TypeAdapter,STRING_FACTORY中的create方法就是判斷需要處理的型別是不是String型別的,如果是就返回STRING,否則返回null,即該型別不用STRING來處理。

總的來說,在建立Gson的例項物件時,建立TypeAdapterFactory的集合。每種TypeAdapterFactory例項包含能處理的Type型別和Type型別的TypeAdapter,不能處理的Type型別返回的TypeAdapter為null,所以在遍歷factories過程中有:

for (TypeAdapterFactory factory : factories) {
        TypeAdapter<T> candidate = factory.create(this, type);
        if (candidate != null) {
            ...
          return candidate;
        }
      }
複製程式碼

3. 由TypeAdapter物件解析json字串

我們回到最初的程式碼:

    TypeToken<T> typeToken = (TypeToken<T>)TypeToken.get(typeOfT);
TypeAdapter<T> typeAdapter = getAdapter(typeToken);
T object = typeAdapter.read(reader);
複製程式碼

STRING就是處理String型別的TypeAdapter,然後我們看它的read()方法。

public static final TypeAdapter<String> STRING = new TypeAdapter<String>() {
    @Override
    public String read(JsonReader in) throws IOException {
      JsonToken peek = in.peek();
      if (peek == JsonToken.NULL) {
        in.nextNull();
        return null;
      }
      /* coerce booleans to strings for backwards compatibility */
      if (peek == JsonToken.BOOLEAN) {
        return Boolean.toString(in.nextBoolean());
      }
      return in.nextString();
    }
   ...
  };
複製程式碼

到這裡位置,我們就將gson.fromJson("hello gson",String.class)的String型別“hello gson”返回。


剛剛是隻是牛刀小試,我們的主材料來了,看看有多豐盛...

Gson.from("{ "name": "zhangsan", "age": 15, "grade": [ 95, 98 ] }", Student.class)

我們重新走剛剛的流程,看看怎麼處理的

Step one : 獲取TypeToken

這一步沒有什麼與眾不同

Step Two: TypeAdapter的獲取。

factories中包含了很多基本型別的TypeAdapterFactory,同時也包含使用者自定義的型別Factory,看原始碼:

        // type adapters for composite and user-defined types
        
    factories.add(new CollectionTypeAdapterFactory(constructorConstructor));
    factories.add(new MapTypeAdapterFactory(constructorConstructor, complexMapKeySerialization));
    this.jsonAdapterFactory = new JsonAdapterAnnotationTypeAdapterFactory(constructorConstructor);
    factories.add(jsonAdapterFactory);
    factories.add(TypeAdapters.ENUM_FACTORY);
    factories.add(new ReflectiveTypeAdapterFactory(constructorConstructor, fieldNamingStrategy, excluder, jsonAdapterFactory));
複製程式碼

此處我們能匹配上的是ReflectiveTypeAdapterFactory,然後我們看它的create()方法,關鍵的地方到了!!!

  @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));
  }
複製程式碼

a. constructorConstructor 獲取Student類的構造器
b. getBoundFields()通過反射獲取Student每一個欄位的的TypeAdapter,並且包裝到Map<String, BoundField>中,後面會講解getBoundFields()的方法。

Step Three 通過TypeAdapter的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()) {
          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;
    }
複製程式碼

到了這一步就似乎海闊天空了,通過傳入的構造器建立Student類的例項,在JsonReader進行處理,in.beginObject()相當於跳過“{”,in.endObject()相當於跳過“}”,其中通過in.hasNext()判斷是否處理完成。 在in.nextName()讀取json字串中的key值,然後在boundFields根據key獲取對應的BoundField ,最後呼叫BoundField.read(in,instance)去處理細節,即每個欄位的對映,我們看一下內部的細節:

new ReflectiveTypeAdapterFactory.BoundField(name, serialize, deserialize) {
      ...
      @Override void read(JsonReader reader, Object value)
          throws IOException, IllegalAccessException {
        Object fieldValue = typeAdapter.read(reader);
        if (fieldValue != null || !isPrimitive) {
          field.set(value, fieldValue);
        }
      }
     
     ...
    };
複製程式碼

當Filed都處理完成後,instance例項的每一個需要處理的欄位都賦值成功,最終將這個物件return出去。


細節說明:

a. 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) {
        boolean serialize = excludeField(field, true);
        boolean deserialize = excludeField(field, false);
        if (!serialize && !deserialize) {
          continue;
        }
        field.setAccessible(true);
        Type fieldType = $Gson$Types.resolve(type.getType(), raw, field.getGenericType());
        List<String> fieldNames = getFieldNames(field);
        BoundField previous = null;
        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);
        }
      }
      type = TypeToken.get($Gson$Types.resolve(type.getType(), raw, raw.getGenericSuperclass()));
      raw = type.getRawType();
    }
    return result;
  }
複製程式碼

遍歷Student類的每一個欄位,遍歷過程中做了兩件事情:

  • a. 該欄位能否被序列化和反序列化,如果都不行就沒有必要處理該欄位,主要通過註解和排除器(Excluder)進行判斷。
  • b. 對欄位進行BoundField的包裝。

b. JsonReader.doPeek()

int doPeek() throws IOException {
    int peekStack = stack[stackSize - 1];
    if (peekStack == JsonScope.EMPTY_ARRAY) {
      stack[stackSize - 1] = JsonScope.NONEMPTY_ARRAY;
    } else if (peekStack == JsonScope.NONEMPTY_ARRAY) {
      // Look for a comma before the next element.
      int c = nextNonWhitespace(true);
      switch (c) {
      case ']':
        return peeked = PEEKED_END_ARRAY;
      case ';':
        checkLenient(); // fall-through
      case ',':
        break;
      default:
        throw syntaxError("Unterminated array");
      }
    } else if (peekStack == JsonScope.EMPTY_OBJECT || peekStack == JsonScope.NONEMPTY_OBJECT) {
      stack[stackSize - 1] = JsonScope.DANGLING_NAME;
      // Look for a comma before the next element.
      if (peekStack == JsonScope.NONEMPTY_OBJECT) {
        int c = nextNonWhitespace(true);
        switch (c) {
        case '}':
          return peeked = PEEKED_END_OBJECT;
        case ';':
          checkLenient(); // fall-through
        case ',':
          break;
        default:
          throw syntaxError("Unterminated object");
        }
      }
      int c = nextNonWhitespace(true);
      switch (c) {
      case '"':
        return peeked = PEEKED_DOUBLE_QUOTED_NAME;
      case '\'':
        checkLenient();
        return peeked = PEEKED_SINGLE_QUOTED_NAME;
      case '}':
        if (peekStack != JsonScope.NONEMPTY_OBJECT) {
          return peeked = PEEKED_END_OBJECT;
        } else {
          throw syntaxError("Expected name");
        }
      default:
        checkLenient();
        pos--; // Don't consume the first character in an unquoted string.
        if (isLiteral((char) c)) {
          return peeked = PEEKED_UNQUOTED_NAME;
        } else {
          throw syntaxError("Expected name");
        }
      }
    } else if (peekStack == JsonScope.DANGLING_NAME) {
      stack[stackSize - 1] = JsonScope.NONEMPTY_OBJECT;
      // Look for a colon before the value.
      int c = nextNonWhitespace(true);
      switch (c) {
      case ':':
        break;
      case '=':
        checkLenient();
        if ((pos < limit || fillBuffer(1)) && buffer[pos] == '>') {
          pos++;
        }
        break;
      default:
        throw syntaxError("Expected ':'");
      }
    } else if (peekStack == JsonScope.EMPTY_DOCUMENT) {
      if (lenient) {
        consumeNonExecutePrefix();
      }
      stack[stackSize - 1] = JsonScope.NONEMPTY_DOCUMENT;
    } else if (peekStack == JsonScope.NONEMPTY_DOCUMENT) {
      int c = nextNonWhitespace(false);
      if (c == -1) {
        return peeked = PEEKED_EOF;
      } else {
        checkLenient();
        pos--;
      }
    } else if (peekStack == JsonScope.CLOSED) {
      throw new IllegalStateException("JsonReader is closed");
    }

    int c = nextNonWhitespace(true);
    switch (c) {
    case ']':
      if (peekStack == JsonScope.EMPTY_ARRAY) {
        return peeked = PEEKED_END_ARRAY;
      }
      // fall-through to handle ",]"
    case ';':
    case ',':
      // In lenient mode, a 0-length literal in an array means 'null'.
      if (peekStack == JsonScope.EMPTY_ARRAY || peekStack == JsonScope.NONEMPTY_ARRAY) {
        checkLenient();
        pos--;
        return peeked = PEEKED_NULL;
      } else {
        throw syntaxError("Unexpected value");
      }
    case '\'':
      checkLenient();
      return peeked = PEEKED_SINGLE_QUOTED;
    case '"':
      return peeked = PEEKED_DOUBLE_QUOTED;
    case '[':
      return peeked = PEEKED_BEGIN_ARRAY;
    case '{':
      return peeked = PEEKED_BEGIN_OBJECT;
    default:
      pos--; // Don't consume the first character in a literal value.
    }

    int result = peekKeyword();
    if (result != PEEKED_NONE) {
      return result;
    }

    result = peekNumber();
    if (result != PEEKED_NONE) {
      return result;
    }

    if (!isLiteral(buffer[pos])) {
      throw syntaxError("Expected value");
    }

    checkLenient();
    return peeked = PEEKED_UNQUOTED;
  }
複製程式碼

該操作邏輯處理較強,主要工作分為3點:

  • json的格式校驗,格式不合法丟擲異常
  • 根據當前的操作,決定下一步的操作方式
  • 流中下一部分的內容型別

相關文章