gson-plugin基礎原始碼分析(二)

月籠冰泉發表於2019-01-19

一、專案地址

專案地址:github-gson-plugin

二、Gson解析核心類

1.ArrayTypeAdapter.JAVA 用於解析陣列型別的資料

  public Object read(JsonReader in) throws IOException {
    if(in.peek() == JsonToken.NULL) {
      in.nextNull();
      return null;
    } else {
      List<E> list = new ArrayList();
      in.beginArray();

      Object array;
      while(in.hasNext()) {
        array = this.componentTypeAdapter.read(in);
        list.add(array);
      }

      in.endArray();
      array = Array.newInstance(this.componentType, list.size());

      for(int i = 0; i < list.size(); ++i) {
        Array.set(array, i, list.get(i));
      }

      return array;
    }
  }

2.CollectionTypeAdapterFactory.JAVA 用於解析集合型別的資料

    @Override public Collection<E> read(JsonReader in) throws IOException {
      if (in.peek() == JsonToken.NULL) {
        in.nextNull();
        return null;
      }

      Collection<E> collection = constructor.construct();
      in.beginArray();
      while (in.hasNext()) {
        E instance = elementTypeAdapter.read(in);
        collection.add(instance);
      }
      in.endArray();
      return collection;
    }

3.MapTypeAdapterFactory.JAVA 用於解析map型別的資料

    @Override public Map<K, V> read(JsonReader in) throws IOException {
      JsonToken peek = in.peek();
      if (peek == JsonToken.NULL) {
        in.nextNull();
        return null;
      }

      Map<K, V> map = constructor.construct();

      if (peek == JsonToken.BEGIN_ARRAY) {
        in.beginArray();
        while (in.hasNext()) {
          in.beginArray(); // entry array
          K key = keyTypeAdapter.read(in);
          V value = valueTypeAdapter.read(in);
          V replaced = map.put(key, value);
          if (replaced != null) {
            throw new JsonSyntaxException("duplicate key: " + key);
          }
          in.endArray();
        }
        in.endArray();
      } else {
        in.beginObject();
        while (in.hasNext()) {
          JsonReaderInternalAccess.INSTANCE.promoteNameToValue(in);
          K key = keyTypeAdapter.read(in);
          V value = valueTypeAdapter.read(in);
          V replaced = map.put(key, value);
          if (replaced != null) {
            throw new JsonSyntaxException("duplicate key: " + key);
          }
        }
        in.endObject();
      }
      return map;
    }

4.ReflectiveTypeAdapterFactory.JAVA 用於解析Object型別

   @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;
    }

5.TypeAdapters.JAVA 用於解析基本資料型別
裡邊每種基本資料型別,都對應一個匿名內部類,只列出boolean型別的解析,其它省略

  public static final TypeAdapter<Boolean> BOOLEAN = new TypeAdapter<Boolean>() {
    @Override
    public Boolean read(JsonReader in) throws IOException {
      JsonToken peek = in.peek();
      if (peek == JsonToken.NULL) {
        in.nextNull();
        return null;
      } else if (peek == JsonToken.STRING) {
        // support strings for compatibility with GSON 1.7
        return Boolean.parseBoolean(in.nextString());
      }
      return in.nextBoolean();
    }
    @Override
    public void write(JsonWriter out, Boolean value) throws IOException {
      out.value(value);
    }
  };

三、跳過異常欄位

1.對於ArrayTypeAdapter.JAVA,CollectionTypeAdapterFactory.JAVA,MapTypeAdapterFactory.JAVA,ReflectiveTypeAdapterFactory.JAVA 我們很清楚的知道預期的資料型別是啥,所以可以判斷當前的資料型別是否與預期的資料型別一致,如果不一致則跳過解析。
2.對於TypeAdapters.JAVA 如果以修改原始碼的方式,也可以通過判斷當前的資料型別是否與預期的資料型別一致的方式進行。但由於每種資料型別都是一個匿名內部類,很難通過javassist判斷預期的資料型別是啥,所以可以通過新增try-catch捕獲異常,在發生異常後,跳過解析。
3.判斷資料型別是否與預期的資料型別一致,如果不一致則跳過解析。

  /**
   * used for array、collection、map、object
   * skipValue when expected token error
   *
   * @param in input json reader
   * @param expectedToken expected token
   */
  public static boolean checkJsonToken(JsonReader in, JsonToken expectedToken) {
    if (in == null || expectedToken == null) {
      return false;
    }
    JsonToken inToken = null;
    try {
      inToken = in.peek();
    } catch (IOException e) {
      e.printStackTrace();
    }
    if (inToken == expectedToken) {
      return true;
    }
    if (inToken != JsonToken.NULL) {
      String exception = "expected " + expectedToken + " but was " + inToken + " path " + in.getPath();
      notifyJsonSyntaxError(exception);
    }
    skipValue(in);
    return false;
  }

