Introduction

Akka Typed Actor从2.4开始直到2.5可以商用,进而Akka 2.6已经把Akka Typed Actor做为推荐的Actor使用模式。Typed Actor与原先的Untyped Actor最大且直观的区别就是ActorRef有类型了,其签名也改成了akka.actor.typed.ActorRef[T]

HelloWorld

第一个示例是一个 Ping-Pong,从一个actor发送消息到另一个actor,并收到回复。

object Ping {
  sealed trait Command
  final case object Start extends Command
  final case class PongCommand(message: String) extends Command
  def apply(): Behavior[Command] = Behaviors.receive {
    case (context, Start) =>
      val pong = context.spawn(Pong(), "pong")
      pong ! Pong.PingCommand("Scala", context.self)
      context.log.info("Started Pong actor and send message complete.")
      Behaviors.same
    case (context, PongCommand(message)) =>
      context.log.info(s"Receive pong message: $message")
      Behaviors.stopped
  }
}

object Pong {
  sealed trait Command
  final case class PingCommand(message: String, replyTo: ActorRef[Ping.Command]) extends Command
  def apply(): Behavior[Command] = Behaviors.receive[Command] {
    case (context, PingCommand(message, replyTo)) =>
      context.log.info(s"Receive ping message: $message")
      replyTo ! Ping.PongCommand(s"Hello $message")
      Behaviors.stopped
  }
}

运行actor需要有一个ActorSystem,这面的代码将执行这个示例。

val system: ActorSystem[Ping.Command] = ActorSystem(Ping(), "helloworld")
system ! Ping.Start
system.terminate()

运行示例程序,可看到如何输出:

sbt:akka-cookbook> cookbook-actor/runMain cookbook.actor.introduction.HelloWorld
[info] running (fork) cookbook.actor.introduction.HelloWorld 
[2019-11-19 19:24:39,285] [INFO] [akka.event.slf4j.Slf4jLogger] [helloworld-akka.actor.default-dispatcher-3] [] - Slf4jLogger started
[2019-11-19 19:24:39,374] [INFO] [cookbook.actor.introduction.Ping$] [helloworld-akka.actor.default-dispatcher-3] [akka://helloworld/user] - Started Pong actor and send message complete.
[2019-11-19 19:24:39,374] [INFO] [cookbook.actor.introduction.Pong$] [helloworld-akka.actor.default-dispatcher-6] [akka://helloworld/user/pong] - Receive ping message: Scala
[2019-11-19 19:24:39,375] [INFO] [cookbook.actor.introduction.Ping$] [helloworld-akka.actor.default-dispatcher-3] [akka://helloworld/user] - Receive pong message: Hello Scala
[success] Total time: 1 s, completed Nov 19, 2019 19:24:39 PM

Behavior

Akka Typed不再需要通过类的形式来实现Actor接口定义,而是函数的形式来定义actor。可以看到,定义的actor类型为Behavior[T](形为),通过Behaviors.receiveMessage[T](T => Behavior[T]): Receive[T]函数来处理接收到的消息,而Receive继承了Behavior trait。通过函数签名可以看到,每次接收到消息并对其处理完成后,都必须要返回一个新的形为。

apply(): Behavior[Command]函数签名里的范性参数类型Command限制了这个actor将只接收CommandCommand子类型的消息,编译器将在编译期对传给actor的消息做类型检查,相对于从前的untyped actor可以向actor传入任何类型的消息,这可以限制的减少程序中的bug。特别是在程序规模很大,当你定义了成百上千个消息时。

也因为有类型的actor,在Akka Typed中没有了隐式发送的sender: ActorRef,必须在发送的消息里面包含回复字段,就如PingCommand消息定义里的replyTo: ActorRef[Ping.Command]字段一样。actor在处理完消息后可以通过它向发送者回复处理结果。

ActorRef

ActorRef[T]Behavior[T]被ActorSystem构造后创建的actor的引用,与经典ActorRef的区别显而易见,它拥有了一个类型参数TT限定了这个actor只能处理T或它的子类型。这相对经典actor是一大进步,特别是在你的actor系统规模很大时,若没有一个静态的类型约束你将十之八九会迷失在消息的海洋……

构造 ActorSystem

ActorSystem构造至少需要转入两个参数:

  • guardianBehavior: Behavior[T]:守卫行为,它将被创建为守卫actor,其 ActorPath 地址为:akka://helloworld/user
  • name: String:ActorSystem名字,这个名字除了在日志线程中显示外,在Akka Cluster时也很重要,用于标识同一个集群。
在此文档中发现错误?该页面的源代码可以在 这里 找到。欢迎随时编辑并提交Pull Request。