Scala Annotation (注解)

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.ClassfileAnnotationJava编译器生成注解
  • scala.annotation.StaticAnnotationScala编译器生成注解

两类注解特质都继承自基类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包路径下。

获取注解:

  • 获取类的注解:

    1. 使用typeOf()方法,获取Type类型的类信息。
    2. 通过Type.typeSymbol获取Symbol
    3. 通过Symbol.annotations获取List[Annotation](注解列表)。
  • 获取方法/成员字段的注解:

    1. 使用typeOf()方法,获取Type类型的类信息。
    2. 通过Type.decls/decl()方法筛选出目标成员的Symbol
    3. 通过Symbol.annotations获取List[Annotation](注解列表)。
  • 获取方法参数的注解:

    1. 使用typeOf()方法,获取Type类型的类信息。
    2. 通过Type.decls/decl()方法筛选出目标方法的MethodSymbol
    3. 通过MethodSymbol.paramLists方法获取目标方法的参数表(List[List[Symbol]]类型,方法柯里化可能会有多个参数表)。
    4. 通过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
  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值