资讯专栏INFORMATION COLUMN

gson-plugin基础源码分析(二)

beanlam / 310人阅读

摘要:对于如果以修改源码的方式,也可以通过判断当前的数据类型是否与预期的数据类型一致的方式进行。但由于每种数据类型都是一个匿名内部类,很难通过判断预期的数据类型是啥,所以可以通过添加捕获异常,在发生异常后,跳过解析。

一、项目地址

项目地址: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 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 read(JsonReader in) throws IOException {
      if (in.peek() == JsonToken.NULL) {
        in.nextNull();
        return null;
      }

      Collection 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 read(JsonReader in) throws IOException {
      JsonToken peek = in.peek();
      if (peek == JsonToken.NULL) {
        in.nextNull();
        return null;
      }

      Map 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 = new TypeAdapter() {
    @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 {

  @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 getInputTypes() {
    return TransformManager.CONTENT_CLASS
  }

  @Override
  Set 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发布(四)

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/72238.html

相关文章

  • gson-plugin如何在JitPack发布(四)

    摘要:一项目地址项目地址二与关系普通的库可以通过源码的方式直接引入并使用,是一个插件,无法通过源码的方式使用,只能编译并发布以后,才能被正常使用。是一个代码仓库,我们可以将源代码托管在这个平台上。 一、项目地址 项目地址:github-gson-plugin 二、github与JitPack关系 1.普通的java库可以通过源码的方式直接引入并使用,gson-plugin是一个插件,无法通过...

    StonePanda 评论0 收藏0
  • gson-plugin告别Json数据类型不一致(一)

    摘要:六原理说明侵入编译流程,在编译过程中,修改库的字节码,修改解析相关的方法,在数据类型不一致的时候,跳过当前字段的解析。 一、目录 1.gson-plugin告别Json数据类型不一致(一)2.gson-plugin基础源码分析(二)3.gson-plugin深入源码分析(三)4.gson-plugin如何在JitPack发布(四) 看完这4篇文章,对Gson解析会有更加深刻的认识,对A...

    canopus4u 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<