雖然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"。
完整代碼見附件