美文网首页初见
Moshi源码浅析

Moshi源码浅析

作者: reaiya | 来源:发表于2020-03-30 23:45 被阅读0次

    [TOC]

    Moshi源码浅析

    基于版本1.9.2,使用kotlin

    简单使用

    Moshi.Builder

    提供一个生成Moshi.Builder的open方法(Rfc3339DateJsonAdapter为Moshi提供的日期解析的Adapter)

    open fun getJson(): Moshi.Builder {
        return Moshi.Builder()
            .add(KotlinJsonAdapterFactory())
            .add(Date::class.java, Rfc3339DateJsonAdapter())
    }
    

    Retrofit.Builder().addConverterFactory

    在Retrofit.Builder()中添加MoshiConverterFactory

    retrofitClient.addConverterFactory(
        MoshiConverterFactory.create(
            getJson().build(),
            this@RetrofitClient
        )
    )
    

    Moshi.adapter

    以处理返回结果为例,在MoshiConverterFactory<Converter.Factory>的responseBodyConverter中根据类型和注解得到adapter

    JsonAdapter<?> adapter = moshi.adapter(type, jsonAnnotations(annotations));
    

    JsonAdapter.fromJson

    然后在MoshiResponseBodyConverter(Converter<ResponseBody, T>)的convert中使用这个adapter解析数据

    // value为ResponseBody
    result = adapter.fromJson(JsonReader.of(value.source()));
    

    流程分析

    得到adapter

    在处理结果时,需要得到一个JsonAdapter,我们看一下这部分的代码

    // 先从缓存中根据type和annotations生成的key去找是否有相应的Adapter
    Object cacheKey = cacheKey(type, annotations);
    synchronized (adapterCache) {
      JsonAdapter<?> result = adapterCache.get(cacheKey);
      if (result != null) return (JsonAdapter<T>) result;
    }
    
    // 得到一个ThreadLocal的LookupChain对象
    LookupChain lookupChain = lookupChainThreadLocal.get();
    if (lookupChain == null) {
      lookupChain = new LookupChain();
      lookupChainThreadLocal.set(lookupChain);
    }
    
    boolean success = false;
    /**
     * 在push中会根据cacheKey查找是否有相应的Lookup,如果找到hit:
     * return hit.adapter != null ? hit.adapter : hit;
     * 如果没找到,添加一个Lookup到lookupChain中,并返回null
     * Lookup本身是一个委托内部adapter实现的JsonAdapter,这里如果hit的adapter为null则返回hit不太理解,因为adapter为null的话,hit没有adapter的功能,会抛异常的
     */
    JsonAdapter<T> adapterFromCall = lookupChain.push(type, fieldName, cacheKey);
    if (adapterFromCall != null) return adapterFromCall;
    
    // 由内部的factories(List<JsonAdapter.Factory>)生成相应的Adapter
    for (int i = 0, size = factories.size(); i < size; i++) {
        JsonAdapter<T> result = (JsonAdapter<T>) factories.get(i).create(type, annotations, this);
        if (result == null) continue;
        
        // factory生成Adapter成功,将结果赋给当前的Lookup的adapter
        lookupChain.adapterFound(result);
        success = true;
        return result;
    }
    
    ....
    
    // 最后将lookupChain从ThreadLocal中remove,并且将当前调用中的所有lookup加入缓存中
    finally {
      lookupChain.pop(success);
    }
    

    通过观察LookupChain内部的数据结构(栈、列表),我们有理由怀疑上面的方法有一个递归调用的过程,也就是说在factory生成adapter的过程中会继续调用这个方法,我们往下看验证一下这个想法

    JsonAdapter.Factory生成adapter

    上面的过程中有一个从factories生成adapter的操作,我们看一下这部分的代码

    factories从何而来

    static final List<JsonAdapter.Factory> BUILT_IN_FACTORIES = new ArrayList<>(5);
    static {
        // 包含了基本类型对应的Adapter
        BUILT_IN_FACTORIES.add(StandardJsonAdapters.FACTORY);
        // 集合类(List、Collection、Set)的Adapter,包含了一个集合元素的类型对应的Adapter。
        // 而这个Adapter正是调用Moshi.adapter方法而来,验证了我们上面的想法
        BUILT_IN_FACTORIES.add(CollectionJsonAdapter.FACTORY);
        // Map的Adapter,包含了Map的key和value对应的Adapter,这两个Adapter同样是调用Moshi.adapter方法而来
        BUILT_IN_FACTORIES.add(MapJsonAdapter.FACTORY);
        // Array的Adapter,包含了元素的类型对应的Adapter
        BUILT_IN_FACTORIES.add(ArrayJsonAdapter.FACTORY);
        // 其它Class对应的Adapter。里面包含一个ClassFactory,用于生成Class的对象。
        // 包含一个FieldBinding数组,每个FieldBinding包含Class字段名、Class字段和对应的Adapter
        BUILT_IN_FACTORIES.add(ClassJsonAdapter.FACTORY);
    }
    
    // 在构造方法中factories初始化为默认的几个Factory和Builder中添加的Factory
    // 注意这里的顺序,是有优先级的
    Moshi(Builder builder) {
        List<JsonAdapter.Factory> factories = new ArrayList<>(
            builder.factories.size() + BUILT_IN_FACTORIES.size());
        factories.addAll(builder.factories);
        factories.addAll(BUILT_IN_FACTORIES);
        this.factories = Collections.unmodifiableList(factories);
    }
    
    // Builder中所有的add方法都是添加Factory
    add(final Type type, final JsonAdapter<T> jsonAdapter)
    add(final Type type, final Class<? extends Annotation> annotation,final JsonAdapter<T>jsonAdapter)
    add(JsonAdapter.Factory factory)
    // 这里没有指定type,是通过AdapterMethodsFactory生成的Factory,在Factory里获取了Return的类型。
    // 也没有指定为JsonAdapter,是因为内部是通过ToJson和FromJson注解获取对应的方法的。
    // 注意,AdapterMethodsFactory里面是使用列表存放Adapter,所以可以将所有的自定义类解析方法写在一个类里
    add(Object adapter)
    addAll(List<JsonAdapter.Factory> factories)
      
    

    KotlinJsonAdapterFactory

    这个Factory是给Kotlin生成Adapter提供支持的,下面我们看一下它是如何实现的

    使用自动生成Adapter代码功能需要启用moshi-kotlin-codegen

    // 检查是否有JsonClass注解
    JsonClass jsonClass = rawType.getAnnotation(JsonClass.class);
    if (jsonClass == null || !jsonClass.generateAdapter()) {
      return null;
    }
    // 这里Adapter的类名为“当前类名+JsonAdapter”
    String adapterClassName = Types.generatedJsonAdapterName(rawType.getName());
    // 查找构造方法生成Adapter的对象返回
    try {
      Class<? extends JsonAdapter<?>> adapterClass = (Class<? extends JsonAdapter<?>>)Class.forName(adapterClassName, true, rawType.getClassLoader());
      Constructor<? extends JsonAdapter<?>> constructor;
      Object[] args;
      if (type instanceof ParameterizedType) {
        Type[] typeArgs = ((ParameterizedType) type).getActualTypeArguments();
        try {
          // 有一个Moshi参数和Array<Type>参数的构造方法
          constructor = adapterClass.getDeclaredConstructor(Moshi.class, Type[].class);
          args = new Object[] { moshi, typeArgs };
        } catch (NoSuchMethodException e) {
          // 只有一个Array<Type>参数的构造方法
          constructor = adapterClass.getDeclaredConstructor(Type[].class);
          args = new Object[] { typeArgs };
        }
      } else {
        try {
          // 只有一个Moshi参数的构造方法
          constructor = adapterClass.getDeclaredConstructor(Moshi.class);
          args = new Object[] { moshi };
        } catch (NoSuchMethodException e) {
          // 无参构造方法
          constructor = adapterClass.getDeclaredConstructor();
          args = new Object[0];
        }
      }
      constructor.setAccessible(true);
      return constructor.newInstance(args).nullSafe();
    

    如果没有JsonClass注解或发生错误

    // 查找主构造方法
    val constructor = rawTypeKotlin.primaryConstructor ?: return null
    val parametersByName = constructor.parameters.associateBy { it.name }
    constructor.isAccessible = true
    
    val bindingsByName = LinkedHashMap<String, KotlinJsonAdapter.Binding<Any, Any?>>()
    
    for (property in rawTypeKotlin.memberProperties) {
      val parameter = parametersByName[property.name]
    
      ...
      
      property.isAccessible = true
      val allAnnotations = property.annotations.toMutableList()
      var jsonAnnotation = property.findAnnotation<Json>()
      // 如果该字段在主构造方法中,将构造方法中该字段的注解加上,而字段上的Json注解优先级高于构造方法中的该字段的
      if (parameter != null) {
        allAnnotations += parameter.annotations
        if (jsonAnnotation == null) {
          jsonAnnotation = parameter.findAnnotation()
        }
      }
    
      // 如果有Json注解,解析需要的字段名为注解的字段名
      val name = jsonAnnotation?.name ?: property.name
      val resolvedPropertyType = resolve(type, rawType, property.returnType.javaType)
      val adapter = moshi.adapter<Any>(
          resolvedPropertyType, Util.jsonAnnotations(allAnnotations.toTypedArray()), property.name)
    
      bindingsByName[property.name] = KotlinJsonAdapter.Binding(
          name,
          jsonAnnotation?.name ?: name,
          adapter,
          property as KProperty1<Any, Any?>,
          parameter,
          parameter?.index ?: -1
      )
    }
    
    val bindings = ArrayList<KotlinJsonAdapter.Binding<Any, Any?>?>()
    // 将构造方法中的参数放在前面,不理解有什么意义
    for (parameter in constructor.parameters) {
      val binding = bindingsByName.remove(parameter.name)
      require(binding != null || parameter.isOptional) {
        "No property for required constructor $parameter"
      }
      bindings += binding
    }
    
    var index = bindings.size
    for (bindingByName in bindingsByName) {
      bindings += bindingByName.value.copy(propertyIndex = index++)
    }
    // 这里bindings将字段名和字段索引做了映射,为什么要这样做,查找资料说是解析List的时候能提高效率,看了JsonValueReader源码并不理解
    val nonTransientBindings = bindings.filterNotNull()
    val options = JsonReader.Options.of(*nonTransientBindings.map { it.name }.toTypedArray())
    return KotlinJsonAdapter(constructor, bindings, nonTransientBindings, options).nullSafe()
    

    JsonReader

    数据究竟是怎样从Buffer解析成一个对象的,我们看一下Moshi里为我们提供的两个JsonReader,JsonUtf8Reader和JsonValueReader

    以ClassJsonAdapter为例

    JsonUtf8Reader

    从构造方法接受一个BufferedSource参数能知道,这个JsonReader肯定是网络请求后返回的数据的第一个爸爸

    下面我们从ClassJsonAdapter的fromJson方法开始,看一下JsonReader是如何解析数据的

    // 开始读一个Object
    reader.beginObject();
    while (reader.hasNext()) {
        // 通过字段名得到字段索引
        int index = reader.selectName(options);
        // 如果没有该字段,跳过
        if (index == -1) {
          reader.skipName();
          reader.skipValue();
          continue;
        }
        // 通过该字段的Adapter解析该字段的值
        fieldsArray[index].read(reader, result);
    }
    // 读完一个Object
    reader.endObject();
    

    beginObject()做了什么

    @Override public void beginObject() throws IOException {
        int p = peeked;
        if (p == PEEKED_NONE) {
          // 当前需要查看接下来需要解析什么数据,里面逻辑有点复杂,感兴趣可以仔细看一下
          p = doPeek();
        }
        if (p == PEEKED_BEGIN_OBJECT) {
          pushScope(JsonScope.EMPTY_OBJECT);
          peeked = PEEKED_NONE;
        } else {
          throw new JsonDataException("Expected BEGIN_OBJECT but was " + peek()
              + " at path " + getPath());
        }
    }
    

    JsonUtf8Reader读取类型数据是通过在doPeek中利用匹配json格式来完成的

    JsonValueReader

    从构造方法接受一个Object参数能知道,这个JsonReader是将一个对象作为输入来解析的

    JsonAdapter中的fromJsonValue还是调用了fromJson,只是传入的是一个JsonValueReader,我们直接看JsonValueReader里的beginObject()

    @Override public void beginObject() throws IOException {
        // 先将数据转换为一个Map
        Map<?, ?> peeked = require(Map.class, Token.BEGIN_OBJECT);
        // 利用entrySet构造一个迭代器
        JsonIterator iterator = new JsonIterator(
            Token.END_OBJECT, peeked.entrySet().toArray(new Object[peeked.size()]), 0);
        stack[stackSize - 1] = iterator;
        scopes[stackSize - 1] = JsonScope.EMPTY_OBJECT;
        
        // 将第一个Entry压入stack,下一步解析使用
        if (iterator.hasNext()) {
          push(iterator.next());
        }
    }
    

    JsonValueReader读取类型数据是直接通过在require中强制数据转换完成的,因为传入的数据本来就是解析过的对象

    结语

    上面还有几个疑问:

    • 在生成类中各字段对应的Adapter的列表时,将构造方法中的参数对应的Adapter放在前面,这样做有什么意义
    • Adapter里面使用字段名和字段索引映射,这样做的好处是什么

    可能会碰到的坑:

    • 以下类型的Class不会自动生成Adapter:Sealed、Abstract、Internal
    • 子类的构造方法的字段名要和父类一致,并且不在子类的构造方法里的字段不会被解析

    总体来说,Moshi的解析流程还是很清晰的,类型对应相应的Adapter,Factory就是用来生产Adapter的。可能还有一个盲点就是泛型,有时间再看看这一块。

    新鲜出炉的“未来邮递员”来了,它带着兑换码来了,快来尝鲜吧! 未来邮递员

    写于2020-03-30

    本篇文章由一文多发平台ArtiPub自动发布

    相关文章

      网友评论

        本文标题:Moshi源码浅析

        本文链接:https://www.haomeiwen.com/subject/pzciuhtx.html