我们来以一个简单示例来演示一下 mina-statemachine 是如何工作的。下图演示了一个典型的录音机的状态机。圆圈表示状态,箭头表示转换。每个转换以一个触发该转换的事件名作为标签。
首先,录音机处于 Empty 状态。插入一个磁带后 load 事件被触发,录音机转为 Loaded 状态。在 Loaded 状态中,eject 事件将会触发其移回 Empty 状态,而 play 事件则会触发其移向 Playing 状态。以此类推...我认为你可以判断出剩下的逻辑。
现在写一些代码。外界将会只能看到 TapeDeck 接口:public interface TapeDeck { void load(String nameOfTape); void eject(); void start(); void pause(); void stop(); }
public class TapeDeckHandler { @State public static final String EMPTY = "Empty"; @State public static final String LOADED = "Loaded"; @State public static final String PLAYING = "Playing"; @State public static final String PAUSED = "Paused"; }
public class TapeDeckHandler { @State public static final String EMPTY = "Empty"; @State public static final String LOADED = "Loaded"; @State public static final String PLAYING = "Playing"; @State public static final String PAUSED = "Paused"; @Transition(on = "load", in = EMPTY, next = LOADED) public void loadTape(String nameOfTape) { System.out.println("Tape '" + nameOfTape + "' loaded"); } @Transitions({ @Transition(on = "play", in = LOADED, next = PLAYING), @Transition(on = "play", in = PAUSED, next = PLAYING) }) public void playTape() { System.out.println("Playing tape"); } @Transition(on = "pause", in = PLAYING, next = PAUSED) public void pauseTape() { System.out.println("Tape paused"); } @Transition(on = "stop", in = PLAYING, next = LOADED) public void stopTape() { System.out.println("Tape stopped"); } @Transition(on = "eject", in = LOADED, next = EMPTY) public void ejectTape() { System.out.println("Tape ejected"); } }
@Transition(on = "load", in = EMPTY, next = LOADED) public void loadTape(String nameOfTape) {
@Transitions({ @Transition(on = "play", in = LOADED, next = PLAYING), @Transition(on = "play", in = PAUSED, next = PLAYING) }) public void playTape() {
public static void main(String[] args) { TapeDeckHandler handler = new TapeDeckHandler(); StateMachine sm = StateMachineFactory.getInstance(Transition.class).create(TapeDeckHandler.EMPTY, handler); TapeDeck deck = new StateMachineProxyBuilder().create(TapeDeck.class, sm); deck.load("The Knife - Silent Shout"); deck.play(); deck.pause(); deck.play(); deck.stop(); deck.eject(); }
TapeDeckHandler handler = new TapeDeckHandler(); StateMachine sm = StateMachineFactory.getInstance(Transition.class).create(TapeDeckHandler.EMPTY, handler);
TapeDeck deck = new StateMachineProxyBuilder().create(TapeDeck.class, sm);
Tape 'The Knife - Silent Shout' loaded Playing tape Tape paused Playing tape Tape stopped Tape ejected
@Transition(on = "foo") public void someMethod(One one, Two two, Three three) { ... }
someMethod(a, b, c);
@Transition(on = "foo") public void someMethod(Two two) { ... }
@Transition(on = "foo") public void someMethod() { ... }
@Transition(on = "foo") public void someMethod(Event event, StateContext context, One one, Two two, Three three) { ... } @Transition(on = "foo") public void someMethod(Event event, One one, Two two, Three three) { ... } @Transition(on = "foo") public void someMethod(StateContext context, One one, Two two, Three three) { ... }
@Transition(on = "foo") public void someMethod(MyStateContext context, Two two) { ... }
// All method arguments matches all event arguments directly @Transition(on = "messageReceived") public void messageReceived(ArrayList l, Integer i) { ... } // Matches since ((a instanceof List && b instanceof Number) == true) @Transition(on = "messageReceived") public void messageReceived(List l, Number n) { ... } // Matches since ((b instanceof Number) == true) @Transition(on = "messageReceived") public void messageReceived(Number n) { ... } // Methods with no arguments always matches @Transition(on = "messageReceived") public void messageReceived() { ... } // Methods only interested in the current Event or StateContext always matches @Transition(on = "messageReceived") public void messageReceived(StateContext context) { ... } // Matches since ((a instanceof Collection) == true) @Transition(on = "messageReceived") public void messageReceived(Event event, Collection c) { ... }
// Incorrect ordering @Transition(on = "messageReceived") public void messageReceived(Integer i, List l) { ... } // ((a instanceof LinkedList) == false) @Transition(on = "messageReceived") public void messageReceived(LinkedList l, Number n) { ... } // Event must be first argument @Transition(on = "messageReceived") public void messageReceived(ArrayList l, Event event) { ... } // StateContext must be second argument if Event is used @Transition(on = "messageReceived") public void messageReceived(Event event, ArrayList l, StateContext context) { ... } // Event must come before StateContext @Transition(on = "messageReceived") public void messageReceived(StateContext context, Event event) { ... }
@State public static final String A = "A"; @State(A) public static final String B = "A->B"; @State(A) public static final String C = "A->C"; @State(B) public static final String D = "A->B->D"; @State(C) public static final String E = "A->C->E";
public static void main(String[] args) { ... deck.load("The Knife - Silent Shout"); deck.play(); deck.pause(); deck.play(); deck.stop(); deck.eject(); deck.play(); } ... Tape stopped Tape ejected Exception in thread "main" o.a.m.sm.event.UnhandledEventException: Unhandled event: org.apache.mina.statemachine.event.Event@15eb0a9[id=play,...] at org.apache.mina.statemachine.StateMachine.handle(StateMachine.java:285) at org.apache.mina.statemachine.StateMachine.processEvents(StateMachine.java:142) ...
@Transitions({ @Transition(on = "*", in = EMPTY, weight = 100), @Transition(on = "*", in = LOADED, weight = 100), @Transition(on = "*", in = PLAYING, weight = 100), @Transition(on = "*", in = PAUSED, weight = 100) }) public void error(Event event) { System.out.println("Cannot '" + event.getId() + "' at this time"); }
... Tape stopped Tape ejected Cannot 'play' at this time.
public static class TapeDeckHandler { @State public static final String ROOT = "Root"; @State(ROOT) public static final String EMPTY = "Empty"; @State(ROOT) public static final String LOADED = "Loaded"; @State(ROOT) public static final String PLAYING = "Playing"; @State(ROOT) public static final String PAUSED = "Paused"; ... @Transition(on = "*", in = ROOT) public void error(Event event) { System.out.println("Cannot '" + event.getId() + "' at this time"); } }
telnet localhost 12345 S: + Greetings from your tape deck! C: list S: + (1: "The Knife - Silent Shout", 2: "Kings of convenience - Riot on an empty street") C: load 1 S: + "The Knife - Silent Shout" loaded C: play S: + Playing "The Knife - Silent Shout" C: pause S: + "The Knife - Silent Shout" paused C: play S: + Playing "The Knife - Silent Shout" C: info S: + Tape deck is playing. Current tape: "The Knife - Silent Shout" C: eject S: - Cannot eject while playing C: stop S: + "The Knife - Silent Shout" stopped C: eject S: + "The Knife - Silent Shout" ejected C: quit S: + Bye! Please come back!
@State public static final String ROOT = "Root"; @State(ROOT) public static final String EMPTY = "Empty"; @State(ROOT) public static final String LOADED = "Loaded"; @State(ROOT) public static final String PLAYING = "Playing"; @State(ROOT) public static final String PAUSED = "Paused";
@IoHandlerTransitions({ @IoHandlerTransition(on = MESSAGE_RECEIVED, in = LOADED, next = PLAYING), @IoHandlerTransition(on = MESSAGE_RECEIVED, in = PAUSED, next = PLAYING) }) public void playTape(TapeDeckContext context, IoSession session, PlayCommand cmd) { session.write("+ Playing \"" + context.tapeName + "\""); }
import static org.apache.mina.statemachine.event.IoHandlerEvents.*;
static class TapeDeckContext extends AbstractStateContext { public String tapeName; }
@IoHandlerTransition(on = MESSAGE_RECEIVED, in = EMPTY, next = LOADED) public void loadTape(TapeDeckContext context, IoSession session, LoadCommand cmd) { if (cmd.getTapeNumber() < 1 || cmd.getTapeNumber() > tapes.length) { session.write("- Unknown tape number: " + cmd.getTapeNumber()); StateControl.breakAndGotoNext(EMPTY); } else { context.tapeName = tapes[cmd.getTapeNumber() - 1]; session.write("+ \"" + context.tapeName + "\" loaded"); } }
StateControl.breakAndGotoNext(EMPTY);
@IoHandlerTransition(on = SESSION_OPENED, in = EMPTY) public void connect(IoSession session) { session.write("+ Greetings from your tape deck!"); }
@IoHandlerTransition(on = MESSAGE_RECEIVED, in = ROOT, weight = 10) public void error(Event event, StateContext context, IoSession session, Command cmd) { session.write("- Cannot " + cmd.getName() + " while " + context.getCurrentState().getId().toLowerCase()); }
private static IoHandler createIoHandler() { StateMachine sm = StateMachineFactory.getInstance(IoHandlerTransition.class).create(EMPTY, new TapeDeckServer()); return new StateMachineProxyBuilder().setStateContextLookup( new IoSessionStateContextLookup(new StateContextFactory() { public StateContext create() { return new TapeDeckContext(); } })).create(IoHandler.class, sm); } // This code will work with MINA 1.0/1.1: public static void main(String[] args) throws Exception { SocketAcceptor acceptor = new SocketAcceptor(); SocketAcceptorConfig config = new SocketAcceptorConfig(); config.setReuseAddress(true); ProtocolCodecFilter pcf = new ProtocolCodecFilter( new TextLineEncoder(), new CommandDecoder()); config.getFilterChain().addLast("codec", pcf); acceptor.bind(new InetSocketAddress(12345), createIoHandler(), config); } // This code will work with MINA trunk: public static void main(String[] args) throws Exception { SocketAcceptor acceptor = new NioSocketAcceptor(); acceptor.setReuseAddress(true); ProtocolCodecFilter pcf = new ProtocolCodecFilter( new TextLineEncoder(), new CommandDecoder()); acceptor.getFilterChain().addLast("codec", pcf); acceptor.setHandler(createIoHandler()); acceptor.setLocalAddress(new InetSocketAddress(PORT)); acceptor.bind(); }