Actor construction is a common source of difficulty for people new to Akka. Unlike (most) ordinary objects, you never instantiate actors explicitly. You would never write, for instance, val echo = new EchoActor
. In fact, if you try this, Akka raises an exception.
Creating actors in Akka is a two-step process: you first create a Props
object, which encapsulates the properties needed to construct an actor. The way to construct a Props
object differs depending on whether the actor takes constructor arguments. If the constructor takes no arguments, we simply pass the actor class as a type parameter to Props
:
val echoProps = Props[EchoActor]
If we have an actor whose constructor does take arguments, we must pass these as additional arguments when defining the Props
object. Let's consider the following actor, for instance:
class TestActor(a:String, b:Int) extends Actor { ... }
We pass the constructor arguments to the Props
object as follows:
val testProps = Props(classOf[TestActor], "hello", 2)
The Props
instance just embodies the configuration for creating an actor. It does not actually create anything. To create an actor, we pass the Props
instance to the system.actorOf
method, defined on the ActorSystem
instance:
val system = ActorSystem("HelloActors")
val echo1 = system.actorOf(echoProps, name="hello-1")
The name
parameter is optional but is useful for logging and error messages. The value returned by .actorOf
is not the actor itself: it is a reference to the actor (it helps to think of it as an address that the actor lives at) and has the ActorRef
type. ActorRef
is immutable, but it can be serialized and duplicated without affecting the underlying actor.
There is another way to create actors besides calling actorOf
on the actor system: each actor exposes a context.actorOf
method that takes a Props
instance as its argument. The context is only accessible from within the actor:
class TestParentActor extends Actor {
val echoChild = context.actorOf(echoProps, name="hello-child")
...
}
The difference between an actor created from the actor system and an actor created from another actor's context lies in the actor hierarchy: each actor has a parent. Any actor created within another actor's context will have that actor as its parent. An actor created by the actor system has a predefined actor, called the user guardian, as its parent. We will understand the importance of the actor hierarchy when we study the actor lifecycle at the end of this chapter.
A very common idiom is to define a props
method in an actor's companion object that acts as a factory method for Props
instances for that actor. Let's amend the EchoActor
companion object:
object EchoActor { def props:Props = Props[EchoActor] // message case class definitions here }
We can then instantiate the actor as follows:
val echoActor = system.actorOf(EchoActor.props)