Moshi 的 Kotlin 代码生成器有啥用?
Posted
技术标签:
【中文标题】Moshi 的 Kotlin 代码生成器有啥用?【英文标题】:What's the use of Moshi's Kotlin codegen?Moshi 的 Kotlin 代码生成器有什么用? 【发布时间】:2020-02-18 11:09:50 【问题描述】:我最近开始在我的 android 应用中使用 Moshi,我很想知道更多关于 @JsonClass(generateAdapter = true)
注释 真正 的作用。
示例数据类:
data class Person(
val name: String
)
我可以对这个类进行序列化/反序列化,如下所示:
val moshi: Moshi = Moshi.Builder().build()
moshi.adapter(Person::class.java).toJson(Person())
我不在此处使用 @JsonClass 注释,因此 codegen 不会启动。
我的问题是,为什么以及何时需要使用@JsonClass(generateAdapter = true)
【问题讨论】:
【参考方案1】:这是三个问题
-
为什么代码生成很有用
代码生成可用作反射式 moshi-kotlin 的编译时替代方案。它们都很有用,因为它们本身就理解 Kotlin 代码及其语言特性。没有它们,Moshi 将无法理解 Kotlin 可空性、默认值等等。在某些情况下,Moshi 恰好与标准 Java 反射一起工作,您上面的示例就是其中之一。不过这很容易出错,在 Moshi 1.9 中,这些将被拒绝,并且需要生成的适配器或 kotlin-reflect。
-
它是如何工作的
Code gen 是一个注解处理器,用于查找使用@JsonClass(generateAdapter = true)
注解的类。它为每个带注释的类生成一个优化的流适配器。这些适配器本身就是 Kotlin,因此能够利用支持目标类的 Kotlin 语言特性。在运行时,Moshi 使用非常简单的已知名称后缀反射性地查找生成的适配器,这允许这些适配器无需手动注册适配器即可正常工作。
您可以在我发布的博客文章中找到关于 1 和 2 的更多信息:https://www.zacsweers.dev/exploring-moshis-kotlin-code-gen/
-
什么时候使用
当您尝试使用 Moshi 序列化 Kotlin 类而不使用您自己的自定义适配器时,您应该使用 moshi-kotlin 或 code gen。反射将没有构建时间开销,但在运行时会慢得多,同时由于 kotlin-reflect 还会产生较大的二进制大小成本,并且无法安全地进行混淆。代码生成会产生构建时间成本,但在运行时速度非常快,二进制大小成本最小,而且大部分是混淆安全的。由您决定哪一个更适合您的用例!您还可以组合使用,例如调试构建中的反射和仅用于发布构建的代码生成。
【讨论】:
谢谢!我有一个问题 wrt @user2340612 的回答:如果我理解正确,使用@JsonClass(generateAdapter = true)
基本上可以避免“ClassJsonAdapter”,从而避免反射?
对。它向 Moshi 表明应该使用生成的适配器。 Moshi 代码生成注释处理器也使用它来指示哪些类应该为它们生成适配器。
@ZacSweers 只是想知道您答案的第三部分。它也有助于de-serialize
json 对吗?【参考方案2】:
早期版本的 Moshi 不支持“codegen”,因此它们完全依赖于反射(即在运行时内省类的能力)。但是,对于需要非常高性能的应用程序来说,这可能是一个问题,因此他们添加了利用注释处理的“代码生成”功能。基本上这允许在编译时生成代码,因此它们可以在不使用反射的情况下执行序列化/反序列化。
为了启用代码生成功能,您还需要启用 Kapt,它是 Kotlin 注释处理器,否则不会发生注释处理。
Here 您将了解如何启用和配置 Kapt,here 您将了解如何设置 Moshi 代码生成依赖项。
编辑
在您添加到问题的代码的 sn-p 中,Moshi 使用 ClassJsonAdapter 序列化您的对象。该适配器利用反射来查找所有字段并创建 JSON 字符串(您可以看到该类从 java.lang.reflect
导入内容)。
另一方面,Moshi 代码生成为您的类生成一个 JsonAdapter。例如,让 Moshi 为您的 Person
类创建适配器将生成以下内容:
// Code generated by moshi-kotlin-codegen. Do not edit.
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonDataException
import com.squareup.moshi.JsonReader
import com.squareup.moshi.JsonWriter
import com.squareup.moshi.Moshi
import java.lang.NullPointerException
import kotlin.String
class PersonJsonAdapter(moshi: Moshi) : JsonAdapter<Person>()
private val options: JsonReader.Options = JsonReader.Options.of("name")
private val stringAdapter: JsonAdapter<String> =
moshi.adapter<String>(String::class.java, kotlin.collections.emptySet(), "name")
override fun toString(): String = "GeneratedJsonAdapter(Person)"
override fun fromJson(reader: JsonReader): Person
var name: String? = null
reader.beginObject()
while (reader.hasNext())
when (reader.selectName(options))
0 -> name = stringAdapter.fromJson(reader) ?: throw JsonDataException("Non-null value 'name' was null at $reader.path")
-1 ->
// Unknown name, skip it.
reader.skipName()
reader.skipValue()
reader.endObject()
var result = Person(
name = name ?: throw JsonDataException("Required property 'name' missing at $reader.path"))
return result
override fun toJson(writer: JsonWriter, value: Person?)
if (value == null)
throw NullPointerException("value was null! Wrap in .nullSafe() to write nullable values.")
writer.beginObject()
writer.name("name")
stringAdapter.toJson(writer, value.name)
writer.endObject()
因此,当您请求Person
的适配器时,Moshi 将使用生成的PersonJsonAdapter
而不是ClassJsonAdapter
。
奖金
Moshi uses reflection to instantiate the generated adapter(然后被缓存,以便下次您请求同一类的适配器时可以重用它),因此如果您想使用,则根本不需要添加任何额外的代码代码生成功能。
【讨论】:
Json 序列化/反序列化无需反射和代码生成即可工作。我的问题是代码生成的优势或真正用途是什么? @JigneshShah 请检查我更新的答案。我希望现在更清楚了:) 感谢您非常详细的回复!如果我理解正确,使用@JsonClass(generateAdapter = true)
基本上可以避免“ClassJsonAdapter”,从而避免反射?
@JigneshShah 基本上是的,但仅适用于带有 @JsonClass(generateAdapter=true)
注释的类。换句话说,如果你有 3 个类并且只有 1 个有那个注解,那么 Moshi 将只为后者生成一个适配器,而其他 2 个类将使用反射进行序列化/反序列化
@JigneshShah 不,codegen 只会为带注释的类生成适配器,因此您需要手动注释内部和/或其他引用的类以上是关于Moshi 的 Kotlin 代码生成器有啥用?的主要内容,如果未能解决你的问题,请参考以下文章
当我们在webservice.asmx.cs中编写所有代码时,webservice.asmx有啥用?