首先,关于状态机的一个极度确切的描述是它是一个有向图形,由一组节点和一组相应的转移函数组成。状态机通过响应一系列事件而“运行”。每个事件都在属于 “当前”  节点的转移函数的控制范围内,其中函数的范围是节点的一个子集。函数返回“下一个”(也许是同一个)节点。这些节点中至少有一个必须是终态。当到达终态,  状态机停止。 
   接下来的问题是,我们为什么要用状态机,什么时候用: 
    
   传统应用程序的控制流程基本是顺序的:遵循事先设定的逻辑,从头到尾地执行。很少有事件能改变标准执行流程;而且这些事件主要涉及异常情况。“命令行实用程序”是这种传统应用程序的典型例子。 
    另一类应用程序由外部发生的事件来驱动——换言之,事件在应用程序之外生成,无法由应用程序或程序员来控制。具体需要执行的代码取决于接收到的事件,或者 它  相对于其他事件的抵达时间。所以,控制流程既不能是顺序的,也不能是事先设定好的,因为它要依赖于外部事件。事件驱动的GUI应用程序是这种应用程序的典  型例子,它们由命令和选择(也就是用户造成的事件)来驱动。 
   下面我们看个mina-statemachine的简单例子。  
   The picture below shows a state machine for a typical tape deck. The  ellipsis is the states while the arrows are the transitions. Each  transition is labeled with an event name which triggers that transition. 
   ![]() 
 
   状态机可归纳为4个要素,即现态、条件、动作、次态。 
   下面用代码来实现这一过程,首先我们定义动作接口: 
  | 01 | packagecom.a2.desktop.example9.mina.statemachine; | 
