Reflection 如何在Kotlin数据类字段上列出(java)注释?

Reflection 如何在Kotlin数据类字段上列出(java)注释?,reflection,kotlin,annotations,jvm,Reflection,Kotlin,Annotations,Jvm,我使用Firestore基于Java的注释标记字段,并使用方法将文档字段映射到Java类元素: @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.FIELD}) public @interface PropertyName { String value(); } 我在Kotlin数据类中的一个字段上使用它,它编译得很好: data class MyDataClass( @Prop

我使用Firestore基于Java的注释标记字段,并使用方法将文档字段映射到Java类元素:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD})
public @interface PropertyName {
  String value();
}
我在Kotlin数据类中的一个字段上使用它,它编译得很好:

data class MyDataClass(
    @PropertyName("z") val x: Int
)
在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 */ }
}
我在这一点上的印象是,这个注释应该通过Kotlin反射以某种方式被发现。据我所知,情况并非如此。我已尝试在以下对象上迭代注释:

  • 每个Kotlin数据类构造函数字段
  • 每个Kotlin字段
  • 每个Kotlin函数
  • 每个Java构造函数
  • 每个Java字段
  • 每个Java方法
  • 它只是没有出现在任何地方

    当我像这样更改注释的用法时(注意目标说明符“get”now):

    注释现在显示在Java类对象的生成getter中。这至少在实践中是可行的,但我很好奇,为什么Kotlin允许我将注释编译为以字段为目标的注释,但不允许我在运行时将其返回(除非我在Kotlin reflect API中遗漏了什么?)

    如果改用基于Kotlin的注释:

    @Retention(AnnotationRetention.RUNTIME)
    @Target(AnnotationTarget.PROPERTY)
    annotation class PropertyName(val value: String)
    
    这样,注释将在运行时显示在Kotlin字段上。这很奇怪,因为Java的ElementType.FIELD似乎并没有完全映射到Kotlin的AnnotationTarget.FIELD

    (顺便说一句,如果我将其更改为AnnotationTarget.VALUE_参数,我还可以在数据类构造函数参数中发现此注释。)

    这对我来说就像一个虫子,但我愿意看看我是不是做错了什么。或者这只是不被支持。我使用的是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())
        }
    }
    

    虽然我可以在Kotlin
    KClass
    中找到它,但我可以在Java中找到它

    @Retention(AnnotationRetention.RUNTIME)
    @Target(AnnotationTarget.FIELD)
    annotation class PropertyName(val value: String)
    
    data class MyDataClass(
        @PropertyName("z") val x: Int
    )
    
    我使用下面的代码

    val a = MyDataClass(1)
    a::class.java.declaredFields.forEach {
        it.annotations.forEach { annotation ->
            Log.e(it.name, annotation.toString())
        }
    }
    
    它打印

    2018-12-19 11:33:07.663 25318-25318/com.example.application E/x:@com.example.PropertyName(value=z)


    这里的问题是,我的期望(可能还有文档)没有让我准备好Kotlin编译器将如何处理各种类型的注释。我的假设是,Kotlin数据类属性上的字段目标注释目标将直接将注释应用于Kotlin合成属性。这一假设是不正确的

    Kotlin对合成属性上的字段注释所做的是将字段注释向下推到生成的类文件中属性的实际值。这意味着对注释的Kotlin属性的任何类型的反射都不会找到注释您必须深入Java类对象才能找到它。

    如果要注释Kotlin类属性,并通过KClass反射找到它,则必须使用Kotlin特有的属性类型注释。这样,如果您在KClass的
    members
    列表中找到该属性,它将具有该注释(但不具有基础支持字段!)

    更进一步,对于Kotlin数据类,构造函数是定义类属性的最重要的东西。因此,如果希望在运行时通过反射创建数据类实例,最好通过其构造函数对其属性进行注释。这意味着对数据类构造函数属性应用带有VALUE_参数类型的注释,可以通过构造函数参数本身的反射来发现这些属性

    从更一般的意义上讲,Java定义的注释类型仅适用于Java类反射,而Kotlin扩展的注释类型仅适用于KClass反射。Kotlin编译器将禁止您在Java元素上使用特定于Kotlin的注释类型。这里的例外是,它允许您将Java注释类型应用于Kotlin概念(带有支持字段的属性),这些概念“归结”为Java本机概念。(FWIW,如果您将Java本机注释代码复制到Kotlin中并使其自动转换,那么如果不考虑这一点,转换可能没有意义。)

    如果您最喜欢的java库只公开适用于java层概念的注释,请考虑它们提供Kotlin扩展,帮助您在更纯Kotlin级别上使用注释。虽然这在Java代码中可能很难使用


    请有人更新文档。:-)

    我想使用开头显示的原始Firestore PropertyName注释,而不是我在Kotlin中为调试而修改的注释。我不想介绍一个新的。如果我的问题不清楚,我不明白为什么我根本找不到原件。@DougStevenson我下载了
    com.google.firebase:firebase数据库:16.0.5
    ,它显示
    2018-12-19 12:16:16.664 25648-25648/com.example.application E/x:@com.google.firebase.database.PropertyName(value=z)
    对于上述代码,我必须指出,您显示的PropertyName注释与我在问题中显示的不同。Firestore SDK不是用Kotlin编写的,也没有Kotlin语法注释类。此外,Firestore与实时数据库不同。firebase数据库工件用于实时数据库,而不是Firestore。请使用我在问题中展示的内容。@DougStevenson请说明您使用的确切软件包firebase软件包。你的意思是
    com.google.firebase:firebase admin:6.6.0
    ?JVM和Android上的firebase firestore都是最新版本。就注释而言,它们基本上是相同的,并且在不同版本之间没有变化。我的目标是自动将Firestore文档字段映射到数据类中。即使注释出现在java声明的字段中,我还是希望通过更标准的Kotlin来理解
    val a = MyDataClass(1)
    a::class.java.declaredFields.forEach {
        it.annotations.forEach { annotation ->
            Log.e(it.name, annotation.toString())
        }
    }