Moshi原理分析

基本使用

Kotlin 复制代码
val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
val xxx = moshi.adapter(XXXX::class.java).fromJson(jsonstring)
  • @Json(name="xxx") 设置别名
  • @Json(ignore = true) 忽略字段
  • @JsonClass(generateAdapter=true)编译生成JsonAdapter

初始化

Java 复制代码
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);
  this.lastOffset = builder.lastOffset;
}

// 内置 Factory 初始化
static {
  BUILT_IN_FACTORIES.add(StandardJsonAdapters.FACTORY);
  BUILT_IN_FACTORIES.add(CollectionJsonAdapter.FACTORY);
  BUILT_IN_FACTORIES.add(MapJsonAdapter.FACTORY);
  BUILT_IN_FACTORIES.add(ArrayJsonAdapter.FACTORY);
  BUILT_IN_FACTORIES.add(RecordJsonAdapter.FACTORY);
  BUILT_IN_FACTORIES.add(ClassJsonAdapter.FACTORY);
}
  1. 添加 Builder 中配置的 Factory
  2. 添加内置的 Factory

JsonClass 注解

  1. 编译期间为类型生成默认的 JsonAdapter
  2. 只能用于 Kotlin class,如果有父类也必须是 Kotlin class

JsonAdapter查找

Moshi.adapter(type),查找处理指定类型的 JsonAdapter

Java 复制代码
public <T> JsonAdapter<T> adapter(
    Type type, Set<? extends Annotation> annotations, @Nullable String fieldName) {

  // 缓存
  Object cacheKey = cacheKey(type, annotations);
  synchronized (adapterCache) {
    JsonAdapter<?> result = adapterCache.get(cacheKey);
    if (result != null) return (JsonAdapter<T>) result;
  }

  LookupChain lookupChain = lookupChainThreadLocal.get();
  if (lookupChain == null) {
    lookupChain = new LookupChain();
    lookupChainThreadLocal.set(lookupChain);
  }

  boolean success = false;
  JsonAdapter<T> adapterFromCall = lookupChain.push(type, fieldName, cacheKey);
  try {
    // Lookup 没获取到,新的查询
    if (adapterFromCall != null) return adapterFromCall;

    // 遍历factory创建JsonAdapter
    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;

      // 查找成功,修改 Lookup 的 adapter
      lookupChain.adapterFound(result);
      success = true;
      return result;
    }
  }
}

<T> JsonAdapter<T> push(Type type, @Nullable String fieldName, Object cacheKey) {
  for (int i = 0, size = callLookups.size(); i < size; i++) {
    Lookup<?> lookup = callLookups.get(i);
    if (lookup.cacheKey.equals(cacheKey)) {
      Lookup<T> hit = (Lookup<T>) lookup;
      stack.add(hit);
      // 存在查找,返回具体 JsonAdapter 或者代理 Lookup
      return hit.adapter != null ? hit.adapter : hit;
    }
  }

  // 首次查找,创建 Lookup 并保存
  Lookup<Object> lookup = new Lookup<>(type, fieldName, cacheKey);
  callLookups.add(lookup);
  stack.add(lookup);
  return null;
}
  1. 查找缓存;
  2. 获取 LookupChain;
  3. 调用 LookupChain.push 提交查询;
  4. 如果当前没有相同类型的查询则创建 LookUp 类型,保存并返回;
  5. LookUp相当于JsonAdapter的代理对象,本身继承了JsonAdapter,内部持有一个 JsonAdapter 成员,次成员是最终查找到的实际 Adapter,序列化及反序列化委托给次成员实现;
  6. 如果当前存在相同类型的查询,获取其对应的 Lookup 对象,如果其中的 adpter 不为空,返回代理的JsonAdapter,否则返回 Lookup 自身;
  7. 如果 LookUpChain.push 返回不为 null,表明查找到了,否则遍历初始化 Moshi 是配置的 factories 列表,调用其 create 方法并传递类型信息,如果 create 返回非空,表示查找到了;
  8. 通知 LookupChain 找到 adppter了,对 LookUp 设置 adapter;
  9. 总结:本身逻辑应该比较简单,缓存查不到直接通过 factory 创建就好了,但是这边加了一个 LookUp 的代理逻辑,这个主要是为了解决循环依赖的问题,比如有 A 和 B 两个类型,A中包含一个B类型的成员,B中包含一个A类型的成员,这时候查找A的JsonAdapter的过程中会去查找B的JsonAdapter,那在查找B的JsonAdapter同时又依赖了A的JsonAdapter,这样就循环依赖了。通过 LookUp 代理,在查找A的时候插入了一个LookUp 代理,没找到A走创建,创建过程中查找B,B也没有同样走创建,创建B的时候查找A返回了A的Lookup对象,这样B创建完成,返回继续A的创建,A创建完了把Lookup中的adapter替换。

