webqq更新——採用反向AJAX實如今線人員上下線模擬

原文: http://www.abigdreamer.com/mywork/webqq-update-online-reverse-ajax-implementation-off-the-assembly-line-simulation.html
本blog已轉移到造夢師http://www.abigdreamer.com,歡迎你們常去個人blog看看!

說明:本次更新只是模擬了一下人員的上下線,並無採用真是的數據(我一臺電腦開那麼多瀏覽器測試的話有點受不了,誰叫電腦爛呢,呵呵) 。另外須要注意的就是,須要用這個新的dwr.jar覆蓋掉原來的那個dwr.jar,要不會出問題的!

有朋友問我怎樣用反向AJAX來實如今線人員列表的更新,提到DWR容許javascript訪問服務器端的Java方法,這使得AJAX使用起來會比較容易,而在DWR2.0裏面添加了一個很是強大的功能——反向AJAX,也就是說,服務器端的Java方法能夠取得瀏覽器端的Web上下文,並能夠調用javascript的方法,將服務器端的數據異步地傳輸給瀏覽器。本文將經過一個demo展現這種特性。這個demo實現了相似股票交易系統中實時更新數據的功能,事實上是經過發佈-訂閱模式去實現的。也就是說,客戶端訂閱一個主題,服務器端經過一個線程向訂閱這個主題的瀏覽器定時、異步地發送數據,從而實現了這種實時更新的功能。
    咱們知道,客戶端瀏覽器能夠隨時鏈接到web服務器,並向服務器請求資源,而服務器卻沒有這種能力,它不能主動地於客戶端瀏覽器創建鏈接,並主動地將數據發送給瀏覽器。DWR支持3種從服務器端發送數據給客戶端的方式:
一、輪詢。客戶端在每一個時間週期內向服務器發送請求,看看服務器端有沒有數據更新,若是有,就向服務器請求數據。
二、Comet:基於HTTP長鏈接的服務器推進方式。客戶端向服務器發送請求後,服務器將數據經過response發送給客戶端,但並不會將此response關閉,而是一直經過response將最新的數據發送給客戶端瀏覽器,直到客戶端瀏覽器關閉。
三、PiggyBack(回傳)。服務器端將最新的數據排成隊列,而後等待客戶端下一次請求,接收到請求後就將等待更新的數據發給客戶端。
這3種方式各有優劣,而DWR能夠同時支持輪詢和Comet。
首先,咱們要讓DWR程序支持反向AJAX。只須要在web.xml中添加以下配置:
javascript

