上一篇文章(DWR詳解一)提到DWR允許javascript訪問伺服器端的Java方法,這使得AJAX使用起來會比較容易,而在DWR2.0裡面添加了一個非常強大的功能——反向AJAX,也就是說,伺服器端的Java方法可以取得瀏覽器端的Web上下文,並可以調用javascript的方法,將伺服器端的資料非同步地傳輸給瀏覽器。本文將通過一個demo展示這種特性。這個demo實現了類似股票交易系統中即時更新資料的功能,事實上是通過發布-訂閱模式去實現的。也就是說,客訂閱一個主題,伺服器端通過一個線程向訂閱這個主題的瀏覽器定時、非同步地發送資料,從而實現了這種即時更新的功能。工程代碼請點擊這裡下載:reverseAjaxDemo
我們知道,用戶端瀏覽器可以隨時串連到web伺服器,並向伺服器請求資源,而伺服器卻沒有這種能力,它不能主動地於用戶端瀏覽器建立串連,並主動地將資料發送給瀏覽器。DWR支援3種從伺服器端發送資料給用戶端的方式:
1、輪詢。用戶端在每個時間周期內向伺服器發送請求,看看伺服器端有沒有資料更新,如果有,就向伺服器請求資料。
2、Comet:基於HTTP長已連線的服務器推動方式。用戶端向伺服器發送請求後,伺服器將資料通過response發送給用戶端,但並不會將此response關閉,而是一直通過response將最新的資料發送給用戶端瀏覽器,直到用戶端瀏覽器關閉。
3、PiggyBack(回傳)。伺服器端將最新的資料排成隊列,然後等待用戶端下一次請求,接收到請求後就將等待更新的資料發給用戶端。
這3種方式各有優劣,而DWR可以同時支援輪詢和Comet。
首先,我們要讓DWR程式支援反向AJAX。只需要在web.xml中DWRServlet裡添加一個初始化參數:
<servlet><servlet-name>dwr-invoker</servlet-name><servlet-class>org.directwebremoting.servlet.DwrServlet</servlet-class><init-param><param-name>activeReverseAjaxEnabled</param-name><param-value>true</param-value></init-param>...</servlet>
另外,這個demo使用了TIBCO General Interface(GI)的AJAX架構,因此需要引入JSX這個組件庫以及在gidemo目錄下在config.xml和appCanvas.xml定義需要使用的組件(主要是用到Matrix資料表格這個組件),關於TIBCO GI,請參考http://www.tibco.com/devnet/index.html
這個demo的核心之處在於伺服器端的發行者——Publisher.java,在這個類裡面,首先通過org.directwebremoting.WebContext來獲得訪問這個應用的Web上下文:
WebContext webContext = WebContextFactory.get();
ServletContext servletContext = webContext.getServletContext();
serverContext = ServerContextFactory.get(servletContext);
webContext.getScriptSessionsByPage("");
這裡主要通過WebContext類獲得DWR應用的WEB上下文,用ServletContext獲得DWRServlet的上下文,以及通過WEB上下文擷取訪問本應用的用戶端瀏覽器的ScriptSession。通過ScriptSession,可以在伺服器端向用戶端瀏覽器發出執行js方法的指令,並把伺服器端資料傳送給js方法,具體的用法如下:
Collection sessions = serverContext.getScriptSessionsByPage("/reverseAjaxDemo/index.html");
ScriptProxy proxy = new ScriptProxy(sessions);
Corporation corp = corporations.getNextChangedCorporation();
proxy.addFunctionCall("OpenAjax.publish", "gidemo", "corporation", corp);
這段代碼首先通過getScriptSessionsByPage方法獲得所有訪問/reverseAjaxDemo/index.html這個資源的用戶端瀏覽器的ScriptSession,並為這些session建立代理(ScriptProxy),通過這個代理,讓用戶端執行OpenAjax.publish的js方法(見OpenAjax.js)。其中addFunctionCall就是向用戶端發送執行js方法的伺服器端方法,第一個參數是js方法的簽名,後面的都是js方法的參數。其中"gidemo"是伺服器端發布的主題(topic),"coporation"是要發布的變數,而corp則是要發布的即時資料。corp這個對象是隨機產生的(見Corporation和Corporations類),Publish.java這個類啟動了一個線程(worker),這個線程不斷地產生corporation的資料,並發布給用戶端。
以下是html頁面的核心部分的代碼:
<div style="width:100%; height:280px;" id="gidemo">
<script. type="text/javascript"
src="JSX/js/JSX30.js"
jsxapppath="gidemo"
jsxlt="true"> </script>
</div>
這一塊代碼主要是使用了GI的matrix組件,該組件可以動態載入資料。另外,頁面引入了index.js,裡面有兩個個主要方法:
function giLoaded() {
OpenAjax.subscribe("gidemo", "corporation", objectPublished);
dwr.engine.setActiveReverseAjax(true);
}
function objectPublished(prefix, name, handlerData, corporation) {
var matrix = giApp.getJSXByName("matrix");
var inserted = matrix.getRecordNode(corporation.jsxid);
matrix.insertRecord(corporation, null, inserted == null);
// There are many ways to get a table to repaint.
// One easy way is to ask it to repaint:
// matrix.repaintData();
// But we are going for a fancy option that does highlighting
for (var property in corporation) {
if (property != "jsxid") {
var x = matrix.getContentElement(corporation.jsxid, property);
if (ox) {
var current = ox.innerHTML;
if (current != "" + corporation[property]) {
ox.style.backgroundColor = "#FDE4EB";
var callback = function(ele) {
return function() { ele.style.backgroundColor = "#FFFFFF"; };
}(ox);
setTimeout(callback, 1000);
ox.innerHTML = corporation[property];
}
}
}
}
}
giLoaded方法通過OpenAjax.subscribe方法訂閱主題為"gidemo"的資料(這些資料由伺服器端的Java方法進行發布)。其中objectPublished是一個回調方法,表示取得資料後頁面的改變。該回調方法非常簡單,只是將matrix組件中發生變化的資料改變一下顏色,實現了即時提醒資料更新的功能。
另外,伺服器端還有一個監聽器PublisherServletContextListener,這是為了在適當的時候關閉發行者的線程。這個監聽器要結合其他兩個DWR的監聽器使用,只需在web.xml裡面聲明就行了:
<listener>
<listener-class>org.directwebremoting.servlet.EfficientShutdownServletContextAttributeListener</listener-class>
</listener>
<listener>
<listener-class>org.directwebremoting.servlet.EfficientShutdownServletContextListener</listener-class>
</listener>
<listener>
<listener-class>gidemo.PublisherServletContextListener</listener-class>
</listener>
最後,看一下dwr的映射關係dwr.xml:
<dwr>
<allow>
<create creator="new" javascript="JDate">
<param name="class" value="java.util.Date"/>
</create>
<create creator="new" javascript="Publisher" scope="application">
<param name="class" value="gidemo.Publisher"/>
</create>
<convert converter="bean" match="gidemo.Corporation"/>
<!-- this is a bad idea for live, but can be useful in testing -->
<convert converter="exception" match="java.lang.Exception"/>
<convert converter="bean" match="java.lang.StackTraceElement"/>
</allow>
</dwr>
注意紅色部分的配置,dwr允許將自訂的Java類型與js對象進行相互轉換,但要聲明轉換器。
以下是程式啟動並執行結果:
這個例子比較複雜,旨在讓大家對反向AJAX的原理有所瞭解。下一篇文章開始,將用一些簡單的例子來說明DWR反向AJAX的用法。
參考資料:
http://getahead.org/dwr/reverse-ajax