通过反射获取案例类字段的麻烦

问题描述

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

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
}

我希望特征DBModel读取作为T参数传递的案例类的字段,所以我调用了两个函数

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
  }

但它们都不起作用

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

解决方法

您缺少WeakTypeTag的类型类getClassFields

此外,您没有在特征DBModel[T <: Model]中传递类型类。

一种解决方案是将它们定义为类型T的上下文边界。但是要使其正常工作,需要通过构造函数传递这些类型类。而且由于特征没有构造函数,解决方案是使用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