7.9 Actor 的内置状态转换
在很多场景下,Actor的业务逻辑可能比较复杂,Actor可能需要根据不同的状态对同一条消息作出不同的处理。Akka已经为我们考虑到了这一点,一个Actor内部消息处理函数可以拥有多个不同的状态,在特定的状态下,可以对同一消息进行不同的处理,状态之间也可以任意切换。
现在让我们模拟一个婴儿Actor,假设婴儿会拥有两种不同的状态,开心或者生气。当你带他玩的时候,他总是会表现出开心状态,当你让他睡觉时,他就会非常生气,小孩子总是拥有用不完的精力,入睡困难可能是一种通病吧!
在我们这个简单的场景模拟中,我们会给这个婴儿Actor发送睡觉和玩两种指令。如果婴儿正在生气,你还让他睡觉,他就会说“我已经生气了”,如果你让他去玩,他就会变得开心起来。同样,如果他正玩得高兴,你让他继续玩,他就会说“我已经很愉快了”,如果让他睡觉,他就马上变得很生气。
下面的这个BabyActor就模拟了上述场景:
01 public class BabyActor extends UntypedActor { 02 private final LoggingAdapter log = Logging.getLogger(getContext().system(), this); 03 public static enum Msg { 04 SLEEP, PLAY, CLOSE; 05 } 06 07 Procedure<Object> angry = new Procedure<Object>() { 08 @Override 09 public void apply(Object message) { 10 System.out.println("angryApply:"+message); 11 if (message == Msg.SLEEP) { 12 getSender().tell("I am already angry", getSelf()); 13 System.out.println("I am already angry"); 14 } else if (message == Msg.PLAY) { 15 System.out.println("I like playing"); 16 getContext().become(happy); 17 } 18 } 19 }; 20 21 Procedure<Object> happy = new Procedure<Object>() { 22 @Override 23 public void apply(Object message) { 24 System.out.println("happyApply:"+message); 25 if (message == Msg.PLAY) { 26 getSender().tell("I am already happy :-)", getSelf()); 27 System.out.println("I am already happy :-)"); 28 } else if (message == Msg.SLEEP) { 29 System.out.println("I don't want to sleep"); 30 getContext().become(angry); 31 } 32 } 33 }; 34 35 @Override 36 public void onReceive(Object msg) { 37 System.out.println("onReceive:"+msg); 38 if (msg == Msg.SLEEP) { 39 getContext().become(angry); 40 } else if (msg == Msg.PLAY) { 41 getContext().become(happy); 42 } else { 43 unhandled(msg); 44 } 45 } 46 }
上述代码中,使用了become()方法用于切换Actor的状态(第39、41行)。方法become()接收一个Procedure参数。Procedure在这里可以表示一种Actor的状态,同时,更重要的是它封装了在这种状态下的消息处理逻辑。
在这个BabyActor中,定义了两种Procedure,一是angry生气(第7行),另一个是happy开心(第21行)。
在初始状态下,BabyActor既没有生气也不开心。因此angry处理函数和happy处理函数都不会工作。当BabyActor接收到消息时,系统会调用onReceive()方法来处理这个消息。
令人吃惊的魔法就在这个onReceive()函数中。当onReceive()处理SLEEP消息时,它会切换当前Actor的状态为angry(第39行)。如果是PLAY消息,则切换状态为happy。
一旦完成状态切换,当后续有新的消息送达时,就不会再由onReceive()函数处理了。由于angry和happy本身就是消息处理函数。因此,后续的消息就直接交由当前状态处理(angry或者happy),从而很好地封装了Actor的多个不同处理逻辑。
下面的代码向我们的婴儿Actor发送了几条PLAY和SLEEP的消息:
1 ActorSystem system = ActorSystem.create("become", ConfigFactory.load("samplehello.conf")); 2 ActorRef child = system.actorOf(Props.create(BabyActor.class), "baby"); 3 system.actorOf(Props.create(WatchActor.class, child), "watcher"); 4 child.tell(BabyActor.Msg.PLAY, ActorRef.noSender()); 5 child.tell(BabyActor.Msg.SLEEP, ActorRef.noSender()); 6 child.tell(BabyActor.Msg.PLAY, ActorRef.noSender()); 7 child.tell(BabyActor.Msg.PLAY, ActorRef.noSender()); 8 9 child.tell(PoisonPill.getInstance(), ActorRef.noSender());
其输出如下(进行过适量裁剪):
onReceive:PLAY happyApply:SLEEP I don't want to sleep angryApply:PLAY I like playing happyApply:PLAY I am already happy :-) [INFO][akka://become/user/watcher] akka://become/user/baby has terminated, shutting down system
可以看到,当第一个PLAY消息到来时,是由onReceive()函数进行处理的,在onReceive()中,将Actor切换为happy状态。因此,当SLEEP消息达到时,由happy.apply()函数处理,接着Actor切换为angry状态。当PLAY消息再次到达时,由angry.apply()函数处理。由此可见,Akka为Actor提供了灵活的状态切换机制,处于不同状态的Actor可以绑定不同的消息处理函数进行消息处理,这对构造结构化应用有着重要的帮助。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论