默認(rèn)基于Http LongPolling技術(shù)實現(xiàn),未來可能擴展出WebSokcet的實現(xiàn)。實現(xiàn)了單一Page中的長連接自動合并功能,即在單一Page中無論開發(fā)者利用Dorado建立多少個長連接,Dorado都只占用個一個物理長連接,并在此物理連接的基礎(chǔ)上構(gòu)建n個虛擬連接。此功能未來可能會進一步擴展,以實現(xiàn)跨Page的連接自動合并。消息收發(fā)的自動合并,當(dāng)客戶端或服務(wù)端在短時間內(nèi)頻繁的利用長連接發(fā)送消息時,Dorado會自動對將一批消息合并為一次物理Request或Response,從而避免瀏覽器過度頻繁的與服務(wù)器建立Http連接。
在客戶端建立一個長連接的方法是直接調(diào)用dorado.Socket.connect()方法,該方法有兩個參數(shù)——options和callback。callback顧名思義就是長連接建立成功后的回調(diào)方法,而options則是長連接的選項,options參數(shù)可以包含如下的幾個重要的子參數(shù)(具體參考API文檔): service —— 字符串。服務(wù)端ExposedService(可暴露遠(yuǎn)程服務(wù))的名稱。這里的ExposedService就是我們在編寫Ajax操作的服務(wù)端代碼時所使用的那種服務(wù)。parameter —— 任意對象,可以為空。即建立連接時傳遞給服務(wù)端的參數(shù)。onReceive —— 當(dāng)收到服務(wù)端發(fā)來的消息時觸發(fā)的事件。onDisconnect —— 當(dāng)連接斷開時觸發(fā)的事件。dorado.Socket.connect()方法的返回值是socket對象,我們可以通過這個對象在做后面的處理. 例如...
var socket = dorado.Socket.connect({ service: "test#messageService" }, function() {
alert("Socket connected.");
});
定義長連接服務(wù)端的代碼的方法幾乎與定義Ajax服務(wù)端代碼的方法完全一樣,唯一的不同是你必須在方法的參數(shù)中保留一個名為socket的參數(shù),該參數(shù)的類型為com.bstek.dorado.view.socket.Socket。通過該參數(shù)我們方便與客戶端進行消息收發(fā)操作。例如...
@Component
public class Test {
@Expose
public void messageService(Socket socket) {
... ...
}
}
如果客戶端指定了建立連接時的參數(shù),那么這里的messageService方法中也可以通過自動適配參數(shù)來接收parameter中的數(shù)據(jù),方法同Ajax操作的一樣,此處不再累述。
客戶端收發(fā)消息的操作比較簡單,要發(fā)送消息可以直接使用socket對象的send()方法,該方法包含三個參數(shù): type?—— 字符串,消息類型。可以是任意字符串。data?—— 任意對象,消息內(nèi)容。callback?—— 消息發(fā)送成功的回調(diào)方法,即服務(wù)端確認(rèn)收到消息后才會觸發(fā)的回調(diào)方法。例如...
socket.send("chat", "吃了嗎?");
客戶端接收消息需要使用Socket對象的onReceive事件。例如...
var socket = dorado.Socket.connect({ service: "test#messageService", onReceive: function(arg) {
alert("收到了來自服務(wù)器的消息..." + "\ntype:" + arg.type + "\ndata:" + arg.data);
}});
服務(wù)端發(fā)送消息的方法與客戶端非常相似,例如...
socket.send(new Message("消息類型", "消息內(nèi)容"));
服務(wù)端接收消息的方法有兩種——阻塞式和非阻塞式。 阻塞式的消息接收 使用阻塞式消息接收通常是在一個獨立的線程中,例如我們可能是這樣來定義ExposedService的...
@Expose
public void messageService(Socket socket) {
(new SocketServerThread(socket) {
@Override
protected void run(Socket socket) {
while (socket.isConnected()) {
try {
Message message = socket.receive(); // 阻塞方法
... ...
socket.send(new Message("chat", "呵呵"));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}).start();
}
非阻塞式的消息接收 非阻塞式的消息接收是利用Socket對象的監(jiān)聽器,它的使用場景相對自由。例如:
@Expose
public void messageService(Socket socket) {
socket.addReceiveListener(new SocketReceiveListener() {
public void onReceive(Socket socket, Message message) {
... ...
socket.send(new Message("chat", "呵呵"));
}
});
}
長任務(wù)(LongTask)功能是在構(gòu)建在長連接基礎(chǔ)之上的。用于完成那些需要在后臺執(zhí)行較長時間的任務(wù),這些任務(wù)的執(zhí)行時長可以是幾十秒、幾分鐘、幾小時、甚至更長,對于這種很長的任務(wù)用戶幾乎不可能一直在網(wǎng)頁端等待他們執(zhí)行結(jié)束,但是他們往往又需要隨時了解該任務(wù)的執(zhí)行狀況 ,或者對正在執(zhí)行的任務(wù)進行一些調(diào)度管理。長任務(wù)(LongTask)功能正是為了解決用戶的上述困擾而提供的。
使用LongTask的方法與使用AjaxAction的方法有些類似,在IDE工具欄的Action中可以找到LongTask這樣一個組件,它有如下幾個重要的屬性... taskName -?該屬性的含義與AjaxAction中的service屬性是一致的,表示一個后臺長任務(wù)的服務(wù)名稱。appearence - 用于指定系統(tǒng)如何向用戶展示當(dāng)前正在執(zhí)行任務(wù)的信息。其中mainTask表示顯示一個模態(tài)的提示器;daemonTask表示顯示一個非模態(tài)的提示器;none表示不現(xiàn)實任何提示,此時用戶可以利用LongTask的事件自行確定如何展示任務(wù)的執(zhí)行狀態(tài)。disableOnActive - 表示是否要在檢測到有正在執(zhí)行的任務(wù)時自動禁用本控件。作為一個最簡單的例子,我們可以直接在LongTask的taskName中定義一個字符串,如 #simpleTask,根據(jù)Dorado中提供的新特性,此種簡略寫法實際表示的值是:<小寫字符開頭的View名稱>#simpleTask。假設(shè)我們的View的文件名是TestLongTask.view.xml,那么#simpleTask實際表示testLongTask#simpleTask。(此規(guī)則同樣適用于AjaxAction.service,DataSet.dataProvider,UpdateAction.dataResolver等屬性)
按照默認(rèn)的開發(fā)討論,我們接下來可以直接在一個名為TestLongTask的JavaBean編寫LongTask后端邏輯代碼了。例如...
@Component
public class TestLongTask {
@Expose
public LongTask simpleTask() {
return new LongTask() {
public Object call() throws Exception {
Thread.sleep(5000);
return null;
}
};
}
}
從上面的代碼可見,定義的LongTask后端的方法與Ajax后端的方法非常相似,唯一的要求是此方法必須返回一個LongTask對象。LongTask是java.util.concurrent.Callable<Object>的實現(xiàn)類,會由LongTask的調(diào)度器在另一個線程中執(zhí)行。在最為簡單的示例中,我們只需要實現(xiàn)LongTask的call()方法,就已經(jīng)完成了一個長任務(wù)。 LongTask類中有這樣幾個常用的方法... setStateInfo() -?每次設(shè)置的TaskStateInfo對象包含三部分的信息——state、text和data。其中state是enum類型的狀態(tài)代碼,而text和data是當(dāng)前狀態(tài)附帶的信息,您可以根據(jù)自己的需要給text和data設(shè)置任意的值。目前Dorado支持的任務(wù)狀態(tài)有....waiting - 等待,例如當(dāng)任務(wù)調(diào)度器發(fā)現(xiàn)目前正在執(zhí)行某個任務(wù)的實例已經(jīng)達到了上限,而用戶仍希望再開啟新的長任務(wù),那么新的長任務(wù)將會進入等待隊列,即等待狀態(tài)。running - 正在執(zhí)行。suspending - 正在嘗試掛起。suspended - 掛起。resuming - 正在嘗試恢復(fù)執(zhí)行。terminated - 執(zhí)行結(jié)束。aborting - 正在中止,即取消執(zhí)行。aborted - 已終止,即已取消。error - 出錯并以退出。appendLog() - 向客戶端輸出日志信息,通常是TaskLog對象封裝的一段文本。
LongTask的StateInfo是可以重復(fù)設(shè)置的,比如同樣的是處于running狀態(tài),我們可以先設(shè)置為new TaskStateInfo(TaskState.running, "正在復(fù)制第1個文件"),然后再設(shè)置為new TaskStateInfo(TaskState.running, "正在復(fù)制第2個文件")。 那么既然StateInfo是可以重復(fù)設(shè)置的,它和Log有什么區(qū)別呢? 區(qū)別在于StateInfo是一種可以維持的信息,直到我們設(shè)置下一個StateInfo之前,當(dāng)前的StateInfo對于長任務(wù)而言都是有效的。而Log則瞬間的,更像是一種調(diào)試信息。 舉個例子,當(dāng)一個長任務(wù)正在執(zhí)行時我們刷新了網(wǎng)頁,那么當(dāng)網(wǎng)頁刷新完成后Dorado會自動的將該任務(wù)的StateInfo同步到Client端,用戶可以立即看到目前有一個長任務(wù)正在執(zhí)行,并且它的狀態(tài)是是“running-正在復(fù)制第2個文件”。而對于刷新網(wǎng)頁期間長任務(wù)輸出的Log信息,用戶是無法接收到放任,他只能看到從此刻開始的Log信息。
在聲明一個LongTask的方法時,我們還可以利用@TaskScheduler來為長任務(wù)配置簡單的調(diào)度信息。@TaskScheduler包含以下幾個屬性... scope - 任務(wù)的可見范圍,目前包含兩種取值 session(默認(rèn))和application。maxRunning - 最大可運行實例數(shù)。maxWaiting - 最大等待隊列的長度。impl - 自定義任務(wù)調(diào)度器的實現(xiàn)類。(待補充...)對于某個長任務(wù)而言,其在scope指定的范圍內(nèi)僅允許存在一個正在運行的實例(包括正在執(zhí)行的和正在等待執(zhí)行的)。因此當(dāng)scope為session時,maxRunning和maxWaiting所表示的是該長任務(wù)在整個系統(tǒng)中的運行數(shù)和等待數(shù),而不是指當(dāng)前Session中的。而當(dāng)scope為application時,maxRunning和maxWaiting將是無效的,因為此時整個系統(tǒng)中事實上只允許存在一個正在運行的實例(包括正在執(zhí)行的和正在等待執(zhí)行的)。
在LongTask執(zhí)行的過程中,我們可以通過客戶端LongTask的suspend()、resume()、abort()等方法來暫停、恢復(fù)或中止LongTask的執(zhí)行。不過這三個方法都不能自動的完成所有的工作,通常用戶都需要編寫一定的代碼才能真正的實現(xiàn)對LongTask的執(zhí)行控制。 以abort()操作為例,當(dāng)用戶在Client端調(diào)用了abort()方法后,Server端的LongTask對象會自動進入aborting狀態(tài),但是LongTask并不會自動的停下來,它仍會以正常的方式繼續(xù)執(zhí)行。因為我們不能強行的殺死LongTask目前的執(zhí)行線程,這種做法是不受推薦且存在隱患的。開發(fā)者需要自行判斷aborting狀態(tài),并且決定以何種方式讓LongTask優(yōu)雅的退出執(zhí)行。這聽起來有些麻煩,不過好在在絕大部分情況下我們并不需要去控制LongTask的執(zhí)行過程。 下面是一個支持suspend()、resume()、abort()這三種操作的LongTask的代碼實例,可以從這里獲得一些啟發(fā)...
@Expose
public LongTask copyFiles() {
return new LongTask() {
public Object call() throws Exception {
for (File file: files) {
synchronized (this) {
if (isSuspendRequired()) {
setStateInfo(new TaskStateInfo(TaskState.suspended);
wait();
}
if (isAbortRequired()) {
setStateInfo(new TaskStateInfo(TaskState.aborted));
break;
}
FileUtils.copy(file, destDir);
}
}
return null;
}
protected void doResume() {
notify();
setStateInfo(new TaskStateInfo(TaskState.running));
}
protected void doAbort() {
notify();
}
};
}
更多建議: