簡體   English   中英

在 Scala 中訪問注釋值

[英]Accessing an Annotation Value in Scala

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

有關的

我想我想要的是 在運行時實例化類型 部分的 “反射概述” ,但我不明白為什么我必須跳過這么多圈子才能獲得注釋中的值。 在 Java 中,該值只是一個拋棄。

問題“在 Scala 反射中查看注釋”似乎相關,但問題來自 2011 年,適用於 Scala 2.9。 我正在使用 2.10,據我所知,從那時起,反射的工作方式發生了很大變化。

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

我知道你可能已經做過,但以防萬一它可以幫助別人:)

在他們當前的形式中,Scala注釋試圖結合Java兼容性(這意味着只有常量參數和注釋中允許的非常有限數量的語言結構)和最終的靈活性(這意味着允許在注釋中可以想象的任何東西)。

這表現在ClassfileAnnotation(兼容性)與StaticAnnotation(靈活性)的區別。 根據這個想法,可以選擇具有有限注釋的Java風格反射作為對象和具有完全靈活注釋的Scala風格反射僅作為抽象語法樹可用(注意我們不能自動將靜態注釋轉換為運行時對象) ,因為存儲在此類注釋中的代碼可能包含任意Scala表達式,這使得評估它們非常困難)。

不幸的是,這個理想化的圖片被classfile注釋不支持運行時保留這一事實打破了: 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必要通用性。

不必依賴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
  }

對於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的列表注釋。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM