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

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