快速入门 Akka Java 指南

Akka 是一个用于在 JVM 上构建高并发、分布式和可容错的事件驱动应用程序的运行时工具包。Akka 既可以用于 Java,也可以用于 Scala。本指南通过描述 Java 版本的Hello World示例来介绍 Akka。如果你喜欢将 Akka 与 Scala 结合使用,请切换到「快速入门 Akka Scala 指南」。

Actors 是 Akka 的执行单元。Actor 模型是一种抽象,它让编写正确的并发、并行和分布式系统更加容易。Hello World示例说明了 Akka 的基础知识。在 30 分钟内,你应该能够下载并运行示例,并使用本指南了解示例是如何构造的。这会让你初步了解 Akka 的魅力,希望这能够让你拥有深入了解 Akka 的兴趣!

在体验过这个示例之后,想深入了解 Akka,阅读「Getting Started Guide」是一个很好的选择。

下载示例

Java 版本的Hello World示例是一个包含 Maven 和 Gradle 构建文件的压缩项目。你可以在 Linux、MacOS 或 Windows 上运行它。唯一的先决条件是安装 Java 8 和 Maven 或 Gradle。

下载和解压示例:

  • 在「Lightbend Tech Hub」上通过点击CREATE A PROJECT FOR ME下载压缩文件。
  • 将 ZIP 文件解压缩到方便的位置:
  • 在 Linux 和 OSX 系统上,打开终端并使用命令unzip akka-quickstart-java.zip。
  • 在 Windows 上,使用文件资源管理器等工具提取项目。

运行示例

确保你已经安装了构建工具,然后打开终端窗口,并从项目目录中键入以下命令以运行Hello World:

1. //  Maven
2. $ mvn compile exec:exec

4. // Grade
5. $ gradle run

输出应该如下所示(一直向右滚动以查看 Actor 输出):

