diff --git a/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java b/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java index d48a2015c5..21f4321c4e 100644 --- a/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java +++ b/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java @@ -5,17 +5,19 @@ package jdocs.akka.typed; // #imports - import akka.actor.typed.ActorRef; import akka.actor.typed.ActorSystem; import akka.actor.typed.Behavior; +import akka.actor.typed.javadsl.AbstractBehavior; +import akka.actor.typed.javadsl.ActorContext; +import akka.actor.typed.javadsl.Behaviors; +import akka.actor.typed.javadsl.Receive; + +// #imports + import akka.actor.typed.Terminated; import akka.actor.typed.Props; import akka.actor.typed.DispatcherSelector; -import akka.actor.typed.javadsl.ActorContext; -import akka.actor.typed.javadsl.Behaviors; - -// #imports import java.io.UnsupportedEncodingException; import java.net.URLEncoder; @@ -23,13 +25,10 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; -public class IntroTest { +public interface IntroTest { // #hello-world-actor - public abstract static class HelloWorld { - // no instances of this class, it's only a name space for messages - // and static methods - private HelloWorld() {} + public class HelloWorld extends AbstractBehavior { public static final class Greet { public final String whom; @@ -51,39 +50,65 @@ public class IntroTest { } } - public static final Behavior greeter = - Behaviors.receive( - (context, message) -> { - context.getLog().info("Hello {}!", message.whom); - message.replyTo.tell(new Greeted(message.whom, context.getSelf())); - return Behaviors.same(); - }); + public static Behavior create() { + return Behaviors.setup(HelloWorld::new); + } + + private final ActorContext context; + + private HelloWorld(ActorContext context) { + this.context = context; + } + + @Override + public Receive createReceive() { + return newReceiveBuilder().onMessage(Greet.class, this::onGreet).build(); + } + + private Behavior onGreet(Greet command) { + context.getLog().info("Hello {}!", command.whom); + command.replyTo.tell(new Greeted(command.whom, context.getSelf())); + return this; + } } // #hello-world-actor // #hello-world-bot - public abstract static class HelloWorldBot { - private HelloWorldBot() {} + public class HelloWorldBot extends AbstractBehavior { - public static final Behavior bot(int greetingCounter, int max) { - return Behaviors.receive( - (context, message) -> { - int n = greetingCounter + 1; - context.getLog().info("Greeting {} for {}", n, message.whom); - if (n == max) { - return Behaviors.stopped(); - } else { - message.from.tell(new HelloWorld.Greet(message.whom, context.getSelf())); - return bot(n, max); - } - }); + public static Behavior create(int max) { + return Behaviors.setup(context -> new HelloWorldBot(context, max)); + } + + private final ActorContext context; + private final int max; + private int greetingCounter; + + private HelloWorldBot(ActorContext context, int max) { + this.context = context; + this.max = max; + } + + @Override + public Receive createReceive() { + return newReceiveBuilder().onMessage(HelloWorld.Greeted.class, this::onGreeted).build(); + } + + private Behavior onGreeted(HelloWorld.Greeted message) { + greetingCounter++; + context.getLog().info("Greeting {} for {}", greetingCounter, message.whom); + if (greetingCounter == max) { + return Behaviors.stopped(); + } else { + message.from.tell(new HelloWorld.Greet(message.whom, context.getSelf())); + return this; + } } } // #hello-world-bot // #hello-world-main - public abstract static class HelloWorldMain { - private HelloWorldMain() {} + public class HelloWorldMain extends AbstractBehavior { public static class Start { public final String name; @@ -93,25 +118,32 @@ public class IntroTest { } } - public static final Behavior main = - Behaviors.setup( - context -> { - final ActorRef greeter = - context.spawn(HelloWorld.greeter, "greeter"); + public static Behavior create() { + return Behaviors.setup(HelloWorldMain::new); + } - return Behaviors.receiveMessage( - message -> { - ActorRef replyTo = - context.spawn(HelloWorldBot.bot(0, 3), message.name); - greeter.tell(new HelloWorld.Greet(message.name, replyTo)); - return Behaviors.same(); - }); - }); + private final ActorContext context; + private final ActorRef greeter; + + private HelloWorldMain(ActorContext context) { + this.context = context; + greeter = context.spawn(HelloWorld.create(), "greeter"); + } + + @Override + public Receive createReceive() { + return newReceiveBuilder().onMessage(Start.class, this::onStart).build(); + } + + private Behavior onStart(Start command) { + ActorRef replyTo = context.spawn(HelloWorldBot.create(3), command.name); + greeter.tell(new HelloWorld.Greet(command.name, replyTo)); + return this; + } } // #hello-world-main - public abstract static class CustomDispatchersExample { - private CustomDispatchersExample() {} + interface CustomDispatchersExample { public static class Start { public final String name; @@ -122,30 +154,49 @@ public class IntroTest { } // #hello-world-main-with-dispatchers - public static final Behavior main = - Behaviors.setup( - context -> { - final String dispatcherPath = "akka.actor.default-blocking-io-dispatcher"; + public class HelloWorldMain extends AbstractBehavior { - Props props = DispatcherSelector.fromConfig(dispatcherPath); - final ActorRef greeter = - context.spawn(HelloWorld.greeter, "greeter", props); + // Start message... + // #hello-world-main-with-dispatchers + public static class Start { + public final String name; - return Behaviors.receiveMessage( - message -> { - ActorRef replyTo = - context.spawn(HelloWorldBot.bot(0, 3), message.name); - greeter.tell(new HelloWorld.Greet(message.name, replyTo)); - return Behaviors.same(); - }); - }); + public Start(String name) { + this.name = name; + } + } + // #hello-world-main-with-dispatchers + + public static Behavior create() { + return Behaviors.setup(HelloWorldMain::new); + } + + private final ActorContext context; + private final ActorRef greeter; + + private HelloWorldMain(ActorContext context) { + this.context = context; + + final String dispatcherPath = "akka.actor.default-blocking-io-dispatcher"; + Props greeterProps = DispatcherSelector.fromConfig(dispatcherPath); + greeter = context.spawn(HelloWorld.create(), "greeter", greeterProps); + } + + // createReceive ... + // #hello-world-main-with-dispatchers + @Override + public Receive createReceive() { + return null; + } + // #hello-world-main-with-dispatchers + } // #hello-world-main-with-dispatchers } public static void main(String[] args) throws Exception { // #hello-world final ActorSystem system = - ActorSystem.create(HelloWorldMain.main, "hello"); + ActorSystem.create(HelloWorldMain.create(), "hello"); system.tell(new HelloWorldMain.Start("World")); system.tell(new HelloWorldMain.Start("Akka")); @@ -156,7 +207,7 @@ public class IntroTest { } // #chatroom-behavior - public static class ChatRoom { + public class ChatRoom { // #chatroom-behavior // #chatroom-protocol @@ -185,7 +236,7 @@ public class IntroTest { // #chatroom-behavior // #chatroom-protocol - static interface SessionEvent {} + interface SessionEvent {} public static final class SessionGranted implements SessionEvent { public final ActorRef handle; @@ -213,7 +264,7 @@ public class IntroTest { } } - static interface SessionCommand {} + interface SessionCommand {} public static final class PostMessage implements SessionCommand { public final String message; @@ -301,7 +352,7 @@ public class IntroTest { // #chatroom-behavior // #chatroom-gabbler - public static class Gabbler { + public class Gabbler { public static Behavior create() { return Behaviors.setup(ctx -> new Gabbler(ctx).behavior()); } diff --git a/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/SpawnProtocolDocTest.java b/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/SpawnProtocolDocTest.java index 99a848d04c..0ea545f1e0 100644 --- a/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/SpawnProtocolDocTest.java +++ b/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/SpawnProtocolDocTest.java @@ -51,7 +51,7 @@ public class SpawnProtocolDocTest { AskPattern.ask( system, replyTo -> - new SpawnProtocol.Spawn<>(HelloWorld.greeter, "greeter", Props.empty(), replyTo), + new SpawnProtocol.Spawn<>(HelloWorld.create(), "greeter", Props.empty(), replyTo), timeout, system.scheduler()); diff --git a/akka-actor-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala b/akka-actor-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala index b3416f3af0..e866710046 100644 --- a/akka-actor-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala @@ -6,12 +6,14 @@ package docs.akka.typed //#fiddle_code //#imports -import akka.NotUsed import akka.actor.typed.scaladsl.Behaviors -import akka.actor.typed.{ ActorRef, ActorSystem, Behavior, DispatcherSelector, Terminated } +import akka.actor.typed.{ ActorRef, ActorSystem, Behavior } //#imports //#fiddle_code +import akka.NotUsed +import akka.actor.typed.{ DispatcherSelector, Terminated } + import org.scalatest.WordSpecLike import akka.actor.testkit.typed.scaladsl.ActorTestKit import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit @@ -27,7 +29,7 @@ object IntroSpec { final case class Greet(whom: String, replyTo: ActorRef[Greeted]) final case class Greeted(whom: String, from: ActorRef[Greet]) - val greeter: Behavior[Greet] = Behaviors.receive { (context, message) => + def apply(): Behavior[Greet] = Behaviors.receive { (context, message) => //#fiddle_code context.log.info("Hello {}!", message.whom) //#fiddle_code @@ -43,14 +45,18 @@ object IntroSpec { //#hello-world-bot object HelloWorldBot { - def bot(greetingCounter: Int, max: Int): Behavior[HelloWorld.Greeted] = + def apply(max: Int): Behavior[HelloWorld.Greeted] = { + bot(0, max) + } + + private def bot(greetingCounter: Int, max: Int): Behavior[HelloWorld.Greeted] = Behaviors.receive { (context, message) => val n = greetingCounter + 1 //#fiddle_code context.log.info("Greeting {} for {}", n, message.whom) //#fiddle_code //#hello-world-bot - println(s"Greeting ${n} for ${message.whom}") + println(s"Greeting $n for ${message.whom}") //#hello-world-bot if (n == max) { Behaviors.stopped @@ -67,12 +73,12 @@ object IntroSpec { final case class Start(name: String) - val main: Behavior[Start] = + def apply(): Behavior[Start] = Behaviors.setup { context => - val greeter = context.spawn(HelloWorld.greeter, "greeter") + val greeter = context.spawn(HelloWorld(), "greeter") Behaviors.receiveMessage { message => - val replyTo = context.spawn(HelloWorldBot.bot(greetingCounter = 0, max = 3), message.name) + val replyTo = context.spawn(HelloWorldBot(max = 3), message.name) greeter ! HelloWorld.Greet(message.name, replyTo) Behaviors.same } @@ -91,10 +97,10 @@ object IntroSpec { val dispatcherPath = "akka.actor.default-blocking-io-dispatcher" val props = DispatcherSelector.fromConfig(dispatcherPath) - val greeter = context.spawn(HelloWorld.greeter, "greeter", props) + val greeter = context.spawn(HelloWorld(), "greeter", props) Behaviors.receiveMessage { message => - val replyTo = context.spawn(HelloWorldBot.bot(greetingCounter = 0, max = 3), message.name) + val replyTo = context.spawn(HelloWorldBot(max = 3), message.name) greeter ! HelloWorld.Greet(message.name, replyTo) Behaviors.same @@ -199,7 +205,7 @@ class IntroSpec extends ScalaTestWithActorTestKit with WordSpecLike { //#hello-world val system: ActorSystem[HelloWorldMain.Start] = - ActorSystem(HelloWorldMain.main, "hello") + ActorSystem(HelloWorldMain(), "hello") system ! HelloWorldMain.Start("World") system ! HelloWorldMain.Start("Akka") diff --git a/akka-actor-typed-tests/src/test/scala/docs/akka/typed/SpawnProtocolDocSpec.scala b/akka-actor-typed-tests/src/test/scala/docs/akka/typed/SpawnProtocolDocSpec.scala index 35252f13d2..0531ab45bc 100644 --- a/akka-actor-typed-tests/src/test/scala/docs/akka/typed/SpawnProtocolDocSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/docs/akka/typed/SpawnProtocolDocSpec.scala @@ -61,7 +61,7 @@ class SpawnProtocolDocSpec extends ScalaTestWithActorTestKit with WordSpecLike { implicit val scheduler: Scheduler = system.scheduler val greeter: Future[ActorRef[HelloWorld.Greet]] = - system.ask(SpawnProtocol.Spawn(behavior = HelloWorld.greeter, name = "greeter", props = Props.empty)) + system.ask(SpawnProtocol.Spawn(behavior = HelloWorld(), name = "greeter", props = Props.empty)) val greetedBehavior = Behaviors.receive[HelloWorld.Greeted] { (context, message) => context.log.info("Greeting for {} from {}", message.whom, message.from) diff --git a/akka-docs/src/main/paradox/typed/actor-lifecycle.md b/akka-docs/src/main/paradox/typed/actor-lifecycle.md index fbef635693..e29ff8d47f 100644 --- a/akka-docs/src/main/paradox/typed/actor-lifecycle.md +++ b/akka-docs/src/main/paradox/typed/actor-lifecycle.md @@ -75,8 +75,8 @@ is a tool that mimics the old style of starting up actors. Child actors are spawned with @apidoc[typed.*.ActorContext]'s `spawn`. In the example below, when the root actor -is started, it spawns a child actor described by the behavior `HelloWorld.greeter`. Additionally, when the root actor receives a -`Start` message, it creates a child actor defined by the behavior `HelloWorldBot.bot`: +is started, it spawns a child actor described by the `HelloWorld` behavior. Additionally, when the root actor receives a +`Start` message, it creates a child actor defined by the behavior `HelloWorldBot`: Scala : @@snip [IntroSpec.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala) { #hello-world-main } diff --git a/akka-docs/src/main/paradox/typed/actors.md b/akka-docs/src/main/paradox/typed/actors.md index 03e6d83acc..3839e178c5 100644 --- a/akka-docs/src/main/paradox/typed/actors.md +++ b/akka-docs/src/main/paradox/typed/actors.md @@ -40,23 +40,27 @@ greet, it also holds an `ActorRef` that the sender of the message supplies so that the `HelloWorld` Actor can send back the confirmation message. -The behavior of the Actor is defined as the `greeter` value with the help +The behavior of the Actor is defined as the `Greeter` with the help of the `receive` behavior factory. Processing the next message then results in a new behavior that can potentially be different from this one. State is updated by returning a new behavior that holds the new immutable state. In this -case we don't need to update any state, so we return `same`, which means +case we don't need to update any state, so we return @scala[`same`]@java[`this`], which means the next behavior is "the same as the current one". The type of the messages handled by this behavior is declared to be of class -`Greet`, meaning that `message` argument is -also typed as such. This is why we can access the `whom` and `replyTo` -members without needing to use a pattern match. +`Greet`@java[.]@scala[, meaning that `message` argument is also typed as such. +This is why we can access the `whom` and `replyTo` members without needing to use a pattern match.] +Typically, an actor handles more than one specific message type and then there +is one common @scala[`trait`]@java[`interface`] that all messages that the +actor can handle @scala[`extends`]@java[`implements`]. On the last line we see the `HelloWorld` Actor send a message to another -Actor, which is done using the @scala[`!` operator (pronounced “bang” or “tell”).]@java[`tell` method.] +Actor, which is done using the @scala[`!` operator (pronounced “bang” or “tell”)]@java[`tell` method]. +It is an asynchronous operation that doesn't block the caller's thread. + Since the `replyTo` address is declared to be of type @scala[`ActorRef[Greeted]`]@java[`ActorRef`], the compiler will only permit us to send messages of this type, other usage will -not be accepted. +be a compiler error. The accepted message types of an Actor together with all reply types defines the protocol spoken by this Actor; in this case it is a simple request–reply @@ -65,8 +69,8 @@ protocol is bundled together with the behavior that implements it in a nicely wrapped scope—the `HelloWorld` @scala[object]@java[class]. As Carl Hewitt said, one Actor is no Actor—it would be quite lonely with -nobody to talk to. We need another Actor that interacts with the `greeter`. -Let's make a `bot` that receives the reply from the `greeter` and sends a number +nobody to talk to. We need another Actor that interacts with the `Greeter`. +Let's make a `HelloWorldBot` that receives the reply from the `Greeter` and sends a number of additional greeting messages and collect the replies until a given max number of messages have been reached. @@ -76,12 +80,12 @@ Scala Java : @@snip [IntroSpec.scala](/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java) { #hello-world-bot } -Note how this Actor manages the counter by changing the behavior for each `Greeted` reply -rather than using any variables. +@scala[Note how this Actor manages the counter by changing the behavior for each `Greeted` reply +rather than using any variables.]@java[Note how this Actor manages the counter with an instance variable.] +No concurrency guards such as `synchronized` or `AtomicInteger` are needed since an actor instance processes one +message at a time. - - -A third actor spawns the `greeter` and the `bot` and starts the interaction between those. +A third actor spawns the `Greeter` and the `HelloWorldBot` and starts the interaction between those. Scala : @@snip [IntroSpec.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala) { #hello-world-main } @@ -97,8 +101,8 @@ Scala Java : @@snip [IntroSpec.scala](/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java) { #hello-world } -We start an Actor system from the defined `main` behavior and send two `Start` messages that -will kick-off the interaction between two separate `bot` actors and the single `greeter` actor. +We start an Actor system from the defined `HelloWorldMain` behavior and send two `Start` messages that +will kick-off the interaction between two separate `HelloWorldBot` actors and the single `Greeter` actor. An application normally consists of a single `ActorSystem`, running many actors, per JVM.