<servlet>
        <servlet-name>dwr-invoker</servlet-name>
        <servlet-class>org.directwebremoting.servlet.DwrServlet</servlet-class>
        <init-param>
            <param-name>debug</param-name>
            <param-value>true</param-value>
        </init-param>
        <init-param>
            <param-name>activeReverseAjaxEnabled</param-name>
            <param-value>true</param-value>
        </init-param>
        <init-param>
            <param-name>initApplicationScopeCreatorsAtStartup</param-name>
            <param-value>true</param-value>
        </init-param>
        <init-param>
            <param-name>maxWaitAfterWrite</param-name>
            <param-value>100</param-value>
        </init-param> 
        <load-on-startup>1</load-on-startup>
    </servlet>    
    <servlet-mapping>
        <servlet-name>dwr-invoker</servlet-name>
        <url-pattern>/dwr/*</url-pattern>
    </servlet-mapping>


將初始化參數activeReverseAjaxEnabled設置爲true表示啓動反向AJAX。另外,添加這個功能的核心之處在於服務器端的發佈者——Publisher.java,在這個類裏面,首先經過org.directwebremoting.WebContext來得到訪問這個應用的Web上下文:
html

WebContext webContext = WebContextFactory.get();
ServletContext servletContext = webContext.getServletContext();
serverContext = ServerContextFactory.get(servletContext);
webContext.getScriptSessionsByPage("");



這裏主要經過WebContext類得到DWR應用的WEB上下文,用ServletContext得到DWRServlet的上下文,以及經過WEB上下文獲取訪問本應用的客戶端瀏覽器的ScriptSession。經過ScriptSession,能夠在服務器端向客戶端瀏覽器發出執行js方法的指令,並把服務器端數據傳送給js方法,具體的用法以下:
java

Collection sessions = serverContext
						.getScriptSessionsByPage("/webQQ/webQQ.jsp");
				ScriptProxy proxy = new ScriptProxy(sessions);

				WebQQUser user = users.getNextUserStatu();
				proxy.addFunctionCall("publishUserStatus", user);



這段代碼首先經過getScriptSessionsByPage方法得到全部訪問/webQQ/webQQ.jsp這個資源的客戶端瀏覽器的ScriptSession,併爲這些session創建代理(ScriptProxy),經過這個代理,讓客戶端執行publishUserStatus的js方法。其中addFunctionCall就是向客戶端發送執行js方法的服務器端方法,第一個參數是js方法的簽名,後面的都是js方法的參數。user是要發佈的即時數據。user這個對象是隨機生成的(見org.darkness.test.webqq. WebQQUsers類),Publish.java這個類啓動了一個線程(worker),這個線程不斷地生成user的數據,併發布給客戶端。
如下是html頁面的核心部分的代碼:
node

var listeners = [];

function subscribeUserStatus(callBackHandler){
	listeners.push(callBackHandler);
}

function publishUserStatus(user){
	for(var i=0;i<listeners.length;i++){
		listeners[i].fun.call(listeners[i].scope,user);
	}
}

subscribeUserStatus({
	fun:function(user){
		try{
			var rootChildNodes = Ext.getCmp('im-tree').root.childNodes;
			for(var i=0;i<rootChildNodes.length;i++){
				var childNodes = rootChildNodes[i].childNodes;
				for(var j=0;j<childNodes.length;j++){
					if(childNodes[j].id == user.account){
						if(user.isShowOnline == 0){
							childNodes[j].getUI().getIconEl().src = 'images/user_delete.gif';
							
							var tempChild = childNodes[j];
							// appendChild就會將其放置到最尾端,不須要再remove了
							//rootChildNodes[i].removeChild(tempChild);
							rootChildNodes[i].appendChild(tempChild);
						}else{
							childNodes[j].getUI().getIconEl().src = 'images/user.gif';
							
							// 將剛上線的人員移動到最頂端
							var firstChild = rootChildNodes[i].firstChild;
							var tempChild = childNodes[j];
							rootChildNodes[i].insertBefore(tempChild, firstChild);
						}
						
						// 原本想用TreeNode自帶的排序方法的,可試過了,老是沒反應
						//rootChildNodes[i].sort(function(nodeA, nodeB){
						//	return nodeA.getUI().getIconEl().src > nodeB.getUI().getIconEl().src;
						//}, rootChildNodes[i]);
						return;
					}
				}
			}
			
		}catch(e){
		}
	},
	scope:this
});
dwr.engine.setActiveReverseAjax(true);


這一塊代碼主要是將改變在線或下線的用戶的圖標。
subscribeUserStatus方法訂閱在線人員列表的數據(這些數據由服務器端的Java方法進行發佈)。其中經過一個匿名回調函數,將取得改變的數據在頁面顯示出來(即人員的上、下線)。該回調方法很是簡單,只是將TreeNode組件中的圖標改變了一下,實現了實時提醒用戶上下線的功能。
另外,服務器端還有一個監聽器PublisherServletContextListener,這是爲了在適當的時候關閉發佈者的線程。這個監聽器要結合其餘兩個DWR的監聽器使用,只需在web.xml裏面聲明就好了:
web

<listener>
     <listener-class>org.directwebremoting.servlet.EfficientShutdownServletContextAttributeListener</listener-class>
</listener>
<listener>
        <listener-class>org.directwebremoting.servlet.EfficientShutdownServletContextListener</listener-class>
    </listener>
    <listener>
        <listener-class> org.darkness.webqq.web.servlet.PublisherServletContextListener</listener-class>
    </listener>


最後,看一下dwr的映射關係dwr.xml:
ajax

<dwr>
  <allow>
    
    <create creator="new" javascript="Publisher" scope="application">
      <param name="class" value="org.darkness.webqq.web.servlet.Publisher"/>
    </create>
    <convert converter="bean" match="org.darkness.webqq.model.WebQQUser"/>

    <!-- 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>


注意<convert converter="bean" match="org.darkness.webqq.model.WebQQUser"/>這個配置,dwr容許將自定義的Java類型與js對象進行相互轉換,但要聲明轉換器。
如下是程序運行的結果:
瀏覽器

相關文章
相關標籤/搜索