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", 2333)
class Test {
@CustomAnnotation("Annotation for Class", 6666)
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", 2333)
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(2333))))
Annotation args: name -> Annotation for Class, num -> 2333
注意事项:
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", 2333)
class Test {
@CustomAnnotation("Annotation for Member", 6666)
val ff = ""
def mm(ss: String, @CustomAnnotation("Annotation for Arg", 9999) 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 -> 2333
Annotation args: name -> Annotation for Member, num -> 6666
Annotation args: name -> Annotation for Arg, num -> 9999