如果你使用Mina開發(fā)一個(gè)復(fù)雜的網(wǎng)絡(luò)應(yīng)用時(shí),你可能在某些地方會(huì)遇到那個(gè)古老而又好用的狀態(tài)模式,來使用這個(gè)模式解決你的復(fù)雜應(yīng)用。然而,在你做這個(gè)決定之前,你或許想檢出Mina的狀態(tài)機(jī)的代碼,它會(huì)根據(jù)當(dāng)前對象的狀態(tài)來返回對接收到的簡短的數(shù)據(jù)的處理信息。
?
注意:現(xiàn)在正式發(fā)布Mina的狀態(tài)機(jī)。因此你要自己在Mina的SVN服務(wù)器上檢出該代碼,并自己編譯,請參考開發(fā)指南,來獲取更多的關(guān)于檢出和編譯Mina源碼的信息。Mina的狀態(tài)機(jī)可以和所有已經(jīng)發(fā)布的版本Mina配合使用(1.0.x, 1.1.x 和 當(dāng)前發(fā)布的版本)。
?
一個(gè)簡單的例子
讓我們使用一個(gè)簡單的例子來展示一下Mina的狀態(tài)機(jī)是如何工作的。下面的圖片展示了一個(gè)錄音機(jī)的狀態(tài)機(jī)。其中的橢圓是狀態(tài),箭頭表示事務(wù)。每個(gè)事務(wù)都有一個(gè)事件的名字來標(biāo)記該事務(wù)。
?
?
初始化時(shí),錄音機(jī)的狀態(tài)是空的。當(dāng)磁帶放如錄音機(jī)的時(shí)候,加載的事件被觸發(fā),錄音機(jī)進(jìn)入到加載? 狀態(tài)。在加載的狀態(tài)下,退出的事件會(huì)使錄音機(jī)進(jìn)入到空的狀態(tài),播放的事件會(huì)使加載的狀態(tài)進(jìn)入到? 播放狀態(tài)。等等......我想你可以推斷后后面的結(jié)果:)
?
? 現(xiàn)在讓我們寫一些代碼。外部(錄音機(jī)中使用該代碼的地方)只能看到錄音機(jī)的接口:
public interface TapeDeck {
void load(String nameOfTape);
void eject();
void start();
void pause();
void stop();
}
?
下面我們開始編寫真正執(zhí)行的代碼,這些代碼在一個(gè)事務(wù)被觸發(fā)時(shí),會(huì)在狀態(tài)機(jī)中執(zhí)行。首先我們定義一
個(gè)狀態(tài)。這些狀態(tài)都使用字符串常量來定義,并且使用@state標(biāo)記來聲明。
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";
}
?
現(xiàn)在我們已經(jīng)定義了錄音機(jī)中的所有狀態(tài),我們可以根據(jù)每個(gè)事務(wù)來創(chuàng)建相應(yīng)的代碼。每個(gè)事務(wù)都和一個(gè)TapeDeckHandler的方法對應(yīng)。每個(gè)事務(wù)的方法都使用@Transtration標(biāo)簽來聲明,這個(gè)標(biāo)簽定義了事件的ID,該ID會(huì)觸發(fā)事務(wù)的執(zhí)行。事務(wù)開始時(shí)的狀態(tài)使用start,事務(wù)結(jié)束使用next,事務(wù)正在運(yùn)行使用on。
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");
}
}
?
請注意,TapeDeckHandler 類沒有實(shí)現(xiàn)TapeDeck ,呵呵,這是故意的。
現(xiàn)在讓我們親密接觸一下這個(gè)代碼。在loadTape方法上的@Transition標(biāo)簽:
@Transition(on = "load", in = EMPTY, next = LOADED)
public void loadTape(String nameOfTape) {}
?
指定了這個(gè)狀態(tài)后,當(dāng)錄音機(jī)處于空狀態(tài)時(shí),磁帶裝載事件啟動(dòng)后會(huì)觸發(fā)loadTape方法,并且錄音機(jī)狀態(tài)將會(huì)變換到Loaded狀態(tài)。@Transition標(biāo)簽中關(guān)于pauseTape,stopTape,ejectTape的方法就不需要在多介紹了。關(guān)于playTape的標(biāo)簽和其他的標(biāo)簽看起來不太一樣。從上面的圖中我們可以知道,當(dāng)錄音機(jī)的狀態(tài)在Loaded或者Paused時(shí),play事件都會(huì)播放磁帶。當(dāng)多個(gè)事務(wù)同時(shí)條用同一個(gè)方法時(shí),@Transition標(biāo)簽需要按下面的方法使用:
@Transitions({
@Transition(on = "play", in = LOADED, next = PLAYING),
@Transition(on = "play", in = PAUSED, next = PLAYING)
})
public void playTape(){}
?
@Transition標(biāo)簽清晰的列出了聲明的方法被多個(gè)事務(wù)調(diào)用的情況。
?###############################################################
要點(diǎn):更多關(guān)于@Transition 標(biāo)簽的參數(shù)
????? (1)如果你省略了on參數(shù),系統(tǒng)會(huì)將該值默認(rèn)為“*”,這樣任何事件都可以觸發(fā)該方法。
????? (2)如果你省略了next參數(shù),系統(tǒng)會(huì)將默認(rèn)值改為“_self_”,這個(gè)是和當(dāng)前的狀態(tài)相關(guān)的,
??????????????? 如果你要實(shí)現(xiàn)一個(gè)循環(huán)的事務(wù),你所需要做的就是省略狀態(tài)機(jī)中的next參數(shù)。
?????? (3)weight參數(shù)用于定義事務(wù)的查詢順序,一般的狀態(tài)的事務(wù)是根據(jù)weight的值
??????????????? 按升序排列的,weight默認(rèn)的是0.
?###############################################################
??
現(xiàn)在最后一步就是使用聲明類創(chuàng)建一個(gè)狀態(tài)機(jī)的對象,并且使用這個(gè)狀態(tài)機(jī)的實(shí)例創(chuàng)建一個(gè)代理對象,該代理對象實(shí)現(xiàn)了TapeDeck接口:
public static void main(String[] args) {
// 創(chuàng)建錄音機(jī)事件的句柄
TapeDeckHandler handler = new TapeDeckHandler();
// 創(chuàng)建錄音機(jī)的狀態(tài)機(jī)
StateMachine sm = StateMachineFactory.getInstance(Transition.class).create(TapeDeckHandler.EMPTY, handler);
// 使用上面的狀態(tài)機(jī),通過一個(gè)代理創(chuàng)建一個(gè)TapeDeck的實(shí)例
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);
?
使用TapeDeckHandler創(chuàng)建一個(gè)狀態(tài)機(jī)的實(shí)例。StateMachineFactory.getInstance(...) 調(diào)用的方法中使用的Transition.class 是通知工廠我們使用@Transition 標(biāo)簽創(chuàng)建一個(gè)狀態(tài)機(jī)。我們指定了狀態(tài)機(jī)開始時(shí)狀態(tài)是空的。一個(gè)狀態(tài)機(jī)是一個(gè)基本的指示圖。狀態(tài)對象和圖中的節(jié)點(diǎn)對應(yīng),事務(wù)對象和箭頭指向的方向?qū)?yīng)。我們在TapeDeckHandler中使用的 每一個(gè)@Transition 標(biāo)簽都和一個(gè)事務(wù)的實(shí)例對應(yīng)。
?###############################################################
要點(diǎn): 那么, @Transition 和 Transition 有什么不同嗎?
?@Transition 是你用來標(biāo)記當(dāng)事務(wù)在狀態(tài)之間變化時(shí)應(yīng)該使用那個(gè)方法。在后臺(tái)處理中,
?Mina的狀態(tài)機(jī)會(huì)為MethodTransition 中每一個(gè)事務(wù)標(biāo)簽創(chuàng)建一個(gè)事務(wù)的實(shí)例。MethodTransition??實(shí)現(xiàn)了Transition 接口。作為一個(gè)Mina狀態(tài)機(jī)的使用者,你不用直接使用Transition 或者M(jìn)ethodTransition 類型的對象。
?###############################################################
錄音機(jī)TapeDeck 的實(shí)例是通過調(diào)用StateMachineProxyBuilder來創(chuàng)建的:
TapeDeck deck = new StateMachineProxyBuilder().create(TapeDeck.class, sm);
?
StateMachineProxyBuilder.create()使用的接口需要由代理的對象來實(shí)現(xiàn),狀態(tài)機(jī)的實(shí)例將接收由代理產(chǎn)生的事件所觸發(fā)的方法。當(dāng)代碼執(zhí)行時(shí),輸出的結(jié)果如下:
Tape 'The Knife - Silent Shout' loaded
Playing tape
Tape paused
Playing tape
Tape stopped
Tape ejected
?
?###############################################################
要點(diǎn): 這和Mina有什么關(guān)系?
??????????? 或許你已經(jīng)注意到,在這個(gè)例子中沒有對Mina進(jìn)行任何配置。但是不要著急。
???? 稍后我們將會(huì)看到如何為Mina的IoHandler接口創(chuàng)建一個(gè)狀態(tài)機(jī)。
?###############################################################
它是怎樣工作的?
讓我們走馬觀花的看看當(dāng)代理調(diào)用一個(gè)方法的時(shí)候發(fā)生了什么。?查看一個(gè)StateContext(狀態(tài)的上下文)對象?狀態(tài)上下文之所以重要是因?yàn)樗4媪水?dāng)前的狀態(tài)。代理調(diào)用一個(gè)方法時(shí),狀態(tài)上下文?會(huì)通知StateContextLookup 實(shí)例去方法的參數(shù)中獲取一個(gè)狀態(tài)的上下文。一般情況下,StateContextLookup 的實(shí)現(xiàn)將會(huì)循環(huán)方法中的參數(shù),并查找一個(gè)指定類型的對象,并且使用這個(gè)對象反轉(zhuǎn)出一個(gè)上下文對象。如果沒有聲明一個(gè)狀態(tài)上下文,StateContextLookup 將會(huì)創(chuàng)一個(gè),并將其存放到對象中。當(dāng)代理Mina的IoHandler接口時(shí),我們將使用IoSessoinStateContextLookup 實(shí)例,該實(shí)例用來查詢一個(gè)IoSession中的方法參數(shù)。它將會(huì)使用 IoSession的屬性值為每一個(gè)Mina的session來存放一個(gè)獨(dú)立的狀態(tài)上下文的實(shí)例。這中方式下,同樣的狀態(tài)機(jī)可以讓所有的Mina的會(huì)話使用,而不會(huì)使每個(gè)會(huì)話彼此產(chǎn)生影響。
###############################################################
要點(diǎn): 在上面的例子中,當(dāng)我們使用StateMachineProxyBuilder創(chuàng)建一個(gè)代理時(shí),我們
一直沒有我們一直沒有配置StateContextLookup 使用哪種實(shí)現(xiàn)。如果沒有配置,系統(tǒng)會(huì)
使用SingletonStateContextLookup 。SingletonStateContextLookup 總是不理會(huì)方法中
傳遞給它的參數(shù),它一直返回一個(gè)相同的狀態(tài)上下文。很明顯,這中方式在多個(gè)客戶端
并發(fā)的情況下使用同一個(gè)同一個(gè)狀態(tài)機(jī)是沒有意義的。這種情況下的配置會(huì)在后面的關(guān)于
IoHandler 的代理配置時(shí)進(jìn)行說明。
?###############################################################
將方法請求反轉(zhuǎn)成一個(gè)事件對象
所有在代理對象上的方法請求都會(huì)有代理對象轉(zhuǎn)換成事件對象。一個(gè)事件有一個(gè)ID或者0個(gè)或多個(gè)參數(shù)。事件的ID和方法的名字相當(dāng),事件的參數(shù)和方法的參數(shù)相當(dāng)。調(diào)用方法deck.load("The Knife - Silent Shout") 相當(dāng)于事件{id = "load", arguments = ["The Knife - Silent Shout"]}.事件對象中包含一個(gè)狀態(tài)上下文的引用,該狀態(tài)上下文是
當(dāng)前查找到的。
?
觸發(fā)狀態(tài)機(jī)
一旦事件對象被創(chuàng)建,代理會(huì)調(diào)用StateMachine.handle(Event).方法。StateMachine.handle(Event)遍歷事務(wù)對象中當(dāng)前的狀態(tài),來查找能夠接收當(dāng)前事件的事務(wù)的實(shí)例。這個(gè)過程會(huì)在事務(wù)的實(shí)例找到后停止。這個(gè)查詢的順序是由事務(wù)的重量值來決定的(重量值一般在@Transition 標(biāo)簽中指定)。
?
?
執(zhí)行事務(wù)
最后一部就是在Transition 中調(diào)用匹配事件對象的Transition.execute(Event)方法。當(dāng)事件已經(jīng)執(zhí)行,這個(gè)狀態(tài)機(jī)將更新當(dāng)前的狀態(tài),更新后的值是你在事務(wù)中定義的后面的狀態(tài)。
###############################################################
要點(diǎn): 事務(wù)是一個(gè)接口。每次你使用@Transition 標(biāo)簽時(shí),MethodTransition對象將會(huì)被創(chuàng)建。
?###############################################################
?
MethodTransition(方法事務(wù))
MethodTransition非常重要,它還需要一些補(bǔ)充說明。如果事件ID和@Transition標(biāo)簽中的on參數(shù)匹配,
事件的參數(shù)和@Transition中的參數(shù)匹配,那么MethodTransition和這個(gè)事件匹配。
所以如果事件看起來像{id = "foo", arguments = [a, b, c]},那么下面的方法:
@Transition(on = "foo")
public void someMethod(One one, Two two, Three three) { ... }
?
只和這個(gè)事件匹配((a instanceof One && b instanceof Two && c instanceof Three) == true).。當(dāng)匹配時(shí),這個(gè)方法將會(huì)被與其匹配的事件使用綁定的參數(shù)調(diào)用。
###############################################################
要點(diǎn): Integer, Double, Float, 等也和他們的基本類型int, double, float, 等匹配。
?###############################################################
因此,上面的狀態(tài)是一個(gè)子集,需要和下面的方法匹配:
@Transition(on = "foo")
public void someMethod(Two two) { ... }
?
上面的方法和((a instanceof Two || b instanceof Two || c instanceof Two) == true)是等價(jià)的。在這種情況下,第一個(gè)被匹配的事件的參數(shù)將會(huì)和該方法綁定,在它被調(diào)用的時(shí)候。一個(gè)方法如果沒有參數(shù),在其事件的ID匹配時(shí),仍然會(huì)被調(diào)用:
@Transition(on = "foo")
public void someMethod() { ... }
?
這樣做讓事件的處理變得有點(diǎn)復(fù)雜,開始的兩個(gè)方法的參數(shù)和事件的類及狀態(tài)的上下文接口相匹配。這意味著:
@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) { ... }
?上面的方法和事件{id = "foo", arguments = [a, b, c]} if ((a instanceof One && b instanceof Two&& c instanceof Three) == true) 是匹配的。當(dāng)前的事件對象和事件的方法綁定,當(dāng)前的狀態(tài)上下文和該方法被調(diào)用時(shí)的上下文綁定。在此之前一個(gè)事件的參數(shù)的集合將會(huì)被使用。當(dāng)然,一個(gè)指定的狀態(tài)上下文的實(shí)現(xiàn)將會(huì)被指定,以用來替代通用的上下文接口。
@Transition(on = "foo")
public void someMethod(MyStateContext context, Two two) { ... }
?
###############################################################
要點(diǎn):方法中參數(shù)的順序很重要。若方法需要訪問當(dāng)前的事件,它必須被配置為第一個(gè)
方法參數(shù)。當(dāng)事件為第一個(gè)參數(shù)的時(shí)候,狀態(tài)上下問不能配置為第二個(gè)參數(shù),它也不能
配置為第一個(gè)方法的參數(shù)。事件的參數(shù)也要按正確的順序進(jìn)行匹配。方法的事務(wù)不會(huì)在
查找匹配事件方法的時(shí)候重新排序。
?###############################################################
?到現(xiàn)在如果你已經(jīng)掌握了上面的內(nèi)容,恭喜你!我知道上面的內(nèi)容會(huì)有點(diǎn)難以消化。希望下面的例子?能讓你對上面的內(nèi)容有更清晰的了解。注意這個(gè)事件Event {id = "messageReceived", arguments =?[ArrayList a = [...], Integer b = 1024]}。下面的方法將和這個(gè)事件是等價(jià)的:
// 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) { ... }
?
但是下面的方法不會(huì)和這個(gè)事件相匹配:
// 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) { ... }
?
?
狀態(tài)繼承
狀態(tài)的實(shí)例將會(huì)有一個(gè)父類的狀態(tài)。如果StateMachine.handle(Event)的方法不能找到一個(gè)事務(wù)和當(dāng)前的事件在當(dāng)前的狀態(tài)中匹配,它將會(huì)尋找父類中的裝。如果仍然沒有找到,那么事務(wù)將會(huì)自動(dòng)尋找父類的父類,知道找到為止。這個(gè)特性很有用,當(dāng)你想為所有的狀態(tài)添加一些通用的代碼時(shí),不需要為每一個(gè)狀態(tài)的方法來聲明事務(wù)。這里你可以創(chuàng)建一個(gè)類的繼承體系,使用下面的方法即可:
@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";
?
?
使用狀態(tài)繼承來處理錯(cuò)誤信息
讓我們回到錄音機(jī)的例子。如果錄音機(jī)里沒有磁帶,當(dāng)你調(diào)用deck.play()方法時(shí)將會(huì)怎樣?讓我們試試:
示例代碼:
public static void main(String[] args) {
...
deck.load("The Knife - Silent Shout");
deck.play();
deck.pause();
deck.play();
deck.stop();
deck.eject();
deck.play();
}
?
運(yùn)行結(jié)果:
...
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)
...
?
哦,我們得到了一個(gè)無法處理的異常UnhandledEventException,這是因?yàn)樵阡浺魴C(jī)的空狀態(tài)時(shí),沒有事務(wù)來處理播放的狀態(tài)。我們將添加一個(gè)指定的事務(wù)來處理所有不能匹配的事件。
@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");
}
?
現(xiàn)在當(dāng)你運(yùn)行上面的main()方法時(shí),你將不會(huì)再得到一個(gè)異常,輸出如下:
...
Tape stopped
Tape ejected
Cannot 'play' at this time.
?
現(xiàn)在這些看起來運(yùn)行的都很好,是嗎?但是如果們有30個(gè)狀態(tài)而不是4個(gè),那該怎么辦?那么我們需要在上面的錯(cuò)誤方法處理中配置30個(gè)事務(wù)的聲明。這樣不好。讓我們用狀態(tài)繼承來解決:
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");
}
}
?
這個(gè)運(yùn)行的結(jié)果和上面的是一樣的,但是它比要每個(gè)方法都配置聲明要簡單的多。
?
Mina的狀態(tài)機(jī)和IoHandler配合使用
現(xiàn)在我們將上面的錄音機(jī)程序改造成一個(gè)TCP服務(wù)器,并擴(kuò)展一些方法。服務(wù)器將接收一些命令類似于:
?load <tape>, play, stop等等。服務(wù)器響應(yīng)的信息將會(huì)是+ <message> 或者是- <message>。協(xié)議是基于
?Mina自身提供的一個(gè)文本協(xié)議,所有的命令和響應(yīng)編碼都是基于UTF-8。這里有一個(gè)簡單的會(huì)話示例:
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!
?
該程序完整的代碼在org.apache.mina.example.tapedeck 包中,這個(gè)可以通過檢出Mina源碼的SVN庫中的mina-example 來得到。代碼使用MinaProtocolCodecFilter來編解碼傳輸?shù)亩M(jìn)數(shù)據(jù)對象。這里只是為每個(gè)狀態(tài)對服務(wù)器的請求實(shí)現(xiàn)了一個(gè)簡單的編解碼器。在此不在對Mina中編解碼的實(shí)現(xiàn)做過多的講解。現(xiàn)在我們看一下這個(gè)服務(wù)器是如何工作的。這里面一個(gè)重要的類就是實(shí)現(xiàn)了錄音機(jī)程序的TapeDeckServer 類。
這里我們要做的第一件事情就是去定義這些狀態(tài):
@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";
?
在這里沒有什么新增的內(nèi)容。然而,但是處理這些事件的方法看起來將會(huì)不一樣。讓我們看看playTape的方法。
@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 + "\"");
}
?
這里沒有使用通用的@Transition和@Transitions的事務(wù)聲明,而是使用了Mina指定的 @IoHandlerTransition和@IoHandlerTransitions聲明。當(dāng)為Mina的IoHandler創(chuàng)建一個(gè)狀態(tài)機(jī)時(shí),它會(huì)選擇讓你使用Java enum (枚舉)類型來替代我們上面使用的字符串類型。這個(gè)在Mina的IoFilter中也是一樣的。 我們現(xiàn)在使用MESSAGE_RECEIVED來替代"play"來作為事件的名字(on是@IoHandlerTransition的一個(gè)屬性)。這個(gè)常量是在org.apache.mina.statemachine.event.IoHandlerEvents中定義的,它的值是"messageReceived",這個(gè)和Mina的IoHandler中的messageReceived()方法是一致的。謝謝Java 5中的靜態(tài)導(dǎo)入,我們在使用該變量的時(shí)候就不用再通過類的名字來調(diào)用該常量,我們只需要按下面的方法導(dǎo)入該類:
import static org.apache.mina.statemachine.event.IoHandlerEvents.*;
?
這樣狀態(tài)內(nèi)容就被導(dǎo)入了。 另外一個(gè)要改變的內(nèi)容是我們自定了一個(gè)StateContext 狀態(tài)上下文的實(shí)現(xiàn)--TapeDeckContext。這個(gè)類主要是用于返回當(dāng)前錄音機(jī)的狀態(tài)的名字。
static class TapeDeckContext extends AbstractStateContext {
public String tapeName;
}
?
###############################################################
要點(diǎn):為什么不把狀態(tài)的名字保存到IoSession中?
我們可以將錄音機(jī)狀態(tài)的名字保存到IoSession中,但是使用一個(gè)自定義的StateContext
來保存這個(gè)狀態(tài)將會(huì)使這個(gè)類型更加安全。
?###############################################################
最后需要注意的事情是playTape()方法使用了PlayCommand命令來作為它的最后的一個(gè)參數(shù)。最后一個(gè)參數(shù)和IoHandler's messageReceived(IoSession session, Object message)方法匹配。這意味著只有在客戶端發(fā)送的信息被編碼成layCommand命令時(shí),該方法才會(huì)被調(diào)用。在錄音機(jī)開始進(jìn)行播放前,它要做的事情就是要裝載磁帶。當(dāng)裝載的命令從客戶端發(fā)送過來時(shí),服務(wù)器提供的磁帶的數(shù)字代號(hào)將會(huì)從磁帶列表中將可用的磁帶的名字取出:
@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狀態(tài)控制器來重寫了下一個(gè)狀態(tài)。如果用戶指定了一個(gè)非法的數(shù)字,我們將不會(huì)將加載狀態(tài)刪除,而是使用一個(gè)空狀態(tài)來代替。代碼如下所示:
StateControl.breakAndGotoNext(EMPTY);
?
狀態(tài)控制器將會(huì)在后面的章節(jié)中詳細(xì)的講述。
connect()方法將會(huì)在Mina開啟一個(gè)會(huì)話并調(diào)用sessionOpened()方法時(shí)觸發(fā)。
@IoHandlerTransition(on = SESSION_OPENED, in = EMPTY)
public void connect(IoSession session) {
session.write("+ Greetings from your tape deck!");
}
?
它所做的工作就是向客戶端發(fā)送歡迎的信息。狀態(tài)機(jī)將會(huì)保持空的狀態(tài)。pauseTape(), stopTape() 和 ejectTape() 方法和 playTape()很相似。這里不再進(jìn)行過多的講述。listTapes(),??info() 和 quit() 方法也很容易理,也不再進(jìn)行過多的講解。請注意后面的三個(gè)方法是在根狀態(tài)下使用的。這意?味著listTapes(),? info() 和 quit() 可以在任何狀態(tài)中使用。
?
?現(xiàn)在讓我們看一下錯(cuò)誤處理。error()將會(huì)在客戶端發(fā)送一個(gè)非法的操作時(shí)觸發(fā):
@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());
}
error()已經(jīng)被指定了一個(gè)高于listTapes(),? info() 和 quit() 的重量值來阻止客戶端調(diào)用上面的方法。注意error()方法是怎樣使用狀態(tài)上下文來保存當(dāng)前狀態(tài)的ID的。字符串常量值由@State annotation (Empty, Loaded etc) 聲明。這個(gè)將會(huì)由Mina的狀態(tài)機(jī)當(dāng)成狀態(tài)的ID來使用。
?
commandSyntaxError()方法將會(huì)在ProtocolDecoder拋CommandSyntaxException 異常時(shí)被調(diào)用。它將會(huì)簡單的輸出客戶端發(fā)送的信息不能解碼為一個(gè)狀態(tài)命令。
?
exceptionCaught() 方法將會(huì)在任何異常發(fā)生時(shí)調(diào)用,除CommandSyntaxException 異常(這個(gè)異常有一個(gè)較高的重量值)。它將會(huì)立刻關(guān)閉會(huì)話。
?
最后一個(gè)@IoHandlerTransition的方法是unhandledEvent() ,它將會(huì)在@IoHandlerTransition中的方法沒有事件匹配時(shí)調(diào)用。我們需要這個(gè)方法是因?yàn)槲覀儧]有@IoHandlerTransition的方法來處理所有可能的事件 (例如:我們沒有處理messageSent(Event)方法)。沒有這個(gè)方法,Mina的狀態(tài)機(jī)將會(huì)在執(zhí)行一個(gè)事件的時(shí)候拋出一個(gè)異常。最后一點(diǎn)我們要看的是那個(gè)類創(chuàng)建了IoHandler的代理,main()方法也在其中:
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();
}
?
createIoHandler() 方法創(chuàng)建了一個(gè)狀態(tài)機(jī),這個(gè)和我們之前所做的相似。除了我們一個(gè)IoHandlerTransition.class類來代替Transition.class 在StateMachineFactory.getInstance(...)方法中。這是我們在使用 @IoHandlerTransition 聲明的時(shí)候必須要做的。當(dāng)然這時(shí)我們使用了一個(gè)IoSessionStateContextLookup和一個(gè)自定義的StateContextFactory類,這個(gè)在我們創(chuàng)建一個(gè)IoHandler 代理時(shí)被使用到了。如果我們沒有使用IoSessionStateContextLookup ,那么所有的客戶端將會(huì)使用同一個(gè)狀態(tài)機(jī),這是我們不希望看到的。
main()方法創(chuàng)建了SocketAcceptor實(shí)例,并且綁定了一個(gè)ProtocolCodecFilter ,它用于編解碼命令對象。最后它綁定了12345端口和IoHandler的實(shí)例。這個(gè)oHandler實(shí)例是由createIoHandler()方法創(chuàng)建的。
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
