[英]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.