Dou*_*son 4 reflection jvm annotations kotlin
我使用 Firestore 的基于 Java 的注释来标记字段和将文档字段映射到 Java 类元素的方法:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD})
public @interface PropertyName {
String value();
}
Run Code Online (Sandbox Code Playgroud)
我在 Kotlin 数据类的一个字段上使用它,它编译得很好:
data class MyDataClass(
@PropertyName("z") val x: Int
)
Run Code Online (Sandbox Code Playgroud)
在 IntelliJ 和 Android Studio 中,我可以看到它出现在反编译的类转储中:
public final data class MyDataClass public constructor(x: kotlin.Int) {
@field:com.google.cloud.firestore.annotation.PropertyName public final val x: kotlin.Int /* compiled code */
public final operator fun component1(): kotlin.Int { /* compiled code */ }
}
Run Code Online (Sandbox Code Playgroud)
在这一点上,我的印象是这个注释应该可以通过 Kotlin 反射以某种方式被发现。据我所知,事实并非如此。我试过迭代注释:
它只是没有出现在任何地方。
当我像这样更改注释的用法时(注意现在的目标说明符“get”):
data class MyDataClass(
@get:PropertyName("z") val x: Int
)
Run Code Online (Sandbox Code Playgroud)
注释现在显示在 Java 类对象的生成的 getter 中。这至少在实践中是可行的,但我很好奇为什么 Kotlin 允许我将注释编译为针对字段的注释,但不允许我在运行时将其取回(除非我在kotlin-reflect API?)。
如果我改用这个基于 Kotlin 的注释:
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.PROPERTY)
annotation class PropertyName(val value: String)
Run Code Online (Sandbox Code Playgroud)
这样,注释就会在运行时显示在 Kotlin 字段上。这很奇怪,因为 Java 的 ElementType.FIELD 似乎并不能完美地映射到 Kotlin 的 AnnotationTarget.FIELD。
(顺便说一句,如果我将其更改为 AnnotationTarget.VALUE_PARAMETER,我也可以在数据类构造函数参数中发现此注释。)
这对我来说感觉像是一个错误,但我愿意看看我是否只是在这里做错了什么。或者,这可能只是不受支持。我正在使用 Kotlin 1.3.11。JVM 和 Android 上的行为相同。
查找注释的代码:
Log.d("@@@@@", "\n\nDump of $kclass")
val ctor = kclass.constructors.first()
Log.d("@@@@@", "Constructor parameters")
ctor.parameters.forEach { p ->
Log.d("@@@@@", p.toString())
Log.d("@@@@@", p.annotations.size.toString())
p.annotations.forEach { a ->
Log.d("@@@@@", " " + a.annotationClass)
}
}
Log.d("@@@@@", "kotlin functions")
kclass.functions.forEach { f ->
Log.d("@@@@@", f.toString())
if (f.annotations.isNotEmpty()) {
Log.d("@@@@@", "*** " + f.annotations.toString())
}
}
Log.d("@@@@@", "kotlin members")
kclass.members.forEach { f ->
Log.d("@@@@@", f.toString())
if (f.annotations.isNotEmpty()) {
Log.d("@@@@@", "*** " + f.annotations.toString())
}
}
Log.d("@@@@@", "kotlin declared functions")
kclass.declaredFunctions.forEach { f ->
Log.d("@@@@@", f.toString())
if (f.annotations.isNotEmpty()) {
Log.d("@@@@@", "*** " + f.annotations.toString())
}
}
val t = kclass.java
Log.d("@@@@@", "java constructors")
t.constructors.forEach { f ->
Log.d("@@@@@", f.toString())
}
Log.d("@@@@@", "java methods")
t.methods.forEach { f ->
Log.d("@@@@@", f.toString())
if (f.annotations.isNotEmpty()) {
Log.d("@@@@@", "*** " + f.annotations.toString())
}
}
Log.d("@@@@@", "java fields")
t.fields.forEach { f ->
Log.d("@@@@@", f.toString())
if (f.annotations.isNotEmpty()) {
Log.d("@@@@@", "*** " + f.annotations.toString())
}
}
Run Code Online (Sandbox Code Playgroud)
这里的问题是我的期望(可能还有文档)并没有让我为 Kotlin 编译器将如何处理各种类型的注释做好准备。我的假设是 Kotlin 数据类属性上的 FIELD 目标注释目标会将注释直接应用于 Kotlin 合成属性。这个假设是不正确的。
Kotlin 对合成属性上的 FIELD 注释所做的是将 FIELD 注释向下推送到生成的类文件中属性的实际支持字段。这意味着对带注释的 Kotlin 属性的任何类型的反射都不会找到该注释。 您必须深入到 Java Class 对象中才能找到它。
如果要注释 Kotlin 类属性,并通过 KClass 反射找到它,则必须使用 PROPERTY 类型注释,这是 Kotlin 独有的。有了这个,如果您在membersKClass 列表中找到该属性,它将具有该注释(但不是底层支持字段!)。
更进一步,对于 Kotlin 数据类,构造函数是定义类属性的最重要的东西。因此,如果您想在运行时通过反射创建数据类实例,最好通过其构造函数注释其属性。这意味着将具有 VALUE_PARAMETER 类型的注释应用于数据类构造函数属性,可以通过构造函数参数本身的反射来发现它们。
从更一般的意义上讲,Java 定义的注解类型只适用于 Java Class 反射,而 Kotlin 扩展的注解类型只适用于 KClass 反射。Kotlin 编译器将禁止您在 Java 元素上使用 Kotlin 特定的注释类型。这里的例外是,它允许您将 Java 注释类型应用于“归结”为 Java 本地概念的 Kotlin 概念(具有支持字段的属性)。(FWIW,如果您将 Java 原生注释代码复制到 Kotlin 中并让它自动转换,如果不考虑这一点,转换可能没有意义。)
如果您最喜欢的 Java 库仅公开适用于 Java 层概念的注解,请考虑要求他们提供 Kotlin 扩展,以帮助您在更纯粹的 Kotlin 级别处理他们的注解。尽管在 Java 代码中使用这可能会很棘手。
有人请更新文档。:-)
虽然我在 Kotlin 中找不到它KClass,但我可以在 Java 中找到它。
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FIELD)
annotation class PropertyName(val value: String)
data class MyDataClass(
@PropertyName("z") val x: Int
)
Run Code Online (Sandbox Code Playgroud)
我使用下面的代码
val a = MyDataClass(1)
a::class.java.declaredFields.forEach {
it.annotations.forEach { annotation ->
Log.e(it.name, annotation.toString())
}
}
Run Code Online (Sandbox Code Playgroud)
它打印
2018-12-19 11:33:07.663 25318-25318/com.example.application E/x: @com.example.PropertyName(value=z)
| 归档时间: |
|
| 查看次数: |
5156 次 |
| 最近记录: |