gson-plugin基础源码分析(二)
一、项目地址
项目地址: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();
四、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)) {\n" + " return null;\n" + " }") ctClass.writeFile(dirPath) ctClass.detach() println("GsonPlugin: inject ArrayTypeAdapter success") } } } } }
七、目录
1.gson-plugin告别Json数据类型不一致(一)
2.gson-plugin基础源码分析(二)
3.gson-plugin深入源码分析(三)
4.gson-plugin如何在JitPack发布(四)