整合GWT與Jetty Continuations

雖然GWT充滿了爭議,但是對於Java開發者而言,能夠使用自己最熟悉的語言來進行Ajax開發,Google推出的GWT的確具有相當大的吸引力,而且作爲一個快速發展的開源框架,相信以後它的用戶羣應該會越來越多。

Jetty Continuations 很好的解決了服務器更新客戶端的問題,服務器不用再爲每一個等待響應的客戶端單獨建立一個線程,藉助Continuations和新IO,可以在有限的線程內支持更多用戶。
更多內容參見
http://docs.codehaus.org/display/JETTY/Continuations

最近在做一個瀏覽器上的在線聊天項目,用來做爲在線客服支持,打算使用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的相關代碼:
Java代碼
/**  
* Throws the Jetty RetryRequest if found.  
*  
* @param caught the exception  
*/  
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;   
    }   
}  

更多內容參見
http://blogs.webtide.com/gregw/2006/12/07/1165517549286.html

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

下載最新的
GWT
http://code.google.com/webtoolkit/download.html
Jetty
http://docs.codehaus.org/display ... Installing#download
然後在
http://jira.codehaus.org/browse/JETTY-399上面找到OpenRemoteServiceServlet14.java和AsyncRemoteServiceServlet14.java,這是OpenRemoteServiceServlet和AsyncRemoteServiceServlet的新版本,支持GWT1.4版本。

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

RPC接口定義:
Java代碼
public interface TestService extends RemoteService {   
  
    public String getNews();   
      
}  

GWT的入口類:
Java代碼
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);   
    }   
  
}  

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

服務器端RPC接口的實現:
Java代碼
public class TestServiceImpl extends AsyncRemoteServiceServlet14 implements  
        TestService {   
      
    private NewsCreator newsCreator;   
      
    public void init() {   
        newsCreator = new NewsCreator();   
    }   
      
    public String getNews() {   
        return newsCreator.getNews(getThreadLocalRequest());   
    }   
  
}  

注意這裏繼承的是AsyncRemoteServiceServlet14。通過一個輔助類NewsCreator來生成新的時間:
Java代碼
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;   
    }   
  
}  

getNews()爲每個請求獲取一個Continuation實例,然後將這個實例保存起來,阻塞10秒,同時NewsCreator每隔兩秒發佈一次時間,發佈時將當前時間附在Continuation上,恢復被阻塞的Continuation。Continuation在被恢復或超時之後相對應的請求都會被重新執行,當再次執行到Continuation.suspend()時,這個方法會馬上返回,然後繼續執行suspend()後面的代碼,返回上一次發佈的時間或是"No Time",當然這裏發佈的間隔比阻塞的時間小,不會出現"No Time"。

 

轉自:http://codinghome.cn/viewthread.php?tid=367

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章