通过反射获取 case class 字段的问题

Troubles with getting case class fields via reflection

我有一个scala代码:特征模型及其实现案例class带有自定义注释的类别,我希望稍后阅读

import scala.annotation.StaticAnnotation
class ExtraFields() extends StaticAnnotation

trait Model {}

case class MyCategory( id: Option[Int],
                     name: String,
                     level: Option[Int],

                     @ExtraFields
                     parent: Option[MyCategory] = None
                   ) extends Model {

}

我有特征 DBModel 及其实现 MyCategoryModel。我将 MyCategory 作为类型参数传递给 DBModel

trait DBModel[T <: Model] {
  lazy val fields = getClassFields[T]
  lazy val fields2 = getClassFields2[T]
}

object MyCategoryModel extends DBModel[MyCategory] {
  def getFields = fields
  def getFields2 = fields2
}

我希望 trait DBModel 读取作为 T 参数传递的 case class 的字段,所以我调用了两个函数

def getClassFields[T] = {
  symbolOf[T].asClass.primaryConstructor
    .typeSignature.paramLists.head.map {v =>
      v.name.toString -> v.annotations
    }
}

def getClassFields2[T: TypeTag]: Iterable[(String, List[universe.Annotation])] =
  typeOf[T].members.collect {
    case m: MethodSymbol if m.isCaseAccessor =>
      m.name.toString -> m.annotations
  }

但其中 none 正在工作

val res = MyCategoryModel.getFields // runtime exception: free type T is not a class
val res2 = MyCategoryModel.getFields2 // compile error: No TypeTag available for T

如果我直接打电话给他们

getClassFields[MyCategory]
getClassFields2[MyCategory]

对于第一个函数,我得到了同样的错误,对于第二个函数,我得到了结果,但是 universe.annotations 看不到我的 ExtraFields 注释,为每个字段返回空列表

你能解释一下这个黑魔法以及如何在我的情况下征服它吗 谢谢

UPD 游乐场https://scastie.scala-lang.org/DTTTyA0CSTSZ0Vj7KFW6Hw

您缺少类型 class WeakTypeTag getClassFields

此外,您没有在特征 DBModel[T <: Model].

中传递类型 classes

一种解决方案是将它们定义为 T 类型的上下文边界。但要使其工作,需要通过构造函数传递这些类型 classes。而且因为 traits 没有构造函数,所以解决方案是使用 abstract class.

所以下面修复了编译问题

abstract class DBModel[T <: Model : WeakTypeTag : TypeTag] {
  lazy val fields = getClassFields[T]
  lazy val fields2 = getClassFields2[T]
}

def getClassFields[T: WeakTypeTag] = ???
def getClassFields2[T: TypeTag] = ???

由于指定注释的方式,方法 getClassFields2 未找到注释。如果定义如下,两种方法都有效。有关详细信息,请阅读 meta docs

  
import scala.annotation.meta.{getter, param}
case class MyCategory(
     id: Option[Int],
     name: String,
     level: Option[Int],
     @(ExtraFields @param @getter) parent: Option[MyCategory] = None
  ) extends Model