1.方法入參:輸入的json為JsonReader,期望的資料型別為JsonToken
2.期望的資料型別:在呼叫方法的時候傳入
3.當前的資料型別:通過in.peek()獲取
4.當前欄位:通過in.getPath()獲取
5.異常資訊拼接:

      String exception = "expected " + expectedToken + " but was " + inToken + " path " + in.getPath();

ReaderTools.JAVA原始碼

四、GsonPlugin外掛編寫


/**
 * Created by tangfuling on 2018/10/25.
 */

class GsonPlugin implements Plugin<Project> {

  @Override
  void apply(Project project) {
    //add dependencies
    project.dependencies.add("compile",
        "com.ke.gson.sdk:gson_sdk:1.3.0")
    //add transform
    project.android.registerTransform(new GsonJarTransform(project))
  }
}

五、Transform侵入編譯流程

/**
 * Created by tangfuling on 2018/10/25.
 */

class GsonJarTransform extends Transform {

  private Project mProject

  GsonJarTransform(Project project) {
    mProject = project
  }

  @Override
  String getName() {
    return "GsonJarTransform"
  }

  @Override
  Set<QualifiedContent.ContentType> getInputTypes() {
    return TransformManager.CONTENT_CLASS
  }

  @Override
  Set<? super QualifiedContent.Scope> getScopes() {
    return TransformManager.SCOPE_FULL_PROJECT
  }

  @Override
  boolean isIncremental() {
    return false
  }

  @Override
  void transform(TransformInvocation transformInvocation)
      throws TransformException, InterruptedException, IOException {
    //初始化ClassPool
    MyClassPool.resetClassPool(mProject, transformInvocation)

    //處理jar和file
    TransformOutputProvider outputProvider = transformInvocation.getOutputProvider()
    for (TransformInput input : transformInvocation.getInputs()) {
      for (JarInput jarInput : input.getJarInputs()) {
        // name must be unique,or throw exception "multiple dex files define"
        def jarName = jarInput.name
        if (jarName.endsWith(`.jar`)) {
          jarName = jarName.substring(0, jarName.length() - 4)
        }
        def md5Name = DigestUtils.md5Hex(jarInput.file.getAbsolutePath())
        //source file
        File file = InjectGsonJar.inject(jarInput.file, transformInvocation.context, mProject)
        if (file == null) {
          file = jarInput.file
        }
        //dest file
        File dest = outputProvider.getContentLocation(jarName + md5Name,
            jarInput.contentTypes, jarInput.scopes, Format.JAR)
        FileUtils.copyFile(file, dest)
      }

      for (DirectoryInput directoryInput : input.getDirectoryInputs()) {
        File dest = outputProvider.getContentLocation(directoryInput.name,
          directoryInput.contentTypes, directoryInput.scopes, Format.DIRECTORY)
        FileUtils.copyDirectory(directoryInput.file, dest)
      }
    }
  }
}

六、javassist修改Gson位元組碼

修改ArrayTypeAdapter.JAVA的read()方法

/**
 * Created by tangfuling on 2018/10/30.
 */

public class InjectArrayTypeAdapter {

  public static void inject(String dirPath) {

    ClassPool classPool = MyClassPool.getClassPool()

    File dir = new File(dirPath)
    if (dir.isDirectory()) {
      dir.eachFileRecurse { File file ->
        if ("ArrayTypeAdapter.class".equals(file.name)) {
          CtClass ctClass = classPool.getCtClass("com.google.gson.internal.bind.ArrayTypeAdapter")
          CtMethod ctMethod = ctClass.getDeclaredMethod("read")
          ctMethod.insertBefore("     if (!com.ke.gson.sdk.ReaderTools.checkJsonToken($1, com.google.gson.stream.JsonToken.BEGIN_ARRAY)) {
" +
              "        return null;
" +
              "      }")
          ctClass.writeFile(dirPath)
          ctClass.detach()
          println("GsonPlugin: inject ArrayTypeAdapter success")
        }
      }
    }
  }
}

七、目錄

1.gson-plugin告別Json資料型別不一致(一)
2.gson-plugin基礎原始碼分析(二)
3.gson-plugin深入原始碼分析(三)
4.gson-plugin如何在JitPack釋出(四)

相關文章