1. // Maven
2. [INFO] Scanning for projects...
3. [INFO]
4. [INFO] ------------------------------------------------------------------------
5. [INFO] Building app 1.0
6. [INFO] ------------------------------------------------------------------------
7. [INFO]
8. [INFO] --- exec-maven-plugin:1.6.0:exec (default-cli) @ app ---
9. >>> Press ENTER to exit <<<
10. [INFO] [05/11/2017 14:07:20.790] [helloakka-akka.actor.default-dispatcher-2] [akka://helloakka/user/printerActor] Hello, Java
11. [INFO] [05/11/2017 14:07:20.791] [helloakka-akka.actor.default-dispatcher-2] [akka://helloakka/user/printerActor] Good day, Play
12. [INFO] [05/11/2017 14:07:20.791] [helloakka-akka.actor.default-dispatcher-2] [akka://helloakka/user/printerActor] Howdy, Akka
13. [INFO] [05/11/2017 14:07:20.791] [helloakka-akka.actor.default-dispatcher-2] [akka://helloakka/user/printerActor] Howdy, Lightbend

15. // Grade
16. :compileJava UP-TO-DATE
17. :processResources NO-SOURCE
18. :classes UP-TO-DATE
19. :run
20. >>> Press ENTER to exit <<<
21. [INFO] [05/11/2017 14:08:22.884] [helloakka-akka.actor.default-dispatcher-2] [akka://helloakka/user/printerActor] Howdy, Akka
22. [INFO] [05/11/2017 14:08:22.884] [helloakka-akka.actor.default-dispatcher-2] [akka://helloakka/user/printerActor] Good day, Play
23. [INFO] [05/11/2017 14:08:22.884] [helloakka-akka.actor.default-dispatcher-2] [akka://helloakka/user/printerActor] Hello, Java
24. [INFO] [05/11/2017 14:08:22.884] [helloakka-akka.actor.default-dispatcher-2] [akka://helloakka/user/printerActor] Howdy, Lightbend
25. <=========----> 75% EXECUTING
26. > :run

恭喜你,你刚刚运行了你的第一个 Akka 应用程序。

Hello World 都做了什么?

正如你在控制台输出中看到的,该示例输出了一些问候语。让我们看看运行时都发生了什么。

mainclass-actorsystem

首先,主函数main创建了一个akka.actor.ActorSystem,它是一个运行Actors的容器。接下来,它创建了三个Greeter Actor实例和一个Printer Actor实例。

然后,该示例将消息发送给Greeter Actor实例,后者在内部存储消息。最后,发送给Greeter Actor的指令消息触发它们向Printer Actor发送消息,Printer Actor将消息输出到控制台:

mainclass-hhgp

Akka 对 Actor 和异步消息传递的使用带来了一系列好处。大家可以考虑一下都带来了什么好处?

使用 Actor 模型的好处

Akka 的以下特性使你能够以直观的方式解决困难的并发性和可伸缩性挑战:

  • 事件驱动模型:Event-driven model,Actor 通过响应消息来执行工作。Actor 之间的通信是异步的,允许 Actor 发送消息并继续自己的工作,而不是阻塞等待响应。
  • 强隔离原则:Strong isolation principles,与 Java 中的常规对象不同,Actor 在调用的方法方面,没有一个公共 API。相反,它的公共 API 是通过 Actor 处理的消息来定义的。这可以防止 Actor 之间共享状态;观察另一个 Actor 状态的唯一方法是向其发送请求状态的消息。
  • 位置透明:Location transparency,系统通过工厂方法构造 Actor 并返回对实例的引用。因为位置无关紧要,所以 Actor 实例可以启动、停止、移动和重新启动,以向上和向下扩展以及从意外故障中恢复。
  • 轻量级:Lightweight,每个实例只消耗几百个字节,这实际上允许数百万并发 Actor 存在于一个应用程序中。

让我们看看在Hello World示例中使用 Actor 和消息一起工作的一些最佳实践。

定义 Actor 和消息

消息可以是任意类型(Object的任何子类型),你可以将装箱类型(如String、Integer、Boolean等)作为消息发送,也可以将普通数据结构(如数组和集合类型)作为消息发送。

Hello World的 Actor 使用三种不同的消息:

  • WhoToGreet:问候消息的接受者;
  • Greet:执行问候的指令;
  • Greeting:包含问候语的消息。

在定义 Actor 及其消息时,请记住以下建议:

  • 因为消息是 Actor 的公共 API,所以定义具有良好名称、丰富语义和特定于域的含义的消息是一个很好的实践,即使它们只是包装你的数据类型,这将使基于 Actor 的系统更容易使用、理解和调试。
  • 消息应该是不可变的,因为它们在不同的线程之间共享。
  • 将 Actor 的关联消息作为静态类放在 Actor 的类中是一个很好的实践,这使得理解 Actor 期望和处理的消息类型更加容易。
  • 在 Actor 类中使用静态props方法来描述如何构造 Actor 也是一种常见的模式。

让我们看看 Actor 如何实现Greeter和Printer来演示这些最佳实践。

Greeter Actor

下面的代码段来自于Greeter.java,其实现了Greeter Actor:

1. package com.lightbend.akka.sample;

3. import akka.actor.AbstractActor;
4. import akka.actor.ActorRef;
5. import akka.actor.Props;
6. import com.lightbend.akka.sample.Printer.Greeting;

8. public class Greeter extends AbstractActor {
9. static public Props props(String message, ActorRef printerActor) {
10. return Props.create(Greeter.class, () -> new Greeter(message, printerActor));
11. }

13. static public class WhoToGreet {
14. public final String who;

16. public WhoToGreet(String who) {
17. this.who = who;
18. }
19. }

21. static public class Greet {
22. public Greet() {
23. }
24. }

26. private final String message;
27. private final ActorRef printerActor;
28. private String greeting = "";

30. public Greeter(String message, ActorRef printerActor) {
31. this.message = message;
32. this.printerActor = printerActor;
33. }

35. @Override
36. public Receive createReceive() {
37. return receiveBuilder()
38. .match(WhoToGreet.class, wtg -> {
39. this.greeting = message + ", " + wtg.who;
40. })
41. .match(Greet.class, x -> {
42. printerActor.tell(new Greeting(greeting), getSelf());
43. })
44. .build();
45. }
46. }

让我们来解析上面的功能:

  • Greeter类扩展了akka.actor.AbstractActor类并实现了createReceive方法。
  • Greeter构造函数接受两个参数:String message,它将在构建问候语时使用,ActorRef printerActor是处理问候语输出的 Actor 的引用。
  • receiveBuilder定义了行为;Actor 应该如何响应它接收到的不同消息。Actor 可以有状态。访问或改变 Actor 的内部状态是线程安全的,因为它受 Actor 模型的保护。createReceive方法应处理 Actor 期望的消息。对于Greeter,它需要两种类型的消息:WhoToGreet和Greet,前者将更新 Actor 的问候语状态,后者将触发向Printer Actor发送问候语。
  • greeting变量包含 Actor 的状态,默认设置为""。
  • 静态props方法创建并返回Props实例。Props是一个配置类,用于指定创建 Actor 的选项,将其视为不可变的,因此可以自由共享用于创建包含相关部署信息的 Actor 的方法。这个例子简单地传递了 Actor 在构造时需要的参数。我们将在本教程的后面部分看到props方法的实际应用。

Printer Actor

Printer的实现非常简单:

  • 它通过Logging.getLogger(getContext().getSystem(), this);创建一个日志器。通过这样做,我们可以在 Actor 中编写log.info() ,而不需要任何额外的连接。
  • 它只处理一种类型的消息Greeting,并记录该消息的内容。
1. package com.lightbend.akka.sample;

3. import akka.actor.AbstractActor;
4. import akka.actor.Props;
5. import akka.event.Logging;
6. import akka.event.LoggingAdapter;

8. public class Printer extends AbstractActor {
9. static public Props props() {
10. return Props.create(Printer.class, () -> new Printer());
11. }

13. static public class Greeting {
14. public final String message;

16. public Greeting(String message) {
17. this.message = message;
18. }
19. }

21. private LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this);

23. public Printer() {
24. }

26. @Override
27. public Receive createReceive() {
28. return receiveBuilder()
29. .match(Greeting.class, greeting -> {
30. log.info(greeting.message);
31. })
32. .build();
33. }
34. }

创建 Actor

到目前为止,我们已经研究了 Actor 的定义和他们的消息。现在,让我们更深入地了解位置透明(location transparency)的好处,看看如何创建 Actor 实例。

位置透明的好处

在 Akka 中,不能使用new关键字创建 Actor 的实例。相反,你应该使用工厂方法创建 Actor 实例。工厂不返回 Actor 实例,而是返回指向 Actor 实例的引用akka.actor.ActorRef。在分布式系统中,这种间接创建实例的方法增加了很多好处和灵活性。

在 Akka 中位置无关紧要。位置透明性意味着,无论是在正在运行 Actor 的进程内,还是运行在远程计算机上,ActorRef都可以保持相同语义。如果需要,运行时可以通过更改 Actor 的位置或整个应用程序拓扑来优化系统。这就启用了故障管理的“让它崩溃(let it crash)”模型,在该模型中,系统可以通过销毁有问题的 Actor 和重新启动健康的 Actor 来自我修复。

Akka ActorSystem

akka.actor.ActorSystem工厂在某种程度上类似于 Spring 的 BeanFactory,它是运行 Actor 的容器并管理他们的生命周期。actorOf工厂方法创建 Actor 并接受两个参数,一个名为props的配置对象和一个String类型的 Actor 名称。

Actor 和 ActorSystem 的名字在 Akka 中很重要。例如,使用它们进行查找。使用与你的域模型(domain model)一致的有意义的名称可以更容易地对它们进行推理。

前面我们看了Hello World的 Actor 定义。现在,让我们看看AkkaQuickstart.java文件中创建 Greeter Actor 和 Printer Actor 实例的代码:

1. final ActorRef printerActor =
2. system.actorOf(Printer.props(), "printerActor");
3. final ActorRef howdyGreeter =
4. system.actorOf(Greeter.props("Howdy", printerActor), "howdyGreeter");
5. final ActorRef helloGreeter =
6. system.actorOf(Greeter.props("Hello", printerActor), "helloGreeter");
7. final ActorRef goodDayGreeter =
8. system.actorOf(Greeter.props("Good day", printerActor), "goodDayGreeter");

注意以下事项:

  • 使用 ActorSystem 上的actorOf方法创建 Printer Actor。正如我们在前面讨论的,它使用了Printer类的静态props方法来获取Props值。ActorRef 提供了对新创建的 Printer Actor 实例的引用。
  • 对于Greeter,代码创建三个 Actor 实例,每个实例都有一个特定的问候语。

注意:在本例中,Greeter Actor 都使用了相同的 Printer 实例,但我们可以创建多个 Printer Actor 实例。示例中使用一个实例来说明稍后我们将讨论的消息传递(message passing)的一个重要概念。

接下来,我们来看看如何与 Actor 通信。

异步通信

Actor 是被动的和消息驱动的。Actor 在收到消息前什么都不做。Actor 使用异步消息进行通信。这样可以确保发送者不会一直等待接收者处理他们的消息。相反,发件人将邮件放在收件人的邮箱之后,就可以自由地进行其他工作。Actor 的邮箱本质上是一个具有排序语义的消息队列。从同一个 Actor 发送的多条消息的顺序被保留,但可以与另一个 Actor 发送的消息交错。

你可能想知道 Actor 在不处理消息的时候在做什么,比如,做什么实际的工作?实际上,它处于挂起状态,在这种状态下,它不消耗除内存之外的任何资源。同样,这也展示了 Actor 的轻量级和高效性。

给 Actor 发生消息

要将消息放入 Actor 的邮箱,我们需要使用ActorRef的tell方法。例如,Hello World的主函数main向 Greeter Actor 发送如下消息:

1. howdyGreeter.tell(new WhoToGreet("Akka"), ActorRef.noSender());
2. howdyGreeter.tell(new Greet(), ActorRef.noSender());

4. howdyGreeter.tell(new WhoToGreet("Lightbend"), ActorRef.noSender());
5. howdyGreeter.tell(new Greet(), ActorRef.noSender());

7. helloGreeter.tell(new WhoToGreet("Java"), ActorRef.noSender());
8. helloGreeter.tell(new Greet(), ActorRef.noSender());

10. goodDayGreeter.tell(new WhoToGreet("Play"), ActorRef.noSender());
11. goodDayGreeter.tell(new Greet(), ActorRef.noSender());

Greeter Actor 也向 Printer Actor 发送消息:

1. printerActor.tell(new Greeting(greeting), getSelf());

我们已经研究了如何创建 Actor 和发送消息。现在,让我们来回顾一下Main类的全部内容。

Main class

Hello World的 Main类创建和控制 Actor。注意,使用ActorSystem作为容器,并使用actorOf方法创建 Actor。最后,类创建要发送给 Actor 的消息。

1. package com.lightbend.akka.sample;

3. import java.io.IOException;

5. import com.lightbend.akka.sample.Greeter.Greet;
6. import com.lightbend.akka.sample.Greeter.WhoToGreet;

8. import akka.actor.ActorRef;
9. import akka.actor.ActorSystem;

11. public class AkkaQuickstart {
12. public static void main(String[] args) {
13. final ActorSystem system = ActorSystem.create("helloakka");
14. try {
15. final ActorRef printerActor =
16. system.actorOf(Printer.props(), "printerActor");
17. final ActorRef howdyGreeter =
18. system.actorOf(Greeter.props("Howdy", printerActor), "howdyGreeter");
19. final ActorRef helloGreeter =
20. system.actorOf(Greeter.props("Hello", printerActor), "helloGreeter");
21. final ActorRef goodDayGreeter =
22. system.actorOf(Greeter.props("Good day", printerActor), "goodDayGreeter");

24. howdyGreeter.tell(new WhoToGreet("Akka"), ActorRef.noSender());
25. howdyGreeter.tell(new Greet(), ActorRef.noSender());

27. howdyGreeter.tell(new WhoToGreet("Lightbend"), ActorRef.noSender());
28. howdyGreeter.tell(new Greet(), ActorRef.noSender());

30. helloGreeter.tell(new WhoToGreet("Java"), ActorRef.noSender());
31. helloGreeter.tell(new Greet(), ActorRef.noSender());

33. goodDayGreeter.tell(new WhoToGreet("Play"), ActorRef.noSender());
34. goodDayGreeter.tell(new Greet(), ActorRef.noSender());

36. System.out.println(">>> Press ENTER to exit <<<");
37. System.in.read();
38. } catch (IOException ioe) {
39. } finally {
40. system.terminate();
41. }
42. }
43. }

类似地,让我们再次看看定义 Actor 和他们接受的消息的完整源代码。

完整代码

下面是创建示例应用程序的三个类Greeter、Printer和AkkaQuickstart的完整源代码:

Greater.java

1. package com.lightbend.akka.sample;

3. import akka.actor.AbstractActor;
4. import akka.actor.ActorRef;
5. import akka.actor.Props;
6. import com.lightbend.akka.sample.Printer.Greeting;

8. public class Greeter extends AbstractActor {
9. static public Props props(String message, ActorRef printerActor) {
10. return Props.create(Greeter.class, () -> new Greeter(message, printerActor));
11. }

13. static public class WhoToGreet {
14. public final String who;

16. public WhoToGreet(String who) {
17. this.who = who;
18. }
19. }

21. static public class Greet {
22. public Greet() {
23. }
24. }

26. private final String message;
27. private final ActorRef printerActor;
28. private String greeting = "";

30. public Greeter(String message, ActorRef printerActor) {
31. this.message = message;
32. this.printerActor = printerActor;
33. }

35. @Override
36. public Receive createReceive() {
37. return receiveBuilder()
38. .match(WhoToGreet.class, wtg -> {
39. this.greeting = message + ", " + wtg.who;
40. })
41. .match(Greet.class, x -> {
42. printerActor.tell(new Greeting(greeting), getSelf());
43. })
44. .build();
45. }
46. }

Printer.java

1. package com.lightbend.akka.sample;

3. import akka.actor.AbstractActor;
4. import akka.actor.Props;
5. import akka.event.Logging;
6. import akka.event.LoggingAdapter;

8. public class Printer extends AbstractActor {
9. static public Props props() {
10. return Props.create(Printer.class, () -> new Printer());
11. }

13. static public class Greeting {
14. public final String message;

16. public Greeting(String message) {
17. this.message = message;
18. }
19. }

21. private LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this);

23. public Printer() {
24. }

26. @Override
27. public Receive createReceive() {
28. return receiveBuilder()
29. .match(Greeting.class, greeting -> {
30. log.info(greeting.message);
31. })
32. .build();
33. }
34. }

AkkaQuickstart.java

1. package com.lightbend.akka.sample;

3. import java.io.IOException;

5. import com.lightbend.akka.sample.Greeter.Greet;
6. import com.lightbend.akka.sample.Greeter.WhoToGreet;

8. import akka.actor.ActorRef;
9. import akka.actor.ActorSystem;

11. public class AkkaQuickstart {
12. public static void main(String[] args) {
13. final ActorSystem system = ActorSystem.create("helloakka");
14. try {
15. final ActorRef printerActor =
16. system.actorOf(Printer.props(), "printerActor");
17. final ActorRef howdyGreeter =
18. system.actorOf(Greeter.props("Howdy", printerActor), "howdyGreeter");
19. final ActorRef helloGreeter =
20. system.actorOf(Greeter.props("Hello", printerActor), "helloGreeter");
21. final ActorRef goodDayGreeter =
22. system.actorOf(Greeter.props("Good day", printerActor), "goodDayGreeter");

24. howdyGreeter.tell(new WhoToGreet("Akka"), ActorRef.noSender());
25. howdyGreeter.tell(new Greet(), ActorRef.noSender());

27. howdyGreeter.tell(new WhoToGreet("Lightbend"), ActorRef.noSender());
28. howdyGreeter.tell(new Greet(), ActorRef.noSender());

30. helloGreeter.tell(new WhoToGreet("Java"), ActorRef.noSender());
31. helloGreeter.tell(new Greet(), ActorRef.noSender());

33. goodDayGreeter.tell(new WhoToGreet("Play"), ActorRef.noSender());
34. goodDayGreeter.tell(new Greet(), ActorRef.noSender());

36. System.out.println(">>> Press ENTER to exit <<<");
37. System.in.read();
38. } catch (IOException ioe) {
39. } finally {
40. system.terminate();
41. }
42. }
43. }

作为另一个最佳实践,我们应该提供一些单元测试。

测试 Actor

Hello World示例中的测试展示了 JUnit 框架的使用。虽然测试的覆盖范围不完整,但它简单地展示了测试 Actor 代码是多么的容易,并提供了一些基本概念。你可以把它作为一个练习来增加你自己的知识。

测试类使用的是akka.test.javadsl.TestKit,它是用于 Actor 和 Actor 系统集成测试的模块。这个类只使用了TestKit提供的一部分功能。

集成测试可以帮助我们确保 Actor 的行为是异步的。第一个测试使用TestKit探针来询问和验证预期的行为。让我们看看源代码片段:

1. package com.lightbend.akka.sample;

3. import static org.junit.Assert.assertEquals;

5. import org.junit.AfterClass;
6. import org.junit.BeforeClass;
7. import org.junit.Test;

9. import com.lightbend.akka.sample.Greeter.Greet;
10. import com.lightbend.akka.sample.Greeter.WhoToGreet;
11. import com.lightbend.akka.sample.Printer.Greeting;

13. import akka.actor.ActorRef;
14. import akka.actor.ActorSystem;
15. import akka.testkit.javadsl.TestKit;

17. public class AkkaQuickstartTest {
18. static ActorSystem system;

20. @BeforeClass
21. public static void setup() {
22. system = ActorSystem.create();
23. }

25. @AfterClass
26. public static void teardown() {
27. TestKit.shutdownActorSystem(system);
28. system = null;
29. }

31. @Test
32. public void testGreeterActorSendingOfGreeting() {
33. final TestKit testProbe = new TestKit(system);
34. final ActorRef helloGreeter = system.actorOf(Greeter.props("Hello", testProbe.getRef()));
35. helloGreeter.tell(new WhoToGreet("Akka"), ActorRef.noSender());
36. helloGreeter.tell(new Greet(), ActorRef.noSender());
37. Greeting greeting = testProbe.expectMsgClass(Greeting.class);
38. assertEquals("Hello, Akka", greeting.message);
39. }
40. }

一旦我们引用了TestKit探针,我们就将它的ActorRef作为构造函数参数的一部分传递给Greeter。然后,我们向Greeter发送两条信息:一条是设置问候语,另一条是触发Greeting的发送。TestKit的expectMsg方法验证是否发送了消息。

示例代码只涉及了TestKit功能的一小部分,在「这里」可以找到更完整的概述。

现在我们已经检查了所有代码。让我们再次运行该示例并查看其输出。

运行应用程序

你可以通过命令行或者 IDE 来运行Hello World应用程序。在本指南的最后一个主题,我们描述了如何在 IntelliJ IDEA 中运行该示例。但是,在我们再次运行应用程序之前,让我们先快速的查看构建文件。

  • Maven POM 文件
1. <project>
2. <modelVersion>4.0.0</modelVersion>

4. <groupId>hello-akka-java</groupId>
5. <artifactId>app</artifactId>
6. <version>1.0</version>

8. <properties>
9. <akka.version>$akka_version$</akka.version>
10. </properties>

12. <dependencies>
13. <dependency>
14. <groupId>com.typesafe.akka</groupId>
15. <artifactId>akka-actor_2.12</artifactId>
16. <version>\${akka.version}</version>
17. </dependency>
18. <dependency>
19. <groupId>com.typesafe.akka</groupId>
20. <artifactId>akka-testkit_2.12</artifactId>
21. <version>\${akka.version}</version>
22. <scope>test</scope>
23. </dependency>
24. <dependency>
25. <groupId>junit</groupId>
26. <artifactId>junit</artifactId>
27. <version>4.12</version>
28. <scope>test</scope>
29. </dependency>
30. </dependencies>
31. <build>
32. <plugins>
33. <plugin>
34. <groupId>org.apache.maven.plugins</groupId>
35. <artifactId>maven-compiler-plugin</artifactId>
36. <version>3.5.1</version>
37. <configuration>
38. <source>1.8</source>
39. <target>1.8</target>
40. </configuration>
41. </plugin>
42. <plugin>
43. <groupId>org.codehaus.mojo</groupId>
44. <artifactId>exec-maven-plugin</artifactId>
45. <version>1.6.0</version>
46. <configuration>
47. <executable>java</executable>
48. <arguments>
49. <argument>-classpath</argument>
50. <classpath />
51. <argument>com.lightbend.akka.sample.AkkaQuickstart</argument>
52. </arguments>
53. </configuration>
54. </plugin>
55. </plugins>
56. </build>
57. </project>
  • Grade 构建文件
1. apply plugin: 'java'
2. apply plugin: 'idea'
3. apply plugin: 'application'

6. repositories {
7. mavenCentral()
8. mavenLocal()
9. }

11. dependencies {
12. compile 'com.typesafe.akka:akka-actor_2.12:$akka_version$'
13. testCompile 'com.typesafe.akka:akka-testkit_2.12:$akka_version$'
14. testCompile 'junit:junit:4.12'
15. }

17. mainClassName = "com.lightbend.akka.sample.AkkaQuickstart"

19. run {
20. standardInput = System.in
21. }

运行项目

和前面一样,从控制台运行应用程序:

1. //  Maven
2. $ mvn compile exec:exec

4. // Grade
5. $ gradle run

输出应该如下所示(一直向右滚动以查看 Actor 输出):

1. // Maven
2. [INFO] Scanning for projects...
3. [INFO]
4. [INFO] ------------------------------------------------------------------------
5. [INFO] Building app 1.0
6. [INFO] ------------------------------------------------------------------------
7. [INFO]
8. [INFO] --- exec-maven-plugin:1.6.0:exec (default-cli) @ app ---
9. >>> Press ENTER to exit <<<
10. [INFO] [05/11/2017 14:07:20.790] [helloakka-akka.actor.default-dispatcher-2] [akka://helloakka/user/printerActor] Hello, Java
11. [INFO] [05/11/2017 14:07:20.791] [helloakka-akka.actor.default-dispatcher-2] [akka://helloakka/user/printerActor] Good day, Play
12. [INFO] [05/11/2017 14:07:20.791] [helloakka-akka.actor.default-dispatcher-2] [akka://helloakka/user/printerActor] Howdy, Akka
13. [INFO] [05/11/2017 14:07:20.791] [helloakka-akka.actor.default-dispatcher-2] [akka://helloakka/user/printerActor] Howdy, Lightbend

15. // Grade
16. :compileJava UP-TO-DATE
17. :processResources NO-SOURCE
18. :classes UP-TO-DATE
19. :run
20. >>> Press ENTER to exit <<<
21. [INFO] [05/11/2017 14:08:22.884] [helloakka-akka.actor.default-dispatcher-2] [akka://helloakka/user/printerActor] Howdy, Akka
22. [INFO] [05/11/2017 14:08:22.884] [helloakka-akka.actor.default-dispatcher-2] [akka://helloakka/user/printerActor] Good day, Play
23. [INFO] [05/11/2017 14:08:22.884] [helloakka-akka.actor.default-dispatcher-2] [akka://helloakka/user/printerActor] Hello, Java
24. [INFO] [05/11/2017 14:08:22.884] [helloakka-akka.actor.default-dispatcher-2] [akka://helloakka/user/printerActor] Howdy, Lightbend
25. <=========----> 75% EXECUTING
26. > :run

还记得我们设置 Printer Actor 使用 Akka 的 Logger 吗?这就是为什么我们记录东西时会有很多额外的信息。日志输出包含诸如何时和从哪个 Actor 记录日志之类的信息。现在,让我们将重点放在 Printer Actor 的输出上:

1. ... Howdy, Akka
2. ... Hello, Java
3. ... Good day, Play
4. ... Howdy, Lightbend

这是我们的代码向 Greeter Actor 发送消息的结果:

1. howdyGreeter.tell(new WhoToGreet("Akka"), ActorRef.noSender());
2. howdyGreeter.tell(new Greet(), ActorRef.noSender());

4. howdyGreeter.tell(new WhoToGreet("Lightbend"), ActorRef.noSender());
5. howdyGreeter.tell(new Greet(), ActorRef.noSender());

7. helloGreeter.tell(new WhoToGreet("Java"), ActorRef.noSender());
8. helloGreeter.tell(new Greet(), ActorRef.noSender());

10. goodDayGreeter.tell(new WhoToGreet("Play"), ActorRef.noSender());
11. goodDayGreeter.tell(new Greet(), ActorRef.noSender());

为了执行单元测试,我们输入test命令:

1. //  Maven
2. $ mvn test

4. // Grade
5. $ gradle test

尝试多次运行代码,并观察日志的顺序,你注意到它们的输入顺序发生变化了吗?这里发生了什么?异步行为变得很明显。这可能是你的一个新思维模式。但是,一旦你获得了使用它的经验,一切都会变得清晰;就像「Neo in the Matrix」一样。

下一步

如果你使用 IntelliJ,请尝试将示例项目与 IntelliJ IDEA 集成。

想要继续了解更多有关 Akka 和 Actor Systems 的信息,请参阅「Getting Started Guide」,欢迎你加入我们!

IntelliJ IDEA

JetBrains 的 IntelliJ 是 Java/Scala 社区中领先的 IDE 之一,它对 Akka 有着极好的支持。本节将指导你完成示例项目的设置、测试和运行。

设置项目

设置项目很简单。打开 IntelliJ 并选择File -> Open...并指向你安装示例项目的目录。

检查项目代码

如果我们打开文件src/main/java/com/lightbend/akka/sample/HelloAkka.java,我们将看到许多行以//# ...开头,作为文档的注释。为了从源代码中去掉这些行,我们可以在 IntelliJ 中使用出色的查找/替换功能。选择Edit -> Find -> Replace in Path...,选中Regex框并添加[//#].*正则表达式,然后单击查找窗口中的Replace in Find Window...。选择想要替换的内容,并对所有文件重复此操作。

测试和运行

对于测试,我们只需右键单击文件src/test/java/com/lightbend/akka/sample/HelloAkkaTest.java,然后选择Run 'HelloAkkaTest'。

类似地运行应用程序,我们右击文件src/main/java/com/lightbend/akka/sample/HelloAkka.java,并选择Run 'HelloAkka.main()'。

有关更多详细信息,请参阅「运行应用程序」部分。

想要进一步了解 IntelliJ IDEA,可以参阅「史上最简单的 IntelliJ IDEA 教程」系列文章。


英文原文链接:Akka Quickstart with Java.