反序列化Java对象

由内置的 ClassJsonAdapter 完成

ini 复制代码
public T fromJson(JsonReader reader) throws IOException {
  T result;
  try {
    result = classFactory.newInstance();
  }
  // 身略 catch

  try {
    reader.beginObject();
    while (reader.hasNext()) {
      int index = reader.selectName(options);
      if (index == -1) {
        reader.skipName();
        reader.skipValue();
        continue;
      }
      fieldsArray[index].read(reader, result);
    }
    reader.endObject();
    return result;
  } catch (IllegalAccessException e) {
    throw new AssertionError();
  }
}

内部通过 ClassFactory 创建出对象,在读取属性赋值

Java 复制代码
// 1、获取无参构造方法,反射创建对象
try {
  final Constructor<?> constructor = rawType.getDeclaredConstructor();
  constructor.setAccessible(true);
  return new ClassFactory<T>() {
    @Override
    public T newInstance()
        throws IllegalAccessException, InvocationTargetException, InstantiationException {
      Object[] args = null;
      return (T) constructor.newInstance(args);
    }

    @Override
    public String toString() {
      return rawType.getName();
    }
  };
} catch (NoSuchMethodException ignored) {
}

// 2、通过 Unsafe 的 allocateInstance 创建对象
try {
  Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
  Field f = unsafeClass.getDeclaredField("theUnsafe");
  f.setAccessible(true);
  final Object unsafe = f.get(null);
  final Method allocateInstance = unsafeClass.getMethod("allocateInstance", Class.class);
  return new ClassFactory<T>() {
    public T newInstance() throws InvocationTargetException, IllegalAccessException {
      return (T) allocateInstance.invoke(unsafe, rawType);
    }

    @Override
    public String toString() {
      return rawType.getName();
    }
  };
}
  1. 反射获取类的无参构造方法,通过无参构造方法创建对象;
  2. 如果没有无参数的构造方法,则反射获取 Unsafe 实例,调用其 allocateInstance 创建对象;
  3. 整体过Gson比较类似;

反序列化Kotlin对象

由 moshi-kotlin 模块的 KotlinJsonAdapter 完成,通常需要在初始化 Moshing 的时候手动添加 KotlinJsonAdapterFactory

尝试从@JsonClass注解生成的JsonAdapter中查找匹配的

  1. 判断是否含有 JsonClass 注解,并且设置了 generateAdapter = true;
  2. 根据类型名拼JsonAdapter字符串得到此类型生成的 JsonAdapter 类型名称,eg:UserInfoJsonAdapter;
  3. Class.formName加载对应的JsonAdapter,反射构造方法创建对象并返回;
