一个Actor内部消息处理函数可以拥有多个不同的状态,在特点的状态下,可以对同一消息进行不同的处理,状态之间也可以任意切换。
现在模拟一个婴儿Baby,假设婴儿会拥有两种不同的状态,开心或者生气。当带他玩的时候,他总是表现出开心状态,当让他睡觉时,他就会非常生气。
在这个简单的场景模拟中,会给这个婴儿Actor发送睡觉和玩两种指令。如果婴儿正在生气,还让他睡觉,他就会说“我已经生气了”,如果你让他玩,他就会变得开心。同样,如果他玩的正高兴,你让他继续玩,他就会说“我已经很愉快了”,如果让他睡觉,他马上变得生气。
下面的这个BabyActor模拟了上述场景:
public class BabyActor extends UntypedActor {
private final LoggingAdapter log = Logging.getLogger(getContext().system(), this);
public static enum Msg {
SLEEP,PLAY,CLOSE;
}
Procedure<Object> angry = new Procedure<Object>() {
@Override
public void apply(Object message) throws Exception {
System.out.println("angryApply:" + message);
if(message == Msg.SLEEP) {
getSender().tell("I am already angry", getSelf());
System.out.println("I am already angry");
} else if(message == Msg.PLAY) {
System.out.println("I like playing");
getContext().become(happy);
}
}
};
Procedure<Object> happy = new Procedure<Object>() {
@Override
public void apply(Object message) throws Exception {
System.out.println("happyApply:" + message);
if(message == Msg.PLAY) {
getSender().tell("I am already happy :-)", getSelf());
System.out.println("I am already happy :-)");
} else if(message == Msg.SLEEP) {
System.out.println("I don't want to sleep");
getContext().become(angry);
}
}
};
@Override
public void onReceive(Object msg) throws Exception {
System.out.println("onReceive:" + msg);
if(msg == Msg.SLEEP) {
getContext().become(angry);
} else if(msg == Msg.PLAY) {
getContext().become(happy);
} else {
unhandled(msg);
}
}
}
上述代码中,使用了become()方法用于切换Actor的状态。方法become()接收一个Procedure参数。Procedure在这里表示一种Actor的状态,同时,更重要的是它封装了在这种状态下的消息处理逻辑。
在这个BabyActor既没有生气也不开心。因此angry处理函数和happy处理函数都不会工作。当BabyActor接收到消息时,系统会调用onReceive()方法来处理这个消息。
当onReceive()处理SLEEP消息时,它会切换当前Actor的状态为angry。如果是PLAY消息,则切换状态为happy。
一旦完成切换状态,当后续有新的消息送达时,就不会再由onReceive()函数处理了。由于angry和happy本身就是消息处理函数。因此,后续的消息就直接交由当前状态处理(angry或者happy),从而很好地封装了Actor的多个不同处理逻辑。
下面的代码向婴儿Actor发送了几条PLAY和SLEEP的消息:
ActorSystem system = ActorSystem.create("become", ConfigFactory.load("samplehello.conf")); ActorRef child = system.actorOf(Props.create(BabyActor.class), "baby"); system.actorOf(Props.create(WatchActor_Router.class), "watcher"); child.tell(BabyActor.Msg.PLAY, ActorRef.noSender()); child.tell(BabyActor.Msg.SLEEP, ActorRef.noSender()); child.tell(BabyActor.Msg.PLAY, ActorRef.noSender()); child.tell(BabyActor.Msg.PLAY, ActorRef.noSender()); child.tell(PoisonPill.getInstance(), ActorRef.noSender());
其输出如下(进行过适量裁剪):
onReceive:PLAY
MyWorker is starting
happyApply:SLEEP
I don't want to sleep
angryApply:PLAY
I like playing
happyApply:PLAY
MyWorker is starting
MyWorker is starting
I am already happy :-)
可以看到,当地一个PLAY消息到来时,是由onReceive()函数进行处理的,在onReceive()中,将Actor切换为happy状态。因此,当SLEEP消息到达时,由happy.apply()函数处理,接着Actor切换为angry状态。当PLAY消息再次到达时,由angry.apply()函数处理。由此可见,Akka为Actor提供了灵活的状态切换机制,处于不同状态的Actor可以绑定不同的消息处理函数进行消息处理,这对构造结构化应用有着重要的帮助。