Scaladoc of scala.reflect.api.Annotations
says
Unlike Java reflection, Scala reflection does not support evaluation
of constructor invocations stored in annotations into underlying
objects. For instance it's impossible to go from @ann(1, 2) class C
to
ann(1, 2)
, so one has to analyze trees representing annotation
arguments to manually extract corresponding values. Towards that end,
arguments of an annotation can be obtained via annotation.tree.children.tail
.
https://github.com/scala/scala/blob/2.13.x/src/reflect/scala/reflect/api/Annotations.scala#L39-L42
You can use Toolbox
to evaluate annotation tree
// libraryDependencies += scalaOrganization.value % "scala-compiler" % scalaVersion.value
import scala.tools.reflect.ToolBox
val rm = u.runtimeMirror(handlerDef.classLoader)
val tb = rm.mkToolBox()
res.map(a => tb.eval(tb.untypecheck(a.tree)).asInstanceOf[Auth]) // Some(Auth(some permission))
https://docs.scala-lang.org/overviews/reflection/symbols-trees-types.html#tree-creation-via-parse-on-toolboxes
Calling a method from Annotation using reflection
Or (e.g. if you want to depend on scala-reflect
only and not on scala-compiler
) you can evaluate the tree manually:
res.map(a => {
val arg = a.tree.children.tail match { case List(q"${s: String}") => s }
Auth(arg)
}) // Some(Auth(some permission))
or
res.map(_.tree match {
case q"new ${t@TypeTree()}(${s: String})" /*if t.tpe == typeOf[Auth]*/ => Auth(s)
}) // Some(Auth(some permission))
Notice that the tree will not match pattern q"new com.example.Auth(${s: String})"
because annotation trees have different shape.
By the way, with rm.staticClass(...)
instead of rm.classSymbol(Class.forName(...))
you can use Scala class name (e.g. org.example.App.MyClass
) instead of Java class name (e.g. org.example.App$MyClass
). Also you can try scala.reflect.runtime.currentMirror
instead of u.runtimeMirror(classLoader)
. Also .decls.find(_.name.toString == methodName)
can be replaced with .decl(u.TermName(methodName))
.
Just in case, if you know types of class and annotation and method name at compile time then you can do the same using compile-time reflection
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
def getMethodAnnotation[Cls, Ann](methodName: String): Ann = macro getMethodAnnotationImpl[Cls, Ann]
def getMethodAnnotationImpl[Cls: c.WeakTypeTag, Ann: c.WeakTypeTag](c: blackbox.Context)(methodName: c.Tree): c.Tree = {
import c.universe._
val q"${methodNameStr: String}" = methodName
weakTypeOf[Cls]
.decl(TermName(methodNameStr))
.annotations.find(_.tree.tpe =:= weakTypeOf[Ann]).get.tree match {
case q"new ${t: TypeTree}[..$targs](...$argss)" => q"new ${t.tpe}[..$targs](...$argss)"
}
}
class MyClass {
@Auth("some permission")
def myMethod(): Unit = ()
}
getMethodAnnotation[MyClass, Auth]("myMethod") //scalac: new Auth("some permission")