Annotation (注解)
Scala
中的注解文法與
Java
中類似。
标準庫定義的注解相關内容在包
scala.annotation
中。
注解的基本文法為:
@注解名稱(注解參數...)
與
Java
注解的用法類似,注解參數不是必須的,一個元素允許擁有多個注解。
自定義注解
Scala 2.10
之前,
Scala
并未提供自定義注解功能,自定義注解需要在
Java
源碼中進行。
Scala 2.10
開始,作為
Reflect
功能的一部分,
Scala
提供了自定義注解支援。
與反射相關功能類似,到目前版本(
Scala 2.12
)為止,注解相關功能依然是
Expermental
(實驗性)的,注解相關
API
一直處于變化中。
Scala
中的自定義注解不是接口/特質,而是類。
自定義注解需要從注解特質中繼承,
Scala
中提供了兩類注解特質:
-
由scala.annotation.ClassfileAnnotation
編譯器生成注解Java
-
由scala.annotation.StaticAnnotation
編譯器生成注解Scala
兩類注解特質都繼承自基類
scala.annotation.Annotation
。
定義注解類文法與普通類相同:
// 标記注解
class 注解名稱 extends StaticAnnotation/ClassfileAnnotation
// 有參注解
class 注解名稱(參數表...) extends StaticAnnotation/ClassfileAnnotation
兩類注解特質的基類相同,是以自定義注解類時允許同時混入兩類注解特質。
繼承自
ClassfileAnnotation
基類的注解在使用時參數應以
具名參數(named arguments)
形式傳入。
繼承自
StaticAnnotation
基類的注解無此限制。
如下所示:
import scala.annotation.{ClassfileAnnotation, StaticAnnotation}
class CustomStaticAnnotation(name: String) extends StaticAnnotation
class CustomClassfileAnnotation(name: String) extends ClassfileAnnotation
@CustomStaticAnnotation("2333") //正确
@CustomClassfileAnnotation("2333") //錯誤,Java注解需要以具名參數形式進行傳入
@CustomClassfileAnnotation(name = "2333") //正确
class Test
解析注解
通過反射機制擷取注解資訊,相關
API
位于
scala.reflect.runtime.universe
包路徑下。
擷取注解:
- 擷取類的注解:
- 使用
方法,擷取typeOf()
類型的類資訊。Type
- 通過
擷取Type.typeSymbol
。Symbol
- 通過
擷取Symbol.annotations
(注解清單)。List[Annotation]
- 使用
- 擷取方法/成員字段的注解:
- 使用
方法,擷取typeOf()
類型的類資訊。Type
- 通過
方法篩選出目标成員的Type.decls/decl()
。Symbol
- 通過
擷取Symbol.annotations
(注解清單)。List[Annotation]
- 使用
- 擷取方法參數的注解:
- 使用
方法,擷取typeOf()
類型的類資訊。Type
- 通過
方法篩選出目标方法的Type.decls/decl()
。MethodSymbol
- 通過
方法擷取目标方法的參數表(MethodSymbol.paramLists
類型,方法柯裡化可能會有多個參數表)。List[List[Symbol]]
- 通過
擷取Symbol.annotations
(注解清單)。List[Annotation]
- 使用
Scala
注解類型為
scala.reflect.runtime.universe.Annotation
。
在
Scala 2.11
之前,
Annotation
類型提供了
scalaArgs/javaArgs
等無參方法用于擷取注解資訊,但在
Scala 2.11
版本中,這些方法已被标記為
deprecated
。
應使用
Annotation.tree
方法擷取注解文法樹,類型為
scala.reflect.runtime.universe.Tree
。
如下所示:
import scala.annotation.StaticAnnotation
import scala.reflect.runtime.universe._
class CustomAnnotation(name: String, num: Int) extends StaticAnnotation
@CustomAnnotation("Annotation for Class", )
class Test {
@CustomAnnotation("Annotation for Class", )
val ff = ""
}
object Main extends App {
{
// 擷取類型注解
val tpe: Type = typeOf[Test]
val symbol: Symbol = tpe.typeSymbol //擷取類型符号資訊
val annotation: Annotation = symbol.annotations.head
val tree: Tree = annotation.tree //擷取文法樹
// 解析注解文法樹...
}
{
// 擷取成員字段注解
val tpe: Type = typeOf[Test]
val symbol: Symbol = tpe.decl(TermName("ff ")) //擷取字段符号資訊
val annotation: Annotation = symbol.annotations.head
val tree: Tree = annotation.tree
// 解析注解文法樹...
}
}
通過
scala.reflect.api.Printer.showRaw()
方法可以擷取文法樹的文本。
注解文法樹中包含了注解參數資訊,可以通過模式比對提取。
如下所示:
import scala.annotation.StaticAnnotation
import scala.reflect.runtime.universe._
class CustomAnnotation(name: String, num: Int) extends StaticAnnotation
@CustomAnnotation("Annotation for Class", )
class Test
object Main extends App {
// 擷取類型注解
val tpe: Type = typeOf[Test]
val symbol: Symbol = tpe.typeSymbol //擷取類型符号資訊
val annotation: Annotation = symbol.annotations.head
val tree: Tree = annotation.tree //擷取文法樹
println(showRaw(tree)) //列印文法樹
val Apply(_, Literal(Constant(name: String)) :: Literal(Constant(num: Int)) :: Nil) = tree
println(s"Annotation args: name -> $name, num -> $num")
}
輸出結果:(
Scala 2.12.2 && macOS 10.12.5
)
Apply(Select(New(TypeTree()), termNames.CONSTRUCTOR), List(Literal(Constant("Annotation for Class")), Literal(Constant())))
Annotation args: name -> Annotation for Class, num ->
注意事項:
- 解析注解參數需要基于文法樹結構,不要使用參數預設值特性,使用預設參數的注解生成的文法樹不包含注解資訊的預設值。
- 類内字段會有多個
,對應不同的TermSymbol
,包含注解資訊的TermName
為TermName
。字段名稱 + 空格
-
樣例類的構造器參數作為類的成員存在,但若在參數上添加注解,注解資訊并未附加在字段資訊中。
提取樣例類構造器成員的注解資訊需要以擷取方法參數注解的方式進行,查找構造器方法(
),擷取參數成員(TermName("<init>")
)。Method.paramLists
- 使用
方法擷取注解文法樹(Annotation.tree
類型),再使用Tree
方法擷取注解文法樹類型資訊(Tree.tpe
類型),與直接使用Type
擷取的注解類型資訊相同,可以用于比較篩選注解類型。typeOf[注解類型]
完整的注解解析執行個體,如下所示:
import scala.annotation.StaticAnnotation
import scala.reflect.runtime.universe._
class CustomAnnotation(name: String, num: Int) extends StaticAnnotation {
override def toString = s"Annotation args: name -> $name, num -> $num"
}
@CustomAnnotation("Annotation for Class", )
class Test {
@CustomAnnotation("Annotation for Member", )
val ff = ""
def mm(ss: String, @CustomAnnotation("Annotation for Arg", ) arg: Int) = ""
}
object Main extends App {
// 擷取指定類型的注解資訊,通過 Annotation.tree.tpe 擷取注解的 Type 類型,以此進行篩選
def getClassAnnotation[T: TypeTag, U: TypeTag] =
symbolOf[T].annotations.find(_.tree.tpe =:= typeOf[U])
// 通過字段名稱擷取指定類型的注解資訊,注意查找字段名稱時添加空格
def getMemberAnnotation[T: TypeTag, U: TypeTag](memberName: String) =
typeOf[T].decl(TermName(s"$memberName ")).annotations.find(_.tree.tpe =:= typeOf[U])
// 通過方法名稱和參數名稱擷取指定類型的注解資訊
def getArgAnnotation[T: TypeTag, U: TypeTag](methodName: String, argName: String) =
typeOf[T].decl(TermName(methodName)).asMethod.paramLists.collect {
case symbols => symbols.find(_.name == TermName(argName))
}.headOption.fold(Option[Annotation](null))(_.get.annotations.find(_.tree.tpe =:= typeOf[U]))
// 解析文法樹,擷取注解資料
def getCustomAnnotationData(tree: Tree) = {
val Apply(_, Literal(Constant(name: String)) :: Literal(Constant(num: Int)) :: Nil) = tree
new CustomAnnotation(name, num)
}
getClassAnnotation[Test, CustomAnnotation].map(_.tree) foreach { classAnnotationTree =>
val classAnnotation = getCustomAnnotationData(classAnnotationTree)
println(classAnnotation)
}
getMemberAnnotation[Test, CustomAnnotation]("ff").map(_.tree) foreach { memberAnnotationTree =>
val memberAnnotation = getCustomAnnotationData(memberAnnotationTree)
println(memberAnnotation)
}
getArgAnnotation[Test, CustomAnnotation]("mm", "arg").map(_.tree) foreach { argAnnotationTree =>
val argAnnotation = getCustomAnnotationData(argAnnotationTree)
println(argAnnotation)
}
}
輸出結果:(
Scala 2.12.2 && macOS 10.12.5
)
Annotation args: name -> Annotation for Class, num ->
Annotation args: name -> Annotation for Member, num ->
Annotation args: name -> Annotation for Arg, num ->