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

标签 reflection kotlin annotations jvm

我使用 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”):
    data class MyDataClass(
        @get:PropertyName("z") val x: Int
    )
    

    注释现在显示在 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_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())
        }
    }
    

    最佳答案

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

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

    如果要注释 Kotlin 类属性,并通过 KClass 反射找到它,则必须使用 PROPERTY 类型注释,这是 Kotlin 独有的。有了这个,如果您在 members 中找到该属性(property)KClass 列表,它将具有该注释(但不是底层支持字段!)。

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

    在更一般的意义上,Java 定义的注解类型 只有适用于Java Class反射,而Kotlin扩展的注解类型只有适用于 KClass 反射。 Kotlin 编译器将禁止您在 Java 元素上使用 Kotlin 特定的注释类型。这里的异常(exception)是,它允许您将 Java 注释类型应用于“归结”为 Java 本地概念的 Kotlin 概念(具有支持字段的属性)。 (FWIW,如果您将 Java 原生注释代码复制到 Kotlin 中并让它自动转换,如果不考虑这一点,转换可能没有意义。)

    如果您最喜欢的 Java 库仅公开适用于 Java 层概念的注解,请考虑要求他们提供 Kotlin 扩展,以帮助您在更纯粹的 Kotlin 级别处理他们的注解。尽管在 Java 代码中使用这可能会很棘手。

    有人请更新文档。 :-)

    关于reflection - 如何在 Kotlin 数据类字段上列出(java)注释?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53843771/

    相关文章:

    kotlin - 父类中 protected 内联方法无法访问其他 protected 方法

    kotlin - 我可以省略在 Kotlin 中不使用的接口(interface)方法吗?

    java - 如何从 Java 中的常量向注解提供枚举值

    c# - 如何获取实现给定接口(interface)的所有加载类型的所有实例?

    Java 反射 : wait() method repeat thrice. 为什么?

    c# - 如何通过反射为参数的默认值调用方法

    java - 如何使用Spring编写注解触发的方法拦截器?

    scala - 包装 isInstanceOf[] 调用的正确方法是什么?

    kotlin - 给定范围内的安全异步

    java - 在单元测试中使用反射来测试注释的存在是否被认为是好的做法?