天天看點

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.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

包路徑下。

擷取注解:

  • 擷取類的注解:
    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", )
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 ->