在Scala中访问注释值

时间:2014-04-13 18:52:13

标签: scala reflection

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

相关

我认为我想要的是#{3}}中的"Reflection Overview"部分&#34;在运行时实例化类型&#34;但我不明白为什么我必须跳过这么多箍才能获得注释中的值。在Java中,价值只是一个抛弃。

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

4 个答案:

答案 0 :(得分:6)

不是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
}

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

答案 1 :(得分:5)

在他们当前的形式中,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

答案 2 :(得分:0)

不必依赖scala编译器,这是我的版本:

  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编译器,那么我们可以做类似的事情:

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
  }

答案 3 :(得分:0)

对于Scala 2.13,获取批注的最干净方法是使用ClassTag

import scala.reflect.ClassTag

class [T]MyClass(...)(implicit tag: ClassTag[T]) {
  val a = tag.runtimeClass.getAnnotations.collect{ case x: MyAnnotation => x }
}

这将为对象提供MyAnnotation类型的列表注释。