Как получить аннотации метода в Scala 2.11

Предположим, объект контроллера выглядит следующим образом:

object Users extends Controller {

  ...

  @ApiOperation(
    httpMethod = "POST",
    nickname = "authenticate",
    value = "Authenticates an user",
    notes = "Returns the JSON Web Token to be used in any subsequent request",
    response = classOf[models.auth.api.Jwt])
  def authenticate = SecuredAction[Users.type]("authenticate").async(parse.json) { implicit request =>
    ...
  }

  ...
}

Как получить значения аннотаций метода authenticate во время выполнения? Я пробовал это:

def methodAnnotations[T: TypeTag]: Map[String, Map[String, Map[String, JavaArgument]]] = {
  typeTag[T].tpe.declarations.collect { case m: MethodSymbol => m }.map { m =>
    val methodName = m.name.toString
    val annotations =  m.annotations.map { a =>
      val annotationName = a.tpe.typeSymbol.name.toString
      val annotationArgs = a.javaArgs.map {
        case (name, value) => name.toString -> value
      }
      annotationName -> annotationArgs
    }.toMap
    methodName -> annotations
  }.toMap
}   

methodAnnotations возвращает указанную аннотацию для указанного метода и вызывается следующим образом:

val mAnnotations = methodAnnotations[T]
val nickname = mAnnotations("myMethodName")("MyAnnotationName")("myAnnotationMemberName").asInstanceOf[LiteralArgument].value.value.asInstanceOf[String]

Проблема в том, что когда я компилирую приведенный выше код, я всегда получаю следующие предупреждения:

type JavaArgument in trait Annotations is deprecated: Use `Annotation.tree` to inspect annotation arguments
method tpe in trait AnnotationApi is deprecated: Use `tree.tpe` instead

Как правильно получить аннотации методов с помощью scala 2.11?


person j3d    schedule 12.11.2014    source источник


Ответы (2)


Если вы можете справиться с использованием Джексона, я бы повторно использовал его функции обработки аннотаций вместо использования отражения Scala.

object Test {
  import com.fasterxml.jackson.databind.introspect.{AnnotatedClass, JacksonAnnotationIntrospector}

  @ApiOperation(
    httpMethod = "POST",
    nickname = "authenticate",
    value = "Authenticates an user",
    notes = "Returns the JSON Web Token to be used in any subsequent request",
    response = classOf[models.auth.api.Jwt])
  def hasAnnotation() {}

  def main(args: Array[String]): Unit = {
    import scala.collection.JavaConversions._

    val introspector = new JacksonAnnotationIntrospector
    val ac = AnnotatedClass.construct(Test.getClass, introspector, null)
    for (method <- ac.memberMethods()) {
      val annotation = method.getAnnotation(classOf[ApiOperation])
      if (annotation != null) {
        println(s"${method.getFullName} -> ${annotation.nickname()}")
      }
    }
  }
}
person Nate    schedule 12.11.2014
comment
Я попробую... но могу я спросить, почему Джексон лучше, чем отражение Скала? просто чтобы быть в курсе ;-) - person j3d; 13.11.2014
comment
@ j3d Jackson широко используется, надежен, быстр и чист. Итак... почему бы и нет, если он легко справляется со своей задачей =)? Кроме того, API отражения Scala не дает вам доступа к параметрам аннотации без просмотра (см.: stackoverflow.com/questions/26635223/). - person Nate; 13.11.2014
comment
Я пробовал... но у меня это не работает, потому что SecuredAction должен принимать параметр типа... а передаваемый тип должен быть object производным от Controller... - person j3d; 14.11.2014
comment
Я не совсем уверен, что вижу связь. В вашем вопросе SecuredAction вообще не используется, за исключением определения вашего метода (которое не будет раскрыто). Вы не пытаетесь получить аннотацию для аутентификации метода или пытаетесь получить что-то еще? - person Nate; 14.11.2014
comment
SecuredAction — это пользовательское действие, предназначенное для защиты методов контроллера в соответствии с текущим профилем авторизации. SecuredAction является общим и используется многими различными контроллерами. SecuredAction автоматически считывает псевдоним метода и определяет, авторизован ли текущий пользователь для вызова метода (для каждого псевдонима есть запись в конфигурации, указывающая, кто может вызывать метод, связанный с этим псевдонимом). - person j3d; 14.11.2014
comment
Без более крупного примера кода я не думаю, что смогу помочь вам в дальнейшем; хотя я почти уверен, что вы можете использовать Джексона, чтобы получить то, что вам нужно. Вам, должно быть, не хватает какой-то идеи/ссылки, чтобы заставить ее работать. Рассматривали ли вы TypeTag для универсального типа в SecuredAction для получения объекта класса? Или рассматривали переход в this.getClass() до "authenticate"? - person Nate; 14.11.2014
comment
Вот код SecuredAction: ideone.com/6dy34l. Если я TypeTag универсальный тип на SecuredAction, он компилируется, но затем происходит сбой во время выполнения, потому что он не находит универсальный тип (например, java.lang.ClassNotFoundException: controllers.auth.Users$). - person j3d; 14.11.2014
comment
К сожалению, я мало знаю о том, как работает игра; это не имеет никакого смысла! Как он работает с вашим кодом без загрузки одноэлементного класса? К вашему сведению, вам не нужен TypeTag для каких-либо методов внутри SecuredAction, поскольку он должен существовать на уровне класса. Альтернативой может быть изменение apply на что-то вроде apply[T <: Controller](cls: Class[T], methodName: String), а затем вызов типа: SecuredAction(classOf[User.type], "authenticate").... По крайней мере, тогда это происходит во время компиляции. В любом случае вы, возможно, не сможете использовать отражение; Вы используете правильный загрузчик классов? - person Nate; 14.11.2014

Вероятно, решение, предложенное Nate, является наиболее чистым и эффективным... но в моем случае это потребовало бы слишком большого рефакторинга, поэтому я просто решил по-прежнему использовать отражение Scala. Вот мое решение:

package utils.common

import scala.reflect.runtime.universe._

/**
  * Provides functionality for obtaining reflective information about
  * classes and objects.
  */
object ReflectHelper {

  /**
    * Returns a `Map` from annotation names to annotation data for
    * the specified type.
    *
    * @tparam T The type to get class annotations for.
    * @return The class annotations for `T`.
    */
  def classAnnotations[T: TypeTag]: Map[String, Map[String, Any]] = {
    typeOf[T].typeSymbol.asClass.annotations.map { a =>
      a.tree.tpe.typeSymbol.name.toString -> a.tree.children.withFilter {
        _.productPrefix eq "AssignOrNamedArg"
      }.map { tree =>
        tree.productElement(0).toString -> tree.productElement(1)
      }.toMap
    }.toMap
  }

  /**
    * Returns a `Map` from method names to a `Map` from annotation names to
    * annotation data for the specified type.
    *
    * @tparam T The type to get method annotations for.
    * @return The method annotations for `T`.
    */
  def methodAnnotations[T: TypeTag]: Map[String, Map[String, Map[String, Any]]] = {
    typeOf[T].decls.collect { case m: MethodSymbol => m }.withFilter {
      _.annotations.length > 0
    }.map { m =>
      m.name.toString -> m.annotations.map { a =>
        a.tree.tpe.typeSymbol.name.toString -> a.tree.children.withFilter {
         _.productPrefix eq "AssignOrNamedArg"
        }.map { tree =>
          tree.productElement(0).toString -> tree.productElement(1)
        }.toMap
      }.toMap
    }.toMap
  }
}

Я надеюсь, что это помогает.

person j3d    schedule 14.11.2014