【问题标题】:Accessing an Annotation Value in Scala在 Scala 中访问注解值
【发布时间】:2019-07-03 20:57:21
【问题描述】:

TL;DR:基本上,我正在寻找 Java 的 Scala 等价物:

(MyAnnotation) Thing.getClass().getAnnotations()[0]

尽管我可以愉快地发现注释并根据它们的类型进行查询,但我似乎无法从 scala.reflect.runtime.universe.Annotation 获取我的实际类型。

scala> // Declare an annotation (it seems StaticAnnotation means runtime
scala> // retention)
scala> case class MyAnnotation(x: Int, y: String) extends scala.annotation.StaticAnnotation
defined class MyAnnotation

scala> // Make a thing decorated with MyAnnotation
scala> @MyAnnotation(x=5, y="cool") case class Thing()
defined class Thing

scala> // Look at the annotation on the Thing...the runtime clearly
scala> // understands the values on it
scala> val annotation = scala.reflect.runtime.universe.typeOf[Thing].typeSymbol.asClass.annotations(0)
annotation: reflect.runtime.universe.Annotation = MyAnnotation(5, "cool")

scala> // I can sort of get at the values by index, which isn't terribly
scala> // safe
scala> annotation.scalaArgs(0)
res0: reflect.runtime.universe.Tree = 5

scala> // And what is a Tree here anyway? It certainly isn't a String (or
scala> // Int). I just want the value!
scala> annotation.scalaArgs(1)
res1: reflect.runtime.universe.Tree = "cool"

scala> // But how do I get at those values programatically?
scala> annotation.asInstanceOf[MyAnnotation]
java.lang.ClassCastException: scala.reflect.internal.AnnotationInfos$CompleteAnnotationInfo cannot be cast to MyAnnotation
        at .<init>(<console>:13)
        at .<clinit>(<console>)
        at .<init>(<console>:7)
        at .<clinit>(<console>)
        at $print(<console>)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:606)
        at scala.tools.nsc.interpreter.IMain$ReadEvalPrint.call(IMain.scala:734)
        at scala.tools.nsc.interpreter.IMain$Request.loadAndRun(IMain.scala:983)
        at scala.tools.nsc.interpreter.IMain.loadAndRunReq$1(IMain.scala:573)
        at scala.tools.nsc.interpreter.IMain.interpret(IMain.scala:604)
        at scala.tools.nsc.interpreter.IMain.interpret(IMain.scala:568)
        at scala.tools.nsc.interpreter.ILoop.reallyInterpret$1(ILoop.scala:760)
        at scala.tools.nsc.interpreter.ILoop.interpretStartingWith(ILoop.scala:805)
        at scala.tools.nsc.interpreter.ILoop.command(ILoop.scala:717)
        at scala.tools.nsc.interpreter.ILoop.processLine$1(ILoop.scala:581)
        at scala.tools.nsc.interpreter.ILoop.innerLoop$1(ILoop.scala:588)
        at scala.tools.nsc.interpreter.ILoop.loop(ILoop.scala:591)
        at scala.tools.nsc.interpreter.ILoop$$anonfun$process$1.apply$mcZ$sp(ILoop.scala:882)
        at scala.tools.nsc.interpreter.ILoop$$anonfun$process$1.apply(ILoop.scala:837)
        at scala.tools.nsc.interpreter.ILoop$$anonfun$process$1.apply(ILoop.scala:837)
        at scala.tools.nsc.util.ScalaClassLoader$.savingContextLoader(ScalaClassLoader.scala:135)
        at scala.tools.nsc.interpreter.ILoop.process(ILoop.scala:837)
        at scala.tools.nsc.MainGenericRunner.runTarget$1(MainGenericRunner.scala:83)
        at scala.tools.nsc.MainGenericRunner.process(MainGenericRunner.scala:96)
        at scala.tools.nsc.MainGenericRunner$.main(MainGenericRunner.scala:105)
        at scala.tools.nsc.MainGenericRunner.main(MainGenericRunner.scala)

这一切的乐趣在于,即使我愿意,我什至不能使用传统的 Java 方法,因为 Scala 不再费心从 getAnnotations() 填充数组。

scala> Thing.getClass.getAnnotations.length
res2: Int = 0

相关

我认为我想要的是在“在运行时实例化类型”部分中的"Reflection Overview",但我不明白为什么我必须跳过这么多圈才能获得注释中的值.在 Java 中,值只是被抛弃了。

"See annotations in Scala reflection" 的问题似乎相关,但问题来自 2011 年,适用于 Scala 2.9。我使用的是 2.10,据我所知,从那时起,反射的工作方式发生了很大变化。

