天天看點

整合 GWT 與 Jetty Continuations

雖然GWT充滿了争議,但是對于Java開發者而言,能夠使用自己最熟悉的語言來進行Ajax開發,Google推出的GWT的确具有相當大的誘惑力,而且作為一個快速發展的開源架構,相信以後它的使用者群應該會越來越多。

Jetty Continuations 很好的解決了伺服器更新用戶端的問題,伺服器不用再為每一個等待響應的用戶端單獨建立一個線程,借助Continuations和新IO,可以在有限的線程内支援更多使用者。

更多内容參見[url]http://docs.codehaus.org/display/JETTY/Continuations[/url]

最近在做一個浏覽器上的線上聊天項目,用來做為線上客服支援,打算使用GWT編寫界面,但是GWT所提供RPC機制并不直接支援Jetty Continuations。為了支援比目前線程數更多的使用者,Jetty中的Continuation.suspend()會抛出一個特殊的運作時異常:RetryRequest。這個異常将傳播到servlet以外,然後通過過濾器傳回,再由SelectChannelConnector捕獲,将請求放入處于等待狀态的Continuation隊列中,此時HTTP連接配接并不關閉,而目前的線程卻可以被放回線程池,供别的請求使用。但是使用GWT時,GWT捕獲了所有的Throwable,這樣就會導緻Continuations機制失敗。而且GWT提供的RemoteServiceServlet類中很多方法都被定義為final或static,于是你無法通過繼承這個類來重寫相關方法,讓它放過RetryRequest。

但是因為GWT是開源項目,于是Jetty組織改寫RemoteServiceServlet,提供了OpenRemoteServiceServlet,将Continuations所敏感的方法去掉final和static,然後繼承OpenRemoteServiceServlet,提供了它們自己的AsyncRemoteServiceServlet,這樣我們GWT伺服器端的RPC接口的實作類直接繼承AsyncRemoteServiceServlet,無須其它更改,就可以使用Jetty Continuations的API了。

AsyncRemoteServiceServlet14.java中放過RetryRequest的相關代碼:

[code]

protected void throwIfRetyRequest(Throwable caught) {

if (caught instanceof UnexpectedException) {

caught = caught.getCause();

}

if (caught instanceof RuntimeException && JETTY_RETRY_REQUEST_EXCEPTION.equals(caught.getClass().getName())) {

throw (RuntimeException) caught;

}

}

[/code]

更多内容參見[url]http://blogs.webtide.com/gregw/2006/12/07/1165517549286.html[/url]

這裡提供一個完整的小例子給大家,也作為我這段時間學習的總結 :wink: :

下載下傳最新的

GWT [url]http://code.google.com/webtoolkit/download.html[/url]

Jetty [url]http://docs.codehaus.org/display/JETTY/Downloading+and+Installing#download[/url]

然後在[url]http://jira.codehaus.org/browse/JETTY-399[/url]上面找到OpenRemoteServiceServlet14.java和AsyncRemoteServiceServlet14.java,這是OpenRemoteServiceServlet和AsyncRemoteServiceServlet的新版本,支援GWT1.4版本。

首先使用GWT的指令行工具建立eclipse下的GWT項目,然後導入到eclipse中,再導入jetty-util-6.1.3.jar。

RPC接口定義:

[code]

public interface TestService extends RemoteService {

public String getNews();

}

[/code]

GWT的入口類:

[code]

public class GCEntry implements EntryPoint {

public void onModuleLoad() {

final TestServiceAsync testService = (TestServiceAsync)GWT.create(TestService.class);

ServiceDefTarget target = (ServiceDefTarget)testService;

target.setServiceEntryPoint(GWT.getModuleBaseURL() + "test");

final TextArea printArea = new TextArea();

printArea.setVisibleLines(10);

printArea.setCharacterWidth(30);

testService.getNews(new AsyncCallback() {

public void onFailure(Throwable caught) { }

public void onSuccess(Object result) {

printArea.setText(printArea.getText() + result);

testService.getNews(this);

}

});

DockPanel dp = new DockPanel();

dp.add(printArea, DockPanel.CENTER);

RootPanel.get().add(dp);

}

}

[/code]

界面将顯示一個文本框,反複調用testService.getNews(),将異步傳回的結果輸出到文本框中。

伺服器端RPC接口的實作:

[code]

public class TestServiceImpl extends AsyncRemoteServiceServlet14 implements

TestService {

private NewsCreator newsCreator;

public void init() {

newsCreator = new NewsCreator();

}

public String getNews() {

return newsCreator.getNews(getThreadLocalRequest());

}

}

[/code]

注意這裡繼承的是AsyncRemoteServiceServlet14。通過一個輔助類NewsCreator來生成新的時間:

[code]

public class NewsCreator implements Runnable {

private Set<Continuation> readers;

public NewsCreator() {

readers = new HashSet<Continuation>();

new Thread(this).start();

}

public void run() {

while (true) {

synchronized(this) {

for (Continuation continuation : readers) {

continuation.setObject(new Date().toString() + "\r\n");

continuation.resume();

}

readers.clear();

}

try {

Thread.sleep(2000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

public String getNews(HttpServletRequest request) {

Continuation continuation = ContinuationSupport.getContinuation(request, null);

synchronized(this) {

readers.add(continuation);

}

continuation.suspend(10000);

String news = continuation.getObject() == null ? "No Time" : (String)continuation.getObject();

continuation.setObject(null);

return news;

}

}

[/code]

getNews()為每個請求擷取一個Continuation執行個體,然後将這個執行個體儲存起來,阻塞10秒,同時NewsCreator每隔兩秒釋出一次時間,釋出時将目前時間附在Continuation上,恢複被阻塞的Continuation。Continuation在被恢複或逾時之後相對應的請求都會被重新執行,當再次執行到Continuation.suspend()時,這個方法會馬上傳回,然後繼續執行suspend()後面的代碼,傳回上一次釋出的時間或是"No Time",當然這裡釋出的間隔比阻塞的時間小,不會出現"No Time"。

完整代碼見附件

繼續閱讀