Java 复制代码
public static @Nullable JsonAdapter<?> generatedAdapter(
    Moshi moshi, Type type, Class<?> rawType) {
  JsonClass jsonClass = rawType.getAnnotation(JsonClass.class);
  // 包含注解并且 generateAdapter 设置为 true
  if (jsonClass == null || !jsonClass.generateAdapter()) {
    return null;
  }
  // 生成类型的名称
  String adapterClassName = Types.generatedJsonAdapterName(rawType.getName());
  Class<? extends JsonAdapter<?>> adapterClass = null;
  try {
  // 反射获取类型
    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 {
        // Common case first
        constructor = adapterClass.getDeclaredConstructor(Moshi.class, Type[].class);
        args = new Object[] {moshi, typeArgs};
      } catch (NoSuchMethodException e) {
        constructor = adapterClass.getDeclaredConstructor(Type[].class);
        args = new Object[] {typeArgs};
      }
    } else {
      try {
        // Common case first
        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();
  }
  // catch 逻辑省略

如果没有对应的 JsonAdapter,则通过 Kotlin 的反射生成对象

  1. 获取 kotlin 主构造方法及其参数列表
  2. 遍历 class 的成员列表,查找每个成员的 JsonAdapter
  3. 记录每个成员的名称,对应的 JsonAdapter,是否是构造方法参数,index信息
  4. 根据收集的信息创建 KotlinJsonAdapter
  5. KotlinJsonAdapter 中读取 json 数据,根据字段是否有值,是否有默认值,是否可能等条件调用不同的构造方法,完成初始化,
  6. 存在默认参数的时候,kotlin内部会生成一个构造方法,在方法内部判断如果默认值参数没传递则使用默认值。
Java 复制代码
// KotlinJsonAdapter
override fun fromJson(reader: JsonReader): T {
  val constructorSize = constructor.parameters.size

  // Read each value into its slot in the array.
  val values = Array<Any?>(allBindings.size) { ABSENT_VALUE }
  reader.beginObject()
  while (reader.hasNext()) {
    val index = reader.selectName(options)
    if (index == -1) {
      reader.skipName()
      reader.skipValue()
      continue
    }
    val binding = nonIgnoredBindings[index]

    val propertyIndex = binding.propertyIndex
    if (values[propertyIndex] !== ABSENT_VALUE) {
      throw JsonDataException(
        "Multiple values for '${binding.property.name}' at ${reader.path}"
      )
    }
    // 保存从json中解析出来的属性值
    values[propertyIndex] = binding.adapter.fromJson(reader)

    reader.endObject()

  // Confirm all parameters are present, optional, or nullable.
  // Class 成员数量 == 构造方法参数数量
  var isFullInitialized = allBindings.size == constructorSize
  for (i in 0 until constructorSize) {
    // 构造函数参数成员没有从 json 中解析出值
    if (values[i] === ABSENT_VALUE) {
      when {
        // 可选的,存在默认值
        constructor.parameters[i].isOptional -> isFullInitialized = false
        // 可空的,直接设置为null
        constructor.parameters[i].type.isMarkedNullable -> values[i] = null
        // 异常
        else -> throw Util.missingProperty(
          constructor.parameters[i].name,
          allBindings[i]?.jsonName,
          reader
        )
      }
    }
  }

  val result = if (isFullInitialized) {
    // 调用默认构造方法
    constructor.call(*values)
  } else {
    // 调用生成的构造方法(带一个mask参数和一个mark参数,内部做了默认参数的赋值工作)
    constructor.callBy(IndexedParameterMap(constructor.parameters, values))
  }

  // Set remaining properties.
  for (i in constructorSize until allBindings.size) {
    val binding = allBindings[i]!!
    val value = values[i]
    binding.set(result, value)
  }

  return result
}

和Gson比较

  • Moshi 对 Kotlin 支持更好,data class、默认值、空安全
  • data class 没有默认的无参构造方法,反序列化的时候通过 Unsfae 的方式去创建的实例对象,导致默认值的初始化以及非空值的断言都没有执行
相关推荐
REDcker20 小时前
Android WebView 版本升级方案详解
android·音视频·实时音视频·webview·js·编解码
麦兜*20 小时前
【springboot】图文详解Spring Boot自动配置原理:为什么@SpringBootApplication是核心?
android·java·spring boot·spring·spring cloud·tomcat
le16161620 小时前
Android 依赖种类及区别:远程仓库依赖、打包依赖、模块依赖、本地仓库依赖
android
lxysbly20 小时前
psp模拟器安卓版带金手指
android
云上凯歌21 小时前
02 Spring Boot企业级配置详解
android·spring boot·后端
hqiangtai21 小时前
Android 高级专家技术能力图谱
android·职场和发展
aqi0021 小时前
FFmpeg开发笔记(九十七)国产的开源视频剪辑工具AndroidVideoEditor
android·ffmpeg·音视频·直播·流媒体
stevenzqzq21 小时前
Android Koin 注入入门教程
android·kotlin
炼金术1 天前
SkyPlayer v1.1.0 - 在线视频播放功能更新
android·ffmpeg
用户276038157811 天前
鲲鹏+昇腾:开启 AI for Science 新范式——基于PINN的流体仿真加速实践
android