接下来我们要把这样的方式用在通信中。用通信的方式来模拟录放机的按钮。
我们先看状态的定义:
001 | package com.a2.desktop.example10.mina.statemachine; |
003 | import static org.apache.mina.statemachine.event.IoHandlerEvents.EXCEPTION_CAUGHT; |
004 | import static org.apache.mina.statemachine.event.IoHandlerEvents.MESSAGE_RECEIVED; |
005 | import static org.apache.mina.statemachine.event.IoHandlerEvents.SESSION_OPENED; |
007 | import org.apache.mina.core.future.IoFutureListener; |
008 | import org.apache.mina.core.session.IoSession; |
010 | import org.apache.mina.statemachine.StateControl; |
011 | import org.apache.mina.statemachine.annotation.IoHandlerTransition; |
012 | import org.apache.mina.statemachine.annotation.IoHandlerTransitions; |
013 | import org.apache.mina.statemachine.annotation.State; |
014 | import org.apache.mina.statemachine.context.AbstractStateContext; |
015 | import org.apache.mina.statemachine.context.StateContext; |
016 | import org.apache.mina.statemachine.event.Event; |
018 | public class TapeDeckServer { |
021 | public static final String ROOT = "Root"; |
024 | public static final String EMPTY = "Empty"; |
026 | public static final String LOADED = "Loaded"; |
028 | public static final String PLAYING = "Playing"; |
030 | public static final String PAUSED = "Paused"; |
032 | private final String[] tapes = { "盖世英雄-王力宏", "唯一-王力宏" }; |
034 | static class TapeDeckContext extends AbstractStateContext { |
035 | public String tapeName; |
038 | @IoHandlerTransition(on = SESSION_OPENED, in = EMPTY) |
039 | public void connect(IoSession session) { |
040 | session.write("+ Greetings from your tape deck!"); |
043 | @IoHandlerTransition(on = MESSAGE_RECEIVED, in = EMPTY, next = LOADED) |
044 | public void loadTape(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 | public void playTape(TapeDeckContext context, IoSession session, PlayCommand cmd) { |
060 | session.write("+ Playing \"" + context.tapeName + "\""); |
063 | @IoHandlerTransition(on = MESSAGE_RECEIVED, in = PLAYING, next = PAUSED) |
064 | public void pauseTape(TapeDeckContext context, IoSession session, PauseCommand cmd) { |
065 | session.write("+ \"" + context.tapeName + "\" paused"); |
068 | @IoHandlerTransition(on = MESSAGE_RECEIVED, in = PLAYING, next = LOADED) |
069 | public void stopTape(TapeDeckContext context, IoSession session, StopCommand cmd) { |
070 | session.write("+ \"" + context.tapeName + "\" stopped"); |
073 | @IoHandlerTransition(on = MESSAGE_RECEIVED, in = LOADED, next = EMPTY) |
074 | public void ejectTape(TapeDeckContext context, IoSession session, EjectCommand cmd) { |
075 | session.write("+ \"" + context.tapeName + "\" ejected"); |
076 | context.tapeName = null; |
079 | @IoHandlerTransition(on = MESSAGE_RECEIVED, in = ROOT) |
080 | public void listTapes(IoSession session, ListCommand cmd) { |
081 | StringBuilder response = new StringBuilder("+ ("); |
082 | for (int i = 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 | public void info(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 | public void quit(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 | public void error(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 | public void commandSyntaxError(IoSession session, CommandSyntaxException e) { |
117 | session.write("- " + e.getMessage()); |
120 | @IoHandlerTransition(on = EXCEPTION_CAUGHT, in = ROOT, weight = 10) |
121 | public void exceptionCaught(IoSession session, Exception e) { |
126 | @IoHandlerTransition(in = ROOT, weight = 100) |
127 | public void unhandledEvent() { |
命令的抽象类:
1 | package com.a2.desktop.example10.mina.statemachine; |
3 | public abstract class Command { |
4 | public abstract String getName(); |
以下是各类命令,实现形式相似:
01 | package com.a2.desktop.example10.mina.statemachine; |
03 | public class LoadCommand extends Command { |
05 | public static final String NAME = "load"; |
07 | private final int tapeNumber; |
09 | public LoadCommand(int tapeNumber) { |
10 | this.tapeNumber = tapeNumber; |
13 | public int getTapeNumber() { |
18 | public String getName() { |
24 | package com.a2.desktop.example10.mina.statemachine; |
26 | public class PlayCommand extends Command { |
28 | public static final String NAME = "play"; |
31 | public String getName() { |
下面是解码器,继承了文本的解码方式:
01 | package com.a2.desktop.example10.mina.statemachine; |
03 | import java.nio.charset.Charset; |
04 | import java.util.LinkedList; |
06 | import org.apache.mina.core.buffer.IoBuffer; |
07 | import org.apache.mina.core.filterchain.IoFilter.NextFilter; |
08 | import org.apache.mina.core.session.IoSession; |
09 | import org.apache.mina.filter.codec.ProtocolDecoderOutput; |
10 | import org.apache.mina.filter.codec.textline.LineDelimiter; |
11 | import org.apache.mina.filter.codec.textline.TextLineDecoder; |
13 | public class CommandDecoder extends TextLineDecoder { |
15 | public CommandDecoder() { |
16 | super(Charset.forName("UTF8"), LineDelimiter.WINDOWS); |
19 | private Object parseCommand(String line) throws CommandSyntaxException { |
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 | throw new CommandSyntaxException("No tape number specified"); |
29 | return new LoadCommand(Integer.parseInt(arg)); |
30 | } catch (NumberFormatException nfe) { |
31 | throw new CommandSyntaxException("Illegal tape number: " + arg); |
33 | } else if (PlayCommand.NAME.equals(cmd)) { |
34 | return new PlayCommand(); |
35 | } else if (PauseCommand.NAME.equals(cmd)) { |
36 | return new PauseCommand(); |
37 | } else if (StopCommand.NAME.equals(cmd)) { |
38 | return new StopCommand(); |
39 | } else if (ListCommand.NAME.equals(cmd)) { |
40 | return new ListCommand(); |
41 | } else if (EjectCommand.NAME.equals(cmd)) { |
42 | return new EjectCommand(); |
43 | } else if (QuitCommand.NAME.equals(cmd)) { |
44 | return new QuitCommand(); |
45 | } else if (InfoCommand.NAME.equals(cmd)) { |
46 | return new InfoCommand(); |
49 | throw new CommandSyntaxException("Unknown command: " + cmd); |
53 | public void decode(IoSession session, IoBuffer in, final ProtocolDecoderOutput out) |
56 | final LinkedList<String> lines = new LinkedList<String>(); |
57 | super.decode(session, in, new ProtocolDecoderOutput() { |
58 | public void write(Object message) { |
59 | lines.add((String) message); |
61 | public void flush(NextFilter nextFilter, IoSession session) {} |
64 | for (String s: lines) { |
65 | out.write(parseCommand(s)); |
处理异常类:
01 | package com.a2.desktop.example10.mina.statemachine; |
03 | import org.apache.mina.filter.codec.ProtocolDecoderException; |
06 | * Exception thrown by CommandDecoder when a line cannot be decoded as a Command |
10 | public class CommandSyntaxException extends ProtocolDecoderException { |
11 | private final String message; |
13 | public CommandSyntaxException(String message) { |
15 | this.message = message; |
19 | public String getMessage() { |
测试类:
01 | package com.a2.desktop.example10.mina.statemachine; |
03 | import java.net.InetSocketAddress; |
05 | import org.apache.mina.core.service.IoHandler; |
06 | import org.apache.mina.filter.codec.ProtocolCodecFilter; |
07 | import org.apache.mina.filter.codec.textline.TextLineEncoder; |
08 | import org.apache.mina.filter.logging.LoggingFilter; |
09 | import org.apache.mina.statemachine.StateMachine; |
10 | import org.apache.mina.statemachine.StateMachineFactory; |
11 | import org.apache.mina.statemachine.StateMachineProxyBuilder; |
12 | import org.apache.mina.statemachine.annotation.IoHandlerTransition; |
13 | import org.apache.mina.statemachine.context.IoSessionStateContextLookup; |
14 | import org.apache.mina.statemachine.context.StateContext; |
15 | import org.apache.mina.statemachine.context.StateContextFactory; |
16 | import org.apache.mina.transport.socket.SocketAcceptor; |
17 | import org.apache.mina.transport.socket.nio.NioSocketAcceptor; |
19 | public class TestMain { |
20 | private static final int PORT = 8082; |
22 | private static IoHandler createIoHandler() { |
23 | StateMachine sm = StateMachineFactory.getInstance( |
24 | IoHandlerTransition.class).create(TapeDeckServer.EMPTY, |
25 | new TapeDeckServer()); |
27 | return new StateMachineProxyBuilder().setStateContextLookup( |
28 | new IoSessionStateContextLookup(new StateContextFactory() { |
29 | public StateContext create() { |
30 | return new TapeDeckServer.TapeDeckContext(); |
32 | })).create(IoHandler.class, sm); |
35 | public static void main(String[] args) throws Exception { |
36 | SocketAcceptor acceptor = new NioSocketAcceptor(); |
37 | acceptor.setReuseAddress(true); |
38 | ProtocolCodecFilter pcf = new ProtocolCodecFilter( |
39 | new TextLineEncoder(), new CommandDecoder()); |
40 | acceptor.getFilterChain().addLast("log1", new LoggingFilter("log1")); |
41 | acceptor.getFilterChain().addLast("codec", pcf); |
42 | acceptor.getFilterChain().addLast("log2", new LoggingFilter("log2")); |
43 | acceptor.setHandler(createIoHandler()); |
44 | acceptor.bind(new InetSocketAddress(PORT)); |
启动测试类,用telnet去连,然后输入各种命令,效果如下:
更详细的代码可以参阅: org.apache.mina.example.tapedeck
代码其实都不难,只是我们需要灵活的将状态机这样的模式运用到自己的项目中去,通过状态之间的有规则的切换来控制更复杂的业务逻辑。