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种从服务器端发送数据给客户端的方式:
1、轮询。客户端在每个时间周期内向服务器发送请求,看看服务器端有没有数据更新,如果有,就向服务器请求数据。
2、Comet:基于HTTP长连接的服务器推动方式。客户端向服务器发送请求后,服务器将数据通过response发送给客户端,但并不会将此response关闭,而是一直通过response将最新的数据发送给客户端浏览器,直到客户端浏览器关闭。
3、PiggyBack(回传)。服务器端将最新的数据排成队列,然后等待客户端下一次请求,接收到请求后就将等待更新的数据发给客户端。
这3种方式各有优劣,而DWR可以同时支持轮询和Comet。
首先,我们要让DWR程序支持反向AJAX。只需要在web.xml中添加如下配置:

<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上下文:

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("/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页面的核心部分的代码:

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里面声明就行了:

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

<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对象进行相互转换,但要声明转换器。
以下是程序运行的结果:
webqq更新——采用反向AJAX实现在线人员上下线模拟

你可能感兴趣的:(Ajax,应用服务器,mysql,浏览器,DWR)