博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
gson-plugin深入源码分析(三)
阅读量:7232 次
发布时间:2019-06-29

本文共 11208 字,大约阅读时间需要 37 分钟。

一、项目地址

项目地址:

二、ReaderTools解析

/** * Created by tangfuling on 2018/10/23. */public class ReaderTools {  private static JsonSyntaxErrorListener mListener;  public static void setListener(JsonSyntaxErrorListener listener) {    mListener = listener;  }  /**   * 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;  }  /**   * used for basic data type, we only deal type Number and Boolean   * skipValue when json parse error   *   * @param in input json reader   * @param exception json parse exception   */  public static void onJsonTokenParseException(JsonReader in, Exception exception) {    if (in == null || exception == null) {      return;    }    skipValue(in);    notifyJsonSyntaxError(exception.getMessage());  }  private static void skipValue(JsonReader in) {    if (in == null) {      return;    }    try {      in.skipValue();    } catch (IOException e) {      e.printStackTrace();    }  }  private static void notifyJsonSyntaxError(String exception) {    if (mListener == null) {      return;    }    String invokeStack = Log.getStackTraceString(new Exception("syntax error exception"));    mListener.onJsonSyntaxError(exception, invokeStack);  }  public interface JsonSyntaxErrorListener {    public void onJsonSyntaxError(String exception, String invokeStack);  }}

1.对外暴露setListener()接口,用户可以监听到Json解析异常。

2.checkJsonToken()方法,用于判断输入字段的数据类型是否与预期的数据类型一致,如果数据类型不一致,则跳过解析,同时通知listener解析失败。该方法用于判断array、collection、map、object是否合法。
3.onJsonTokenParseException()方法,会利用javassist对Gson抛出的Exception进行捕获,然后调用该方法,同时通知listener解析失败。该方法用于判断Integer、Boolean等基本数据类型。

三、GsonPlugin插件编写

1.ReaderTools.java的setListener()方法需要暴露给用户使用,但Plugin仅仅是一个插件,无法将java语言的接口暴露出去给用户使用,所以需要建立2个工程。

2.gson-plugin-sdk:主要包含ReaderTools.java,与用户交互的类及方法需要在这个sdk中定义并实现。
3.gson-plugin:主要是侵入编译流程,并修改Gson的字节码,同时在特定的地方调用ReaderTools.java中的方法,如checkJsonToken()方法,onJsonTokenParseException()方法等。
4.这样用户接入需要引入两个库,gson-plugin-sdk和gson-plugin。
5.为了方便用户接入,可以在gson-plugin中帮助用户引入gson-plugin-sdk,这样用户就只需要引入gson-plugin即可。
6.在gson-plugin中帮助用户引入gson-plugin-sdk

project.dependencies.add("compile", "com.ke.gson.sdk:gson_sdk:1.3.0")

7.GsonPlugin为插件入口类,在此注册自定义的GsonJarTransform