【问题讨论】:

    标签: scala reflection


    【解决方案1】:

    在目前的形式中,Scala 注释尝试将 Java 兼容性(这意味着仅常量参数和注释中允许的语言结构数量非常有限)和终极灵活性(意味着允许在注释中想象的任何东西)结合起来。

    这体现在 ClassfileAnnotation(兼容性)与 StaticAnnotation(灵活性)的区别上。根据这个想法,可以在 Java 风格的反射和有限的注解作为对象和 Scala 风格的反射之间选择,完全灵活的注解只能作为抽象语法树使用(请注意,我们不能自动将静态注解转换为运行时对象,因为存储在此类注释中的代码可能包含任意 Scala 表达式,这使得评估它们非常困难)。

    不幸的是,由于类文件注释不支持运行时保留这一事实打破了这种理想主义的图景:https://issues.scala-lang.org/browse/SI-32,这意味着实际上无法选择 - 目前仅支持 Scala 风格的反射。希望有一天我们能修复 SI-32,但我不知道有任何持续的努力让它发挥作用。

    几个月前,我想实现类似scala.reflect.Annotation.eval 的东西,它会采用Scala 风格的注释并在可能的情况下对其进行评估。然而,在我们的一次反射会议上讨论后,我们决定反对它,因为除了不幸的非通用性之外,这个 API 还会给编译时反射(在 Scala 中与运行时反射统一)带来麻烦。

    这已在https://issues.scala-lang.org/browse/SI-6423 记录为问题,也在https://groups.google.com/forum/#!topic/scala-internals/8v2UL-LR9yY 进行了讨论,但目前没有具体的改进计划。希望 Project Palladium 能在这方面有所帮助,因为它的核心组件之一是 Scala 解释器,它将提供必要的通用性来支持 Annotation.eval

    【讨论】:

    • 感谢您的信息。我想这里的解决方法是在 Java 中定义一个 @interface 并从那里开始。可惜……新的 Scala 属性看起来比它们的 Java 对应物强大得多。
    • 好吧,如果您更喜欢 Scala 注释而不是 Java,也许您会对自己编写 Annotation.eval 感兴趣?对于常量参数的情况,这应该不是很复杂。
    • 我很乐意,但这需要超越我自己的 Scala 技能:-)
    【解决方案2】:

    不是 java 等价物,但在 scala 2.11.6 中,这可以从注释中提取值:

    case class MyAnnotationClass(id: String) extends scala.annotation.StaticAnnotation
    
    val myAnnotatedClass: ClassSymbol = u.runtimeMirror(Thread.currentThread().getContextClassLoader).staticClass("MyAnnotatedClass")
    val annotation: Option[Annotation] = myAnnotatedClass.annotations.find(_.tree.tpe =:= u.typeOf[MyAnnotationClass])
    val result = annotation.flatMap { a =>
      a.tree.children.tail.collect({ case Literal(Constant(id: String)) => DoSomething(id) }).headOption
    }
    

    我知道您可能已经这样做了,但以防万一它可以帮助某人:)

    【讨论】:

    • 感谢您提供此解决方案。您能否建议如何提取 labmda 而不是常量,然后将其用于计算?
    • 什么是u?你能举个合适的例子吗?
    • 谢谢你,它可以工作,如果你只需要 value ,你可以这样改变它: val result = annotation.flatMap { a => a.tree.children.tail.head.collect({ case Literal(Constant(value)) => value }).headOption }lazy val max = result.get.value.asInstanceOf[Int]
    【解决方案3】:

    无需依赖 scala-compiler,这是我的版本:

      def fetchAnnotations[T <: Annotation](cls : Class[_]) : List[T]= {
        import scala.reflect.runtime.universe._
        val mirror = runtimeMirror(cls.getClassLoader)
        val clsSymbol = mirror.staticClass(cls.getCanonicalName)
        val annotations = clsSymbol.annotations
    
        val res = ListBuffer[T]()
        for(annt : Annotation <- annotations) {
          val anntCls = annt.tree.tpe.typeSymbol.asClass
          val classMirror = mirror.reflectClass(anntCls);
          val anntType = annt.tree.tpe
          val constructor = anntType.decl(termNames.CONSTRUCTOR).asMethod;
          val constructorMirror = classMirror.reflectConstructor(constructor);
    
          val instance = annt.tree match {
            case Apply(c, args : List[Tree])   =>
              val res = args.collect({
                case i: Tree =>
                  i match {
                    case Literal(Constant(value)) =>
                      value
                  }
              })
              constructorMirror(res: _*).asInstanceOf[T]
          }
    
          res+=(instance)
        }
        res.toList
      }
    

    只有当我怀疑参数是原始参数时,前面的代码才有效。

    如果我们能负担得起依赖 scala-compiler,那么我们可以这样做:

    def fetchAnnotations_toolBox[T <: Annotation](cls : Class[_]) : List[T]= {
        import scala.reflect.runtime.universe._
        val mirror = runtimeMirror(cls.getClassLoader)
        val clsSymbol = mirror.staticClass(cls.getCanonicalName)
        val annotations = clsSymbol.annotations
    
        val res = ListBuffer[T]()
        for(annt : Annotation <- annotations) {
          import scala.tools.reflect.ToolBox
    
          val toolbox = mirror.mkToolBox()
          val instance = toolbox.eval(toolbox.untypecheck(toolbox.typecheck(annt.tree))).asInstanceOf[T]
          res+=(instance)
        }
        res.toList
      }
    

    【讨论】:

      猜你喜欢
      • 2021-05-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-11-24
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多