如何在运行时访问在案例类字段上定义的注释

时间:2012-07-13 10:21:05

标签: scala reflection parameters constructor

我已经定义了以下java Annotation

   @Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.CONSTRUCTOR, ElementType.FIELD})
   @Retention(RetentionPolicy.RUNTIME)
   public @interface MyAnnotation { 
     String value() default "";
   }

我定义了以下scala案例类:

@Prefer("xyz")
case class TestAnno(arg1 : String, @Prefer("abc") agr2 : String)

我能够使用反射看到在案例类级别定义的注释,但我无法访问为案例类arg2的{​​{1}}成员定义的注释。反编译代码我发现变量声明或它的scala访问器似乎都没有注释。只有构造函数定义和TestAnno方法似乎保留了case类声明中定义的参数的注释。

是否有其他方法可以强制scala编译器为case类中声明的字段生成注释,或者我必须读取构造函数并使用ASM ByteCode LibraryParaNamer等库来查找参数有哪些注释?需要一个主要适用于Scala案例类的解决方案。

3 个答案:

答案 0 :(得分:15)

您只需要执行以下操作:

case class TestAnno(arg1 : String, @(Prefer @field)("abc") agr2 : String)

此处有更多信息http://www.scala-lang.org/api/current/#scala.annotation.meta.package

答案 1 :(得分:5)

Quentin的解决方案有效,但恕我直言,这对用户来说太过分析。

您可以使用标准反射API读取构造函数参数的注释。我需要这个用于宏实现。

scala> :paste
// Entering paste mode (ctrl-D to finish)

import scala.annotation.StaticAnnotation
final class min(i: Long) extends StaticAnnotation

case class Foo(@min(1) c: String)
import scala.reflect.runtime.universe._
symbolOf[Foo].asClass.primaryConstructor.typeSignature.paramLists.head.head.annotations

// Exiting paste mode, now interpreting.

import scala.annotation.StaticAnnotation
defined class min
defined class Foo
import scala.reflect.runtime.universe._
res0: List[reflect.runtime.universe.Annotation] = List(min(1L))

答案 2 :(得分:2)

我做了一些搜索,并提供了两个解决方案。评论,建议,改进是受欢迎的,我已将此答案标记为维基。第一个基于ScalaBeansParaNamer

  def valNamesWithAnnotations[T <: AnyRef](obj : T)(implicit m : Manifest[T]) : List[(String, List[java.lang.annotation.Annotation])] = {
    val descriptor = descriptorOf(obj.getClass)

    val c = descriptor.beanType.erasure

    val constructor: Option[Constructor[_]] = {
      if (c.getConstructors().isEmpty) None
      else Some(c.getConstructors()(0).asInstanceOf[Constructor[_]])
    }

    val paranamer = new BytecodeReadingParanamer
    val ctorParameterNames = constructor.map(paranamer.lookupParameterNames(_)).getOrElse(scala.Array[String]()).toList
    val ctorParamAnnos = constructor.getOrElse(sys.error("Cannot find constructor entry for class " + c.getName)).getParameterAnnotations

    val builder = List.newBuilder[(String, List[java.lang.annotation.Annotation])]
    val paramIter = ctorParameterNames.iterator
    val annoIter = ctorParamAnnos.iterator

    while(paramIter.hasNext && annoIter.hasNext ) {
      builder += ((paramIter.next, annoIter.next.toList))
    }

    builder.result
  }

第二种方法使用ScalaSignatures并基于此SO answer

  def valNamesWithAnnotations[C: ClassManifest] : List[(String, List[java.lang.annotation.Annotation])] = {
    val cls = classManifest[C].erasure
    val ctors = cls.getConstructors

    assert(ctors.size == 1, "Class " + cls.getName + " should have only one constructor")
    val sig = ScalaSigParser.parse(cls).getOrElse(sys.error("No ScalaSig for class " + cls.getName + ", make sure it is a top-level case class"))

    val classSymbol = sig.parseEntry(0).asInstanceOf[ClassSymbol]
    assert(classSymbol.isCase, "Class " + cls.getName + " is not a case class")

    val tableSize = sig.table.size
    val ctorIndex = (1 until tableSize).find { i =>
      sig.parseEntry(i) match {
        case m @ MethodSymbol(SymbolInfo("<init>", owner, _, _, _, _), _) => owner match {
          case sym: SymbolInfoSymbol if sym.index == 0 => true
          case _ => false
        }
        case _ => false
      }
    }.getOrElse(sys.error("Cannot find constructor entry in ScalaSig for class " + cls.getName))

    val paramsListBuilder = List.newBuilder[String]
    for (i <- (ctorIndex + 1) until tableSize) {
      sig.parseEntry(i) match {
        case MethodSymbol(SymbolInfo(name, owner, _, _, _, _), _) => owner match {
          case sym: SymbolInfoSymbol if sym.index == ctorIndex => paramsListBuilder += name
          case _ =>
        }
        case _ =>
      }
    }

    val paramAnnoArr = ctors(0).getParameterAnnotations
    val builder = List.newBuilder[(String, List[java.lang.annotation.Annotation])]

    val paramIter = paramsListBuilder.result.iterator
    val annoIter = paramAnnoArr.iterator

    while(paramIter.hasNext && annoIter.hasNext ) {
      builder += ((paramIter.next, annoIter.next.toList))
    }

    builder.result
  }