Gson源碼解析

我個人覺得 Gson是一個非常優秀的json解析開源項目,效率高, 代碼結構也非常的清晰 。給使用者提供了非常好的用戶體驗。封裝也非常的優雅 。因此, 我對Gson的研究充滿了興趣,這裏順帶提一下我前兩天發現的一個非常不錯的Rest封裝框架: Retrofit, 我不知道知道的人有多少, 我之前一直使用的是自己寫的Rest封裝,代碼的 複用 性也不強, 而Retrofit卻把這樣東西做了極致。我 將在後面的文章中研究它。 你可能很少不了 @子墨 這種想到哪寫到哪的做法。不過如果 你看 的順眼就不妨跟 着 這不成文的節奏走吧,因爲我不打算改變我的寫作方式。

        回到 正題, Gson主要分成兩部分 ,一個就是 數據拆解,一個是數據封裝。這兩個概念我都是基於JSON 層的概念而言。這樣JSON對象的拆解 的結果就是JAVA對象,而所謂的封裝就是將一個OO的對象封裝成爲一個json對象。當然json對象可以理解爲就是一個字符串。那麼實際上Gson做的事情就是完成St ring到JAVA 對象的映射。我們建立起這個概念以後我們就能明白我們的 研究主要分成兩個大部分。前幾個系列我們會先說String到JAVA對象之間的映射。

      我們 導入Gson的源碼 發現代碼比 預想的少的多, 分包也很明確。你是不是瞬間對Google公司崇敬起來。我們明白, 如果要對 系統之上構建一個封裝的 話,用的就是門面模式 , 關於門面模式的說法,大家可以參考 我的一篇文章:

子墨對酒《三國殺》裏論模式(二)門面模式

構造門面的 包名一般都是所有系統的頂層包。我們看到 com.google.gson 就很好的封裝了這些門面。也就是說我們要跟系統打交道, 實際上就是要跟這些門面打 交道。

     String 到 對象的主要門面是Gson .java。 裏面提供了各種各樣的方法方便你調用。

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);
      System.out.println("object = "+object);
      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);
    } finally {
      reader.setLenient(oldLenient);
    }
  }
我們能比較清晰的看到, 實際上對於數據的轉換, 是由:TypeAdapter<T> typeAdapter這個對象來完成的,我們可以從名字看出它本質上一個適配器。關於適配器模式大家可以參考 我的 一篇文章:

子墨對酒《三國殺》裏論模式(三)適配器模式

爲什麼需要適配 器呢 ?我們粘出一個適配器的代碼相信大家就能瞭解了:
public final class DateTypeAdapter extends TypeAdapter<Date> {
  public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
    @SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal
    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
      return typeToken.getRawType() == Date.class ? (TypeAdapter<T>) new DateTypeAdapter() : null;
    }
  };

  private final DateFormat enUsFormat
      = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.US);
  private final DateFormat localFormat
      = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT);
  private final DateFormat iso8601Format = buildIso8601Format();

  private static DateFormat buildIso8601Format() {
    DateFormat iso8601Format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
    iso8601Format.setTimeZone(TimeZone.getTimeZone("UTC"));
    return iso8601Format;
  }

  @Override public Date read(JsonReader in) throws IOException {
    if (in.peek() == JsonToken.NULL) {
      in.nextNull();
      return null;
    }
    return deserializeToDate(in.nextString());
  }

  private synchronized Date deserializeToDate(String json) {
    try {
      return localFormat.parse(json);
    } catch (ParseException ignored) {
    }
    try {
      return enUsFormat.parse(json);
    } catch (ParseException ignored) {
    }
    try {
      return iso8601Format.parse(json);
    } catch (ParseException e) {
      throw new JsonSyntaxException(json, e);
    }
  }

  @Override public synchronized void write(JsonWriter out, Date value) throws IOException {
    if (value == null) {
      out.nullValue();
      return;
    }
    String dateFormatAsString = enUsFormat.format(value);
    out.value(dateFormatAsString);
  }
}
         我們可以發現, 對於傳入的數據類型來說, Gson並不知道什麼時候要用它來適配,它的適配要完全依賴於這些適配器。當你傳入的是適配器 判斷條件所需的字節碼條件的時候, 就將採用該種適配器來進行適配 。 但是令人非常遺憾的是: 在gson這個門面的設計的時候,並沒有提供接口用來factory的擴展,這樣 你就得完全按照Gson的那一套規則 。 而且對於 一個對象的生成採用成文的規定,完全沒有給外界修改的權利。但是這 並不會影響G son作爲一個出色的項目,優雅的代碼結構而存在。

       代碼中讓我們非常崩潰的就是大量的匿名類,不過Gson似乎也意識到了這個問題:

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
      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 + "]";
      }
    };
  }
  我們發現它使用通用的模板來生成類,無可厚非。而且也複寫了toString爲了方便調試。我 寫了一段代碼來做測試:
/**
 * @author 非子墨
 * */
public class TestGson {
  public static class User{
    public String name;
    public int age;
    public Info info;
    @Override
    public String toString() {
      // TODO Auto-generated method stub
      return "["+name+":"+info.msg+"]";
    }
  }

  public static class Info{
    public Info() {}
    String msg;
  }
  public static void main(String[] args) {
    String gsonValue = "{name:'非子墨',age:23,info:{msg:'I am a student!'}}";
    Gson gson = new Gson() ;
    User user = gson.fromJson(gsonValue, User.class);
    System.out.println("user = "+user);
  }
}
最後的運行結果是:
Gson:>>>Factory[type=java.lang.String,adapter=com.google.gson.internal.bind.TypeAdapters$13@398020cc]
Gson:>>>Factory[type=java.lang.Integer+int,adapter=com.google.gson.internal.bind
Gson:>>>com.google.gson.internal.bind.ReflectiveTypeAdapterFactory@6030e280
Gson:create type = com.test.TestGson$Info
Gson:>>>com.google.gson.internal.bind.ReflectiveTypeAdapterFactory@6030e280
Gson:create type = com.test.TestGson$User
這裏面我 加了一些log ,我們可以通過這些log能很明白的理解Gson的總體解析結構,就是順序解析, 如果是對象的 話是通過
ReflectiveTypeAdapterFactory
這個類生成的Adapter對象來生成。我們也會發現,作爲最終要解析成的對象User, 實際上 是最後解析的, 因此我們推測,解析完 內部對象以後採用組合的方式來注入。至於組合模式,將會在我以後關於 <三國殺>設計模式的文章 中 補充到。

由於時間的 關係 本系列文章將會分成多個小節來寫, 下一篇我希望還是像Proguard源碼分析一樣 先從Json的數據讀 取開始, 主要的類是:

JsonReader .java 

待續。。。 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章