/** * 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)) }}

四、GsonJarTransform编译流程

@Override  String getName() {    return "GsonJarTransform"  }  @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)      }    }  }

1.初始化ClassPool,javassist中用到的类都需要先加入ClassPath。

/** * Created by tangfuling on 2018/10/31. */public class MyClassPool {  private static ClassPool sClassPool  public static ClassPool getClassPool() {    return sClassPool  }  public static void resetClassPool(Project project, TransformInvocation transformInvocation) {    // ClassPool.getDefault() 有可能被其他使用 Javassist 的插件污染(如 nuwa),    // 导致ClassPool中出现重复的类,Javassist抛出异常,所以不能使用默认的    sClassPool = new ClassPool()    sClassPool.appendSystemPath()    // bootClasspath 包括 android.jar 和 useLibrary 指定的library 的路径(如 org.apache.http.legacy )    project.android.bootClasspath.each {      sClassPool.appendClassPath(it.absolutePath)    }    // 其它class    for (TransformInput input : transformInvocation.getInputs()) {      for (JarInput jarInput : input.getJarInputs()) {        sClassPool.appendClassPath(jarInput.file.getAbsolutePath())      }      for (DirectoryInput directoryInput : input.getDirectoryInputs()) {        sClassPool.appendClassPath(directoryInput.file.getAbsolutePath())      }    }  }}

2.transform处理过程

2.1.在编译过程中,transform会对项目中所有依赖的jar文件和项目本身的class文件进行处理,将处理结果交给下一个步骤,继续处理。
2.2.如果不做任何处理,那么transform至少会做一件事情,将输入的jar文件和class文件,拷贝到build/intermediates/transforms/GsonJarTransform目录。
2.3.gson-plugin需要对gson.jar做处理。

File file = InjectGsonJar.inject(jarInput.file, transformInvocation.context, mProject)

五、处理gson.jar包

/** * Created by tangfuling on 2018/10/25. */class InjectGsonJar {  public static File inject(File jarFile, Context context, Project project) throws NotFoundException {    if (!jarFile.name.contains("gson")) {      return null    }    println("GsonPlugin: inject gson jar start")    //原始jar path    String srcPath = jarFile.getAbsolutePath()    //原始jar解压后的tmpDir    String tmpDirName = jarFile.name.substring(0, jarFile.name.length() - 4)    String tmpDirPath = context.temporaryDir.getAbsolutePath() + File.separator + tmpDirName    //目标jar path    String targetPath = context.temporaryDir.getAbsolutePath() + File.separator + jarFile.name    //解压    Decompression.uncompress(srcPath, tmpDirPath)    //修改    InjectReflectiveTypeAdapterFactory.inject(tmpDirPath)    InjectMapTypeAdapterFactory.inject(tmpDirPath)    InjectArrayTypeAdapter.inject(tmpDirPath)    InjectCollectionTypeAdapterFactory.inject(tmpDirPath)    InjectTypeAdapters.inject(tmpDirPath)    //重新压缩    Compressor.compress(tmpDirPath, targetPath)    //删除临时目录    StrongFileUtil.deleteDirPath(tmpDirPath)    println("GsonPlugin: inject gson jar success")    //返回目标jar    File targetFile = new File(targetPath)    if (targetFile.exists()) {      return targetFile    }    return null  }}

1.输入的gson.jar位置:.gradle/caches/modules-2/files-2.1/com.google.code.gson/gson

2.对输入的jar包解压到一个临时目录,并对解压后的class文件进行修改:build/tmp/transformClassesWithGsonJarTransformForDebug,会生成一个文件夹gson-2.8.5
3.将修改后的文件重新压缩到当前目录:build/tmp/transformClassesWithGsonJarTransformForDebug,会重新生成一个jar包gson-2.8.5.jar
4.删除步骤2中生成的文件夹gson-2.8.5
5.将tmp目录下的gson-2.8.5.jar返回
6.transform会将tmp目录下gson-2.8.5.jar拷贝到build/intermediates/transforms/GsonJarTransform目录供下一个步骤使用。

六、修改内部类的方法

1.这个Adapter.class的read()方法是对Object类型的数据进行解析,我们判断输入的数据类型不是Object类型,就直接跳过解析,核心是在read()方法中插入ReaderTools.checkJsonToken()方法。

2.每一个类、每一个内部类、每一个匿名内部类,都会生成一个独立的.class文件,如ReflectiveTypeAdapterFactory.class,ReflectiveTypeAdapterFactory$Adapter.class,ReflectiveTypeAdapterFactory$BoundField.class,ReflectiveTypeAdapterFactory$1.class。
3.遍历文件夹找到对应的class,通过javassist在read()方法前面插入判断代码。

/** * Created by tangfuling on 2018/10/30. */public class InjectReflectiveTypeAdapterFactory {  public static void inject(String dirPath) {    ClassPool classPool = MyClassPool.getClassPool()    File dir = new File(dirPath)    if (dir.isDirectory()) {      dir.eachFileRecurse { File file ->        if ("ReflectiveTypeAdapterFactory.class".equals(file.name)) {          CtClass ctClass = classPool.getCtClass("com.google.gson.internal.bind.ReflectiveTypeAdapterFactory\$Adapter")          CtMethod ctMethod = ctClass.getDeclaredMethod("read")          ctMethod.insertBefore("     if (!com.ke.gson.sdk.ReaderTools.checkJsonToken(\$1, com.google.gson.stream.JsonToken.BEGIN_OBJECT)) {\n" +              "        return null;\n" +              "      }")          ctClass.writeFile(dirPath)          ctClass.detach()          println("GsonPlugin: inject ReflectiveTypeAdapterFactory success")        }      }    }  }}

七、字节码加 try-catch

1.TypeAdapters.class处理基本数据类型,每个基本数据类型都对应一个匿名内部类

public static final TypeAdapter
BOOLEAN = new TypeAdapter
() { public Boolean read(JsonReader in) throws IOException { if(in.peek() == JsonToken.NULL) { in.nextNull(); return null; } else { return in.peek() == JsonToken.STRING?Boolean.valueOf(Boolean.parseBoolean(in.nextString())):Boolean.valueOf(in.nextBoolean()); } } public void write(JsonWriter out, Boolean value) throws IOException { if(value == null) { out.nullValue(); } else { out.value(value.booleanValue()); } } };

2.找到TypeAdapters的所有内部类,获取内部类的read()方法的返回值,如果是Number或Boolean类型,添加try-catch代码块,并回调ReaderTools.onJsonTokenParseException()方法。

/** * Created by tangfuling on 2018/10/30. */public class InjectTypeAdapters {  public static void inject(String dirPath) {    ClassPool classPool = MyClassPool.getClassPool()    File dir = new File(dirPath)    if (dir.isDirectory()) {      dir.eachFileRecurse { File file ->        if (file.name.contains("TypeAdapters\$")) {          String innerClassName = file.name.substring(13, file.name.length() - 6)          CtClass ctClass = classPool.getCtClass("com.google.gson.internal.bind.TypeAdapters\$" + innerClassName)          //only deal type Boolean and Number          CtMethod[] methods = ctClass.declaredMethods          boolean isModified = false          for (CtMethod ctMethod : methods) {            if ("read".equals(ctMethod.name)) {              String returnTypeName = ctMethod.getReturnType().name              if ("java.lang.Number".equals(returnTypeName)                  || "java.lang.Boolean".equals(returnTypeName)) {                CtClass etype = classPool.get("java.lang.Exception")                ctMethod.addCatch("{com.ke.gson.sdk.ReaderTools.onJsonTokenParseException(\$1, \$e); return null;}", etype)                isModified = true              }            }          }          if (isModified) {            ctClass.writeFile(dirPath)            println("GsonPlugin: inject TypeAdapters success")          }          ctClass.detach()        }      }    }  }}

3.其中$1表示read()方法的第1个参数JsonReader,$e表示捕获的Exception

八、目录

转载地址:http://zzvfm.baihongyu.com/

你可能感兴趣的文章
xmr monero miner
查看>>
Python 元祖的操作
查看>>
05-老马jQuery教程-动画
查看>>
[RK3288][Android6.0] 调试笔记 --- 通用GPIO驱动控制LED【转】
查看>>
浅谈struts2的国际化----i18n
查看>>
一步一步从原理跟我学邮件收取及发送 3.telnet命令行发一封信
查看>>
【BIEE】08_修改浏览器标题栏显示内容
查看>>
MFC中的双缓冲技术(解决绘图闪烁问题)
查看>>
select * from A.B.C.D sqlserver 中 select * from .Literary_PuDong.dbo.Users
查看>>
linux LVM:物理卷逻辑卷
查看>>
Windows10 显示库、隐藏6个目录、隐藏OneDrive
查看>>
IE的layout布局
查看>>
常见前端知识摘要
查看>>
使用neon 开发nodejs addon
查看>>
Win8 Metro(C#)数字图像处理--2.69中点滤波器
查看>>
Python之排序
查看>>
Appium 点击屏幕
查看>>
正则表达式30分钟入门教程
查看>>
新建DataTable添加列添加行
查看>>
[LeetCode]460.LFU缓存机制
查看>>