Moshi源碼淺析

[TOC]java

Moshi源碼淺析

基於版本1.9.2,使用kotlin

簡單使用

Moshi.Builder

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

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

Retrofit.Builder().addConverterFactory

在Retrofit.Builder()中添加MoshiConverterFactorygithub

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

Moshi.adapter

以處理返回結果爲例,在MoshiConverterFactory<Converter.Factory>的responseBodyConverter中根據類型和註解獲得adapterjson

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註解或發生錯誤ide

// 查找主構造方法
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自動發佈

相關文章
相關標籤/搜索