【问题标题】:Play framework and reading a scala annotation播放框架并阅读 Scala 注释
【发布时间】:2021-01-29 06:32:03
【问题描述】:

我正在努力从 Play 控制器方法中的 scala 注释中获取值。

我为注解定义了一个类:

case class Auth(perm: String) extends scala.annotation.StaticAnnotation

然后我在 Play 的一个过滤器中阅读它:

import scala.reflect.runtime.{universe => u}

val res = u.runtimeMirror(handlerDef.classLoader)
  .classSymbol(Class.forName(handlerDef.controller))
  .info
  .decls
  .find(_.name.toString == handlerDef.method)
  .flatMap(_.asMethod.annotations.find(_.tree.tpe =:= u.typeOf[Auth]))

现在,我得到Option[Annotation],当我println 它时,它是:Some(Auth("test")) - 我在注释中输入的值,所以一切都很好。

我不知道如何将Annotation 实际转换为我的Auth

感谢您的帮助,谢谢!

【问题讨论】:

    标签: scala reflection playframework annotations scala-reflect


    【解决方案1】:

    scala.reflect.api.Annotations 的 Scaladoc 说

    与 Java 反射不同,Scala 反射不支持求值 存储在注释中的构造函数调用到底层 对象。例如,不可能从 @ann(1, 2) class C 转到 ann(1, 2),所以必须分析表示注释的树 手动提取相应值的参数。为此, 注解的参数可以通过annotation.tree.children.tail获取。

    https://github.com/scala/scala/blob/2.13.x/src/reflect/scala/reflect/api/Annotations.scala#L39-L42

    您可以使用Toolbox 评估注释树

    // 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

    或者(例如,如果您只想依赖 scala-reflect 而不是 scala-compiler)您可以手动评估树:

    res.map(a => {
      val arg = a.tree.children.tail match { case List(q"${s: String}") => s }
      Auth(arg)
    }) // Some(Auth(some permission))
    

    res.map(_.tree match {
      case q"new ${t@TypeTree()}(${s: String})" /*if t.tpe == typeOf[Auth]*/ => Auth(s)
    }) // Some(Auth(some permission))
    

    请注意,树将不匹配模式q"new com.example.Auth(${s: String})",因为注释树具有不同的形状。

    顺便说一句,使用rm.staticClass(...) 代替rm.classSymbol(Class.forName(...)),您可以使用Scala 类名(例如org.example.App.MyClass)代替Java 类名(例如org.example.App$MyClass)。您也可以尝试scala.reflect.runtime.currentMirror 而不是u.runtimeMirror(classLoader)。另外.decls.find(_.name.toString == methodName) 可以替换为.decl(u.TermName(methodName))

    以防万一,如果您在编译时知道类和注解的类型以及方法名称,那么您可以使用编译时反射来做同样的事情

    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")
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-08-03
      • 2013-09-22
      • 2015-06-09
      • 2013-07-11
      • 2016-08-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多