| 09 | publicinterfaceTapeDeck { | 
| 11 |     voidload(String nameOfTape); | 
 Tape Deck就是老式的录音机的意思。而动作接口,就相当于录音机外面的几个按钮。接下来我们定义状态: 
  | 01 | packagecom.a2.desktop.example9.mina.statemachine; | 
| 03 | importorg.apache.mina.statemachine.annotation.State; | 
| 04 | importorg.apache.mina.statemachine.annotation.Transition; | 
| 05 | importorg.apache.mina.statemachine.annotation.Transitions; | 
| 13 | publicclassTapeDeckHandler { | 
| 15 |     publicstaticfinalString EMPTY = "Empty"; | 
| 17 |     publicstaticfinalString LOADED = "Loaded"; | 
| 19 |     publicstaticfinalString PLAYING = "Playing"; | 
| 21 |     publicstaticfinalString PAUSED = "Paused"; | 
| 23 |     @Transition(on = "load", in = EMPTY, next = LOADED) | 
| 24 |     publicvoidloadTape(String nameOfTape) { | 
| 25 |         System.out.println("Tape '"+ nameOfTape + "' loaded"); | 
| 29 |         @Transition(on = "play", in = LOADED, next = PLAYING), | 
| 30 |         @Transition(on = "play", in = PAUSED, next = PLAYING) | 
| 32 |     publicvoidplayTape() { | 
| 33 |         System.out.println("Playing tape"); | 
| 36 |     @Transition(on = "pause", in = PLAYING, next = PAUSED) | 
| 37 |     publicvoidpauseTape() { | 
| 38 |         System.out.println("Tape paused"); | 
| 41 |     @Transition(on = "stop", in = PLAYING, next = LOADED) | 
| 42 |     publicvoidstopTape() { | 
| 43 |         System.out.println("Tape stopped"); | 
| 46 |     @Transition(on = "eject", in = LOADED, next = EMPTY) | 
| 47 |     publicvoidejectTape() { | 
| 48 |         System.out.println("Tape ejected"); | 
 状态即现态。在Transition Annotation中的on表示动作的ID,对应着动作接口中的方法名,in表示的是动作的起始状态,next表示的是动作的后续状态。 
   这里要注意以下几点: 
  | 1 | More about the @Transition parameters | 
| 2 | •   If you omit the on parameter it will defaultto "*"which will match any event. | 
| 3 | •   If you omit the next parameter it will defaultto "_self_"which is an alias for the current state. To create a loop transition in your state machine allyou have to do is to omit the next parameter. | 
| 4 | •    The weight parameter can be used to define in what order transitions  will be searched. Transitions for a particular state will be ordered in  ascending order according to their weight value. weight is 0by default. | 
 最后我们看一下测试类: 
  | 01 | packagecom.a2.desktop.example9.mina.statemachine; | 
| 03 | importorg.apache.mina.statemachine.StateMachine; | 
| 04 | importorg.apache.mina.statemachine.StateMachineFactory; | 
| 05 | importorg.apache.mina.statemachine.StateMachineProxyBuilder; | 
| 06 | importorg.apache.mina.statemachine.annotation.Transition; | 
| 10 |         publicstaticvoidmain(String[] args) { | 
| 12 |             TapeDeckHandler handler = newTapeDeckHandler(); | 
| 13 |             StateMachine sm = StateMachineFactory.getInstance(Transition.class).create(TapeDeckHandler.EMPTY, handler); | 
| 14 |             TapeDeck deck = newStateMachineProxyBuilder().create(TapeDeck.class, sm); | 
| 16 |             deck.load("Kiss Goodbye-Lee Hom"); | 
 运行结果正确,按着我们放置录音的想法来的。如果,我们调换其中的一个顺序,当然不符合我的逻辑: 
  | 1 | deck.load("Kiss Goodbye-Lee Hom"); | 
| 6 |             deck.pause();/**stop-->pause org.apache.mina.statemachine.event.UnhandledEventException*/ | 
 这样程序就报错了,所以mina的状态机帮我们很方便的调控了事件发生的状态。 
   我们来看一下mina状态机实现的一些细节: 
   1.    Lookup a StateContext Object 
          The StateContext object is important because it holds the current State. When a method is called on the proxy it will ask aStateContextLookup instance to get the StateContext from the method's arguments. Normally, the StateContextLookup implementation will loop through the method arguments and look for a particular type of object and use it to retrieve a StateContext object. If noStateContext has been assigned yet the StateContextLookup will create one and store it in the object. 
    
   2.    Convert the method invocation into an Event object 
   All method invocations on the proxy object will be translated into Event objects by the proxy. An Event has  an id and zero or more arguments. The id corresponds to the name of the  method and the event arguments correspond to the method arguments. The  method call deck.load("The Knife - Silent Shout") corresponds to the event {id = "load", arguments = ["The Knife - Silent Shout"]}. The Event object also contains a reference to the StateContext object looked up previously. 
   3.    Invoke the StateMachine 
   Once the Event object has been created the proxy will call StateMachine.handle(Event). StateMachine.handle(Event) loops through the Transition objects of the current State in search for a Transition instance which accepts the current Event. This process will stop after a Transition has been found. The Transition objects will be searched in order of weight (typically specified by the@Transition  annotation). 
   4.    Execute the Transition 
   The final step is to call Transition.execute(Event) on the Transition which matched the Event. After the Transition has been executed the StateMachine will update the current State with the end state defined by the Transition. 
   -------------------------------------------------------------------- 
   上面只是用mina实现了一个最简单的状态机,通过这个例子我们可以大概的了解到了状态机的执行过程,当然基于Annotation这样的方式我们用最基本的代码也能实现出来。接下来我们要把这样的方式用在通信中。用通信的方式来模拟录放机的按钮。 
    
   我们先看状态的定义: 
  | 001 | packagecom.a2.desktop.example10.mina.statemachine; | 
| 003 | importstaticorg.apache.mina.statemachine.event.IoHandlerEvents.EXCEPTION_CAUGHT; | 
| 004 | importstaticorg.apache.mina.statemachine.event.IoHandlerEvents.MESSAGE_RECEIVED; | 
| 005 | importstaticorg.apache.mina.statemachine.event.IoHandlerEvents.SESSION_OPENED; | 
| 007 | importorg.apache.mina.core.future.IoFutureListener; | 
| 008 | importorg.apache.mina.core.session.IoSession; | 
| 010 | importorg.apache.mina.statemachine.StateControl; | 
| 011 | importorg.apache.mina.statemachine.annotation.IoHandlerTransition; | 
| 012 | importorg.apache.mina.statemachine.annotation.IoHandlerTransitions; | 
| 013 | importorg.apache.mina.statemachine.annotation.State; | 
| 014 | importorg.apache.mina.statemachine.context.AbstractStateContext; | 
| 015 | importorg.apache.mina.statemachine.context.StateContext; | 
| 016 | importorg.apache.mina.statemachine.event.Event; | 
| 018 | publicclassTapeDeckServer { | 
| 021 |     publicstaticfinalString ROOT = "Root"; | 
| 024 |     publicstaticfinalString EMPTY = "Empty"; | 
| 026 |     publicstaticfinalString LOADED = "Loaded"; | 
| 028 |     publicstaticfinalString PLAYING = "Playing"; | 
| 030 |     publicstaticfinalString PAUSED = "Paused"; | 
| 032 |     privatefinalString[] tapes = { "盖世英雄-王力宏", "唯一-王力宏"}; | 
| 034 |     staticclassTapeDeckContext extendsAbstractStateContext { | 
| 035 |         publicString tapeName; | 
| 038 |      @IoHandlerTransition(on = SESSION_OPENED, in = EMPTY) | 
| 039 |         publicvoidconnect(IoSession session) { | 
| 040 |             session.write("+ Greetings from your tape deck!"); | 
| 043 |         @IoHandlerTransition(on = MESSAGE_RECEIVED, in = EMPTY, next = LOADED) | 
| 044 |         publicvoidloadTape(TapeDeckContext context, IoSession session, LoadCommand cmd) { | 
| 046 |             if(cmd.getTapeNumber() < 1|| cmd.getTapeNumber() > tapes.length) { | 
| 047 |                 session.write("- Unknown tape number: "+ cmd.getTapeNumber()); | 
| 048 |                 StateControl.breakAndGotoNext(EMPTY); | 
| 050 |                 context.tapeName = tapes[cmd.getTapeNumber() - 1]; | 
| 051 |                 session.write("+ \""+ context.tapeName + "\" loaded"); | 
| 055 |         @IoHandlerTransitions({ | 
| 056 |             @IoHandlerTransition(on = MESSAGE_RECEIVED, in = LOADED, next = PLAYING), | 
| 057 |             @IoHandlerTransition(on = MESSAGE_RECEIVED, in = PAUSED, next = PLAYING) | 
| 059 |         publicvoidplayTape(TapeDeckContext context, IoSession session, PlayCommand cmd) { | 
| 060 |             session.write("+ Playing \""+ context.tapeName + "\""); | 
| 063 |         @IoHandlerTransition(on = MESSAGE_RECEIVED, in = PLAYING, next = PAUSED) | 
| 064 |         publicvoidpauseTape(TapeDeckContext context, IoSession session, PauseCommand cmd) { | 
| 065 |             session.write("+ \""+ context.tapeName + "\" paused"); | 
| 068 |         @IoHandlerTransition(on = MESSAGE_RECEIVED, in = PLAYING, next = LOADED) | 
| 069 |         publicvoidstopTape(TapeDeckContext context, IoSession session, StopCommand cmd) { | 
| 070 |             session.write("+ \""+ context.tapeName + "\" stopped"); | 
| 073 |         @IoHandlerTransition(on = MESSAGE_RECEIVED, in = LOADED, next = EMPTY) | 
| 074 |         publicvoidejectTape(TapeDeckContext context, IoSession session, EjectCommand cmd) { | 
| 075 |             session.write("+ \""+ context.tapeName + "\" ejected"); | 
| 076 |             context.tapeName = null; | 
| 079 |         @IoHandlerTransition(on = MESSAGE_RECEIVED, in = ROOT) | 
| 080 |         publicvoidlistTapes(IoSession session, ListCommand cmd) { | 
| 081 |             StringBuilder response = newStringBuilder("+ ("); | 
| 082 |             for(inti = 0; i < tapes.length; i++) { | 
| 083 |                 response.append(i + 1).append(": "); | 
| 084 |                 response.append('"').append(tapes[i]).append('"'); | 
| 085 |                 if(i < tapes.length - 1) { | 
| 086 |                     response.append(", "); | 
| 089 |             response.append(')'); | 
| 090 |             session.write(response); | 
| 093 |         @IoHandlerTransition(on = MESSAGE_RECEIVED, in = ROOT) | 
| 094 |         publicvoidinfo(TapeDeckContext context, IoSession session, InfoCommand cmd) { | 
| 095 |             String state = context.getCurrentState().getId().toLowerCase(); | 
| 096 |             if(context.tapeName == null) { | 
| 097 |                 session.write("+ Tape deck is "+ state + ""); | 
| 099 |                 session.write("+ Tape deck is "+ state  | 
| 100 |                         + ". Current tape: \""+ context.tapeName + "\""); | 
| 104 |         @IoHandlerTransition(on = MESSAGE_RECEIVED, in = ROOT) | 
| 105 |         publicvoidquit(TapeDeckContext context, IoSession session, QuitCommand cmd) { | 
| 106 |             session.write("+ Bye! Please come back!").addListener(IoFutureListener.CLOSE); | 
| 109 |         @IoHandlerTransition(on = MESSAGE_RECEIVED, in = ROOT, weight = 10) | 
| 110 |         publicvoiderror(Event event, StateContext context, IoSession session, Command cmd) { | 
| 111 |             session.write("- Cannot "+ cmd.getName()  | 
| 112 |                     + " while "+ context.getCurrentState().getId().toLowerCase()); | 
| 115 |         @IoHandlerTransition(on = EXCEPTION_CAUGHT, in = ROOT) | 
| 116 |         publicvoidcommandSyntaxError(IoSession session, CommandSyntaxException e) { | 
| 117 |             session.write("- "+ e.getMessage()); | 
| 120 |         @IoHandlerTransition(on = EXCEPTION_CAUGHT, in = ROOT, weight = 10) | 
| 121 |         publicvoidexceptionCaught(IoSession session, Exception e) { | 
| 126 |         @IoHandlerTransition(in = ROOT, weight = 100) | 
| 127 |         publicvoidunhandledEvent() { | 
 命令的抽象类: 
  | 1 | packagecom.a2.desktop.example10.mina.statemachine; | 
| 3 | publicabstractclassCommand { | 
| 4 |      publicabstractString getName(); | 
 以下是各类命令,实现形式相似: 
  | 01 | packagecom.a2.desktop.example10.mina.statemachine; | 
| 03 | publicclassLoadCommand extendsCommand { | 
| 05 |     publicstaticfinalString NAME = "load"; | 
| 07 |     privatefinalinttapeNumber; | 
| 09 |     publicLoadCommand(inttapeNumber) { | 
| 10 |         this.tapeNumber = tapeNumber; | 
| 13 |     publicintgetTapeNumber() { | 
| 18 |     publicString getName() { | 
| 24 | packagecom.a2.desktop.example10.mina.statemachine; | 
| 26 | publicclassPlayCommand extendsCommand { | 
| 28 |     publicstaticfinalString NAME = "play"; | 
| 31 |     publicString getName() { | 
 下面是解码器,继承了文本的解码方式: 
  | 01 | packagecom.a2.desktop.example10.mina.statemachine; | 
| 03 | importjava.nio.charset.Charset; | 
| 04 | importjava.util.LinkedList; | 
| 06 | importorg.apache.mina.core.buffer.IoBuffer; | 
| 07 | importorg.apache.mina.core.filterchain.IoFilter.NextFilter; | 
| 08 | importorg.apache.mina.core.session.IoSession; | 
| 09 | importorg.apache.mina.filter.codec.ProtocolDecoderOutput; | 
| 10 | importorg.apache.mina.filter.codec.textline.LineDelimiter; | 
| 11 | importorg.apache.mina.filter.codec.textline.TextLineDecoder; | 
| 13 | publicclassCommandDecoder extendsTextLineDecoder { | 
| 15 |      publicCommandDecoder() { | 
| 16 |             super(Charset.forName("UTF8"), LineDelimiter.WINDOWS); | 
| 19 |         privateObject parseCommand(String line) throwsCommandSyntaxException { | 
| 20 |             String[] temp = line.split(" +", 2); | 
| 21 |             String cmd = temp[0].toLowerCase(); | 
| 22 |             String arg = temp.length > 1? temp[1] : null; | 
| 24 |             if(LoadCommand.NAME.equals(cmd)) { | 
| 26 |                     thrownewCommandSyntaxException("No tape number specified"); | 
| 29 |                     returnnewLoadCommand(Integer.parseInt(arg)); | 
| 30 |                 } catch(NumberFormatException nfe) { | 
| 31 |                     thrownewCommandSyntaxException("Illegal tape number: "+ arg); | 
| 33 |             } elseif(PlayCommand.NAME.equals(cmd)) { | 
| 34 |                 returnnewPlayCommand(); | 
| 35 |             } elseif(PauseCommand.NAME.equals(cmd)) { | 
| 36 |                 returnnewPauseCommand(); | 
| 37 |             } elseif(StopCommand.NAME.equals(cmd)) { | 
| 38 |                 returnnewStopCommand(); | 
| 39 |             } elseif(ListCommand.NAME.equals(cmd)) { | 
| 40 |                 returnnewListCommand(); | 
| 41 |             } elseif(EjectCommand.NAME.equals(cmd)) { | 
| 42 |                 returnnewEjectCommand(); | 
| 43 |             } elseif(QuitCommand.NAME.equals(cmd)) { | 
| 44 |                 returnnewQuitCommand(); | 
| 45 |             } elseif(InfoCommand.NAME.equals(cmd)) { | 
| 46 |                 returnnewInfoCommand(); | 
| 49 |             thrownewCommandSyntaxException("Unknown command: "+ cmd); | 
| 53 |         publicvoiddecode(IoSession session, IoBuffer in, finalProtocolDecoderOutput out)  | 
| 56 |             finalLinkedList<String> lines = newLinkedList<String>(); | 
| 57 |             super.decode(session, in, newProtocolDecoderOutput() { | 
| 58 |                 publicvoidwrite(Object message) { | 
| 59 |                     lines.add((String) message); | 
| 61 |                 publicvoidflush(NextFilter nextFilter, IoSession session) {} | 
| 64 |             for(String s: lines) { | 
| 65 |                 out.write(parseCommand(s)); | 
 处理异常类: 
  | 01 | packagecom.a2.desktop.example10.mina.statemachine; | 
| 03 | importorg.apache.mina.filter.codec.ProtocolDecoderException; | 
| 06 |  * Exception thrown by CommandDecoder when a line cannot be decoded as a Command | 
| 10 | publicclassCommandSyntaxException extendsProtocolDecoderException { | 
| 11 |     privatefinalString message; | 
| 13 |     publicCommandSyntaxException(String message) { | 
| 15 |         this.message = message; | 
| 19 |     publicString getMessage() { | 
 测试类:  
| 01 | packagecom.a2.desktop.example10.mina.statemachine; | 
| 03 | importjava.net.InetSocketAddress; | 
| 05 | importorg.apache.mina.core.service.IoHandler; | 
| 06 | importorg.apache.mina.filter.codec.ProtocolCodecFilter; | 
| 07 | importorg.apache.mina.filter.codec.textline.TextLineEncoder; | 
| 08 | importorg.apache.mina.filter.logging.LoggingFilter; | 
| 09 | importorg.apache.mina.statemachine.StateMachine; | 
| 10 | importorg.apache.mina.statemachine.StateMachineFactory; | 
| 11 | importorg.apache.mina.statemachine.StateMachineProxyBuilder; | 
| 12 | importorg.apache.mina.statemachine.annotation.IoHandlerTransition; | 
| 13 | importorg.apache.mina.statemachine.context.IoSessionStateContextLookup; | 
| 14 | importorg.apache.mina.statemachine.context.StateContext; | 
| 15 | importorg.apache.mina.statemachine.context.StateContextFactory; | 
| 16 | importorg.apache.mina.transport.socket.SocketAcceptor; | 
| 17 | importorg.apache.mina.transport.socket.nio.NioSocketAcceptor; | 
| 19 | publicclassTestMain { | 
| 20 |      privatestaticfinalintPORT = 8082; | 
| 22 |         privatestaticIoHandler createIoHandler() { | 
| 23 |             StateMachine sm = StateMachineFactory.getInstance( | 
| 24 |                     IoHandlerTransition.class).create(TapeDeckServer.EMPTY, | 
| 25 |                     newTapeDeckServer()); | 
| 27 |             returnnewStateMachineProxyBuilder().setStateContextLookup( | 
| 28 |                     newIoSessionStateContextLookup(newStateContextFactory() { | 
| 29 |                         publicStateContext create() { | 
| 30 |                             returnnewTapeDeckServer.TapeDeckContext(); | 
| 32 |                     })).create(IoHandler.class, sm); | 
| 35 |         publicstaticvoidmain(String[] args) throwsException { | 
| 36 |             SocketAcceptor acceptor = newNioSocketAcceptor(); | 
| 37 |             acceptor.setReuseAddress(true); | 
| 38 |             ProtocolCodecFilter pcf = newProtocolCodecFilter( | 
| 39 |                     newTextLineEncoder(), newCommandDecoder()); | 
| 40 |             acceptor.getFilterChain().addLast("log1", newLoggingFilter("log1")); | 
| 41 |             acceptor.getFilterChain().addLast("codec", pcf); | 
| 42 |             acceptor.getFilterChain().addLast("log2", newLoggingFilter("log2")); | 
| 43 |             acceptor.setHandler(createIoHandler()); | 
| 44 |             acceptor.bind(newInetSocketAddress(PORT)); | 
 启动测试类,用telnet去连,然后输入各种命令,效果如下: 
  ![]() 
   更详细的代码可以参阅: org.apache.mina.example.tapedeck 
 代码其实都不难,只是我们需要灵活的将状态机这样的模式运用到自己的项目中去,通过状态之间的有规则的切换来控制更复杂的业务逻辑。