Spring整合DWR comet 實(shí)現(xiàn)無(wú)刷新 多人聊天室
用dwr的comet(推)來(lái)實(shí)現(xiàn)簡(jiǎn)單的無(wú)刷新多人聊天室,comet是長(zhǎng)連接的一種。通常我們要實(shí)現(xiàn)無(wú)刷新,一般會(huì)使用到Ajax。Ajax 應(yīng)用程序可以使用兩種基本的方法解決這一問(wèn)題:一種方法是瀏覽器每隔若干秒時(shí)間向服務(wù)器發(fā)出輪詢(xún)以進(jìn)行更新,另一種方法是服務(wù)器始終打開(kāi)與瀏覽器的連接并在數(shù)據(jù)可用時(shí)發(fā)送給瀏覽器。***種方法一般利用setTimeout或是setInterval定時(shí)請(qǐng)求,并返回***數(shù)據(jù),這無(wú)疑增加了服務(wù)器的負(fù)擔(dān),浪費(fèi)了大量的資源。而第二種方法也會(huì)浪費(fèi)服務(wù)器資源,長(zhǎng)期的建立連接;而相對(duì)***種來(lái)說(shuō),第二種方式會(huì)更優(yōu)于***種方法;這里有一個(gè)一對(duì)多和多對(duì)一的關(guān)系,而comet向多個(gè)客戶(hù)端推送數(shù)據(jù)就是一對(duì)多的關(guān)系。而具體使用哪種方式,要看你當(dāng)前的需求而定,沒(méi)有絕對(duì)的。
為什么使用Comet?
輪詢(xún)方法的主要缺點(diǎn)是:當(dāng)擴(kuò)展到更多客戶(hù)機(jī)時(shí),將生成大量的通信量。每個(gè)客戶(hù)機(jī)必須定期訪(fǎng)問(wèn)服務(wù)器以檢查更新,這為服務(wù)器資源添加了更多負(fù)荷。最壞的一種情況是對(duì)不頻繁發(fā)生更新的應(yīng)用程序使用輪詢(xún),例如一種 Ajax 郵件 Inbox。在這種情況下,相當(dāng)數(shù)量的客戶(hù)機(jī)輪詢(xún)是沒(méi)有必要的,服務(wù)器對(duì)這些輪詢(xún)的回答只會(huì)是 “沒(méi)有產(chǎn)生新數(shù)據(jù)”。雖然可以通過(guò)增加輪詢(xún)的時(shí)間間隔來(lái)減輕服務(wù)器負(fù)荷,但是這種方法會(huì)產(chǎn)生不良后果,即延遲客戶(hù)機(jī)對(duì)服務(wù)器事件的感知。當(dāng)然,很多應(yīng)用程序可以實(shí)現(xiàn)某種權(quán)衡,從而獲得可接受的輪詢(xún)方法。
盡管如此,吸引人們使用 Comet 策略的其中一個(gè)優(yōu)點(diǎn)是其顯而易見(jiàn)的高效性??蛻?hù)機(jī)不會(huì)像使用輪詢(xún)方法那樣生成煩人的通信量,并且事件發(fā)生后可立即發(fā)布給客戶(hù)機(jī)。但是保持長(zhǎng)期連接處于打開(kāi)狀態(tài)也會(huì)消耗服務(wù)器資源。當(dāng)?shù)却隣顟B(tài)的 servlet 持有一個(gè)持久性請(qǐng)求時(shí),該 servlet 會(huì)獨(dú)占一個(gè)線(xiàn)程。這將限制 Comet 對(duì)傳統(tǒng) servlet 引擎的可伸縮性,因?yàn)榭蛻?hù)機(jī)的數(shù)量會(huì)很快超過(guò)服務(wù)器棧能有效處理的線(xiàn)程數(shù)量。
如果本示例結(jié)合Jetty應(yīng)用服務(wù)器效果會(huì)更好。
開(kāi)發(fā)環(huán)境:
System:Windows
WebBrowser:IE6+、Firefox3+
JavaEE Server:tomcat5.0.2.8、tomcat6
IDE:eclipse、MyEclipse 8
開(kāi)發(fā)依賴(lài)庫(kù):
JavaEE5、Spring 3.0.5、dwr 3
Email:hoojo_@126.com
Blog:http://blog.csdn.net/IBM_hoojo or http://hoojo.cnblogs.com/
#p#
 一、準(zhǔn)備工作 1、 下載dwr的相關(guān)jar包 https://java.net/downloads/dwr/Development%20Builds/Build%20116/dwr.jar 程序中還需要spring的相關(guān)jar包 http://ebr.springsource.com/repository/app/library/version/detail?name=org.springframework.spring&version=3.0.5.RELEASE 需要的jar包如下 2、 建立一個(gè)WebProject,名稱(chēng)DWRComet  在web.xml中添加dwr、spring配置如下: 3、 在src目錄加入applicationContext-beans.xml配置,這個(gè)配置專(zhuān)門(mén)配置bean對(duì)象,用來(lái)配置需要注入的對(duì)象。 4、 在WEB-INF目錄添加dwr.xml文件,基本代碼如下
以上的準(zhǔn)備基本完畢,下面來(lái)完成無(wú)刷新聊天室代碼
#p#
二、聊天室相關(guān)業(yè)務(wù)實(shí)現(xiàn)
1、 聊天實(shí)體類(lèi)Model
- package com.hoo.entity;
 - import java.util.Date;
 - /**
 - * <b>function:</b>
 - * @author hoojo
 - * @createDate 2011-6-3 下午06:40:07
 - * @file Message.java
 - * @package com.hoo.entity
 - * @project DWRComet
 - * @blog http://blog.csdn.net/IBM_hoojo
 - * @email hoojo_@126.com
 - * @version 1.0
 - */
 - public class Message {
 - private int id;
 - private String msg;
 - private Date time;
 - //getter、setter
 - }
 
2、 編寫(xiě)聊天信息的事件
- package com.hoo.chat;
 - import org.springframework.context.ApplicationEvent;
 - /**
 - * <b>function:</b>發(fā)送聊天信息事件
 - * @author hoojo
 - * @createDate 2011-6-7 上午11:24:21
 - * @file MessageEvent.java
 - * @package com.hoo.util
 - * @project DWRComet
 - * @blog http://blog.csdn.net/IBM_hoojo
 - * @email hoojo_@126.com
 - * @version 1.0
 - */
 - public class ChatMessageEvent extends ApplicationEvent {
 - private static final long serialVersionUID = 1L;
 - public ChatMessageEvent(Object source) {
 - super(source);
 - }
 - }
 
繼承ApplicationEvent,構(gòu)造參數(shù)用于傳遞發(fā)送過(guò)來(lái)的消息。這個(gè)事件需要一個(gè)監(jiān)聽(tīng)器監(jiān)聽(tīng),一旦觸發(fā)了這個(gè)事件,我們就可以向客戶(hù)端發(fā)送消息。
3、 發(fā)送消息服務(wù)類(lèi),用戶(hù)客戶(hù)端發(fā)送消息。dwr需要暴露這個(gè)類(lèi)里面的發(fā)送消息的方法
- package com.hoo.chat;
 - import org.springframework.beans.BeansException;
 - import org.springframework.context.ApplicationContext;
 - import org.springframework.context.ApplicationContextAware;
 - import com.hoo.entity.Message;
 - /**
 - * <b>function:</b>客戶(hù)端發(fā)消息服務(wù)類(lèi)業(yè)務(wù)
 - * @author hoojo
 - * @createDate 2011-6-7 下午02:12:47
 - * @file ChatService.java
 - * @package com.hoo.chat
 - * @project DWRComet
 - * @blog http://blog.csdn.net/IBM_hoojo
 - * @email hoojo_@126.com
 - * @version 1.0
 - */
 - public class ChatService implements ApplicationContextAware {
 - private ApplicationContext ctx;
 - public void setApplicationContext(ApplicationContext ctx) throws BeansException {
 - this.ctx = ctx;
 - }
 - /**
 - * <b>function:</b> 向服務(wù)器發(fā)送信息,服務(wù)器端監(jiān)聽(tīng)ChatMessageEvent事件,當(dāng)有事件觸發(fā)就向所有客戶(hù)端發(fā)送信息
 - * @author hoojo
 - * @createDate 2011-6-8 下午12:37:24
 - * @param msg
 - */
 - public void sendMessage(Message msg) {
 - //發(fā)布事件
 - ctx.publishEvent(new ChatMessageEvent(msg));
 - }
 - }
 
上面的sendMessage需要瀏覽器客戶(hù)端調(diào)用此方法完成消息的發(fā)布,傳遞一個(gè)Message對(duì)象,并且是觸發(fā)ChatMessageEvent事件。
#p#
4、 編寫(xiě)監(jiān)聽(tīng)器監(jiān)聽(tīng)客戶(hù)端是否觸發(fā)ChatMessageEvent
- package com.hoo.chat;
 - import java.util.Collection;
 - import java.util.Date;
 - import javax.servlet.ServletContext;
 - import org.directwebremoting.ScriptBuffer;
 - import org.directwebremoting.ScriptSession;
 - import org.directwebremoting.ServerContext;
 - import org.directwebremoting.ServerContextFactory;
 - import org.springframework.context.ApplicationEvent;
 - import org.springframework.context.ApplicationListener;
 - import org.springframework.web.context.ServletContextAware;
 - import com.hoo.entity.Message;
 - /**
 - * <b>function:</b>監(jiān)聽(tīng)客戶(hù)端事件,想客戶(hù)端推出消息
 - * @author hoojo
 - * @createDate 2011-6-7 上午11:33:08
 - * @file SendMessageClient.java
 - * @package com.hoo.util
 - * @project DWRComet
 - * @blog http://blog.csdn.net/IBM_hoojo
 - * @email hoojo_@126.com
 - * @version 1.0
 - */
 - @SuppressWarnings("unchecked")
 - public class ChatMessageClient implements ApplicationListener, ServletContextAware {
 - private ServletContext ctx;
 - public void setServletContext(ServletContext ctx) {
 - this.ctx = ctx;
 - }
 - @SuppressWarnings("deprecation")
 - public void onApplicationEvent(ApplicationEvent event) {
 - //如果事件類(lèi)型是ChatMessageEvent就執(zhí)行下面操作
 - if (event instanceof ChatMessageEvent) {
 - Message msg = (Message) event.getSource();
 - ServerContext context = ServerContextFactory.get();
 - //獲得客戶(hù)端所有chat頁(yè)面script session連接數(shù)
 - Collection<ScriptSession> sessions = context.getScriptSessionsByPage(ctx.getContextPath() + "/chat.jsp");
 - for (ScriptSession session : sessions) {
 - ScriptBuffer sb = new ScriptBuffer();
 - Date time = msg.getTime();
 - String s = time.getYear() + "-" + (time.getMonth() + 1) + "-" + time.getDate() + " "
 - + time.getHours() + ":" + time.getMinutes() + ":" + time.getSeconds();
 - //執(zhí)行setMessage方法
 - sb.appendScript("showMessage({msg: '")
 - .appendScript(msg.getMsg())
 - .appendScript("', time: '")
 - .appendScript(s)
 - .appendScript("'})");
 - System.out.println(sb.toString());
 - //執(zhí)行客戶(hù)端script session方法,相當(dāng)于瀏覽器執(zhí)行JavaScript代碼
 - //上面就會(huì)執(zhí)行客戶(hù)端瀏覽器中的showMessage方法,并且傳遞一個(gè)對(duì)象過(guò)去
 - session.addScript(sb);
 - }
 - }
 - }
 - }
 
上面的代碼主要是監(jiān)聽(tīng)客戶(hù)端的事件,一旦客戶(hù)端有觸發(fā)ApplicationEvent事件或是其子類(lèi),就會(huì)執(zhí)行onApplicationEvent方法。代碼中通過(guò)instanceof判斷對(duì)象實(shí)例,然后再執(zhí)行。如果有觸發(fā)ChatMessageEvent事件,就獲取所有連接chat.jsp這個(gè)頁(yè)面的ScriptSession。然后像所有的ScriptSession中添加script。這樣被添加的ScriptSession就會(huì)在有連接chat.jsp的頁(yè)面中執(zhí)行。
所以這就是客戶(hù)端為什么會(huì)執(zhí)行服務(wù)器端的JavaScript代碼。但前提是需要在web.xml中添加dwrComet配置以及在chat頁(yè)面添加ajax反轉(zhuǎn)。
5、 下面開(kāi)始在bean容器和dwr的配置中添加我們的配置
applicationContext-beans.xml配置
- <bean id="chatService" class="com.hoo.chat.ChatService"/>
 - <bean id="chatMessageClient" class="com.hoo.chat.ChatMessageClient"/>
 
上面的chatService會(huì)在dwr配置中用到
dwr.xml配置
- <allow>
 - <convert match="com.hoo.entity.Message" converter="bean">
 - <param name="include" value="msg,time" />
 - </convert>
 - <create creator="spring" javascript="ChatService">
 - <param name="beanName" value="chatService" />
 - </create>
 - </allow>
 
charService的sendMessage方法傳遞的是Message對(duì)象,所以要配置Message對(duì)象的convert配置。
上面的create的creator是spring,表示在spring容器中拿chatService對(duì)象。里面的參數(shù)的beanName表示在spring容器中找name等于charService的bean對(duì)象。
#p#
6、 客戶(hù)端chat.jsp頁(yè)面代碼
- <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
 - <%
 - String path = request.getContextPath();
 - String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
 - %>
 - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
 - <html>
 - <head>
 - <base href="<%=basePath%>">
 - <title>Chat</title>
 - <meta http-equiv="pragma" content="no-cache">
 - <meta http-equiv="cache-control" content="no-cache">
 - <meta http-equiv="expires" content="0">
 - <script type="text/javascript" src="${pageContext.request.contextPath }/dwr/engine.js"></script>
 - <script type="text/javascript" src="${pageContext.request.contextPath }/dwr/util.js"></script>
 - <script type="text/javascript" src="${pageContext.request.contextPath }/dwr/interface/ChatService.js"></script>
 - <script type="text/javascript">
 - function send() {
 - var time = new Date();
 - var content = dwr.util.getValue("content");
 - var name = dwr.util.getValue("userName");
 - var info = encodeURI(encodeURI(name + " say:\n" + content));
 - var msg = {"msg": info, "time": time};
 - dwr.util.setValue("content", "");
 - if (!!content) {
 - ChatService.sendMessage(msg);
 - } else {
 - alert("發(fā)送的內(nèi)容不能為空!");
 - }
 - }
 - function showMessage(data) {
 - var message = decodeURI(decodeURI(data.msg));
 - var text = dwr.util.getValue("info");
 - if (!!text) {
 - dwr.util.setValue("info", text + "\n" + data.time + " " + message);
 - } else {
 - dwr.util.setValue("info", data.time + " " + message);
 - }
 - }
 - </script>
 - </head>
 - <body onload="dwr.engine.setActiveReverseAjax(true);">
 - <textarea rows="20" cols="60" id="info" readonly="readonly"></textarea>
 - <hr/>
 - 昵稱(chēng):<input type="text" id="userName"/><br/>
 - 消息:<textarea rows="5" cols="30" id="content"></textarea>
 - <input type="button" value=" Send " onclick="send()" style="height: 85px; width: 85px;"/>
 - </body>
 - </html>
 
首先,你需要導(dǎo)入dwr的engine.js文件,這個(gè)很重要,是dwr的引擎文件。其次你使用的那個(gè)類(lèi)的方法,也需要在導(dǎo)入進(jìn)來(lái)。一般是interface下的,并且在dwr.xml中配置過(guò)的create。
上面的js中調(diào)用的charService類(lèi)中的sendMessage方法,所以在jsp頁(yè)面中導(dǎo)入的是ChatService.js。
在body的onload事件中,需要設(shè)置反轉(zhuǎn)Ajax,這個(gè)很重要。
showMessage是ChatMessageClient的onApplicationEvent方法中的appendScript中需要執(zhí)行的方法。data參數(shù)也是在那里傳遞過(guò)來(lái)的。
每當(dāng)發(fā)送sendMessage方法后就會(huì)觸發(fā)ChatMessageEvent事件,然后監(jiān)聽(tīng)的地方就會(huì)執(zhí)行onApplicationEvent方法,在這個(gè)方法中又會(huì)執(zhí)行瀏覽器中的showMessage方法。
原文鏈接:http://www.cnblogs.com/hoojo/archive/2011/06/08/2075201.html
【編輯推薦】
- Oracle計(jì)劃修復(fù)Java SE中的17個(gè)漏洞
 - Java SE 6新年***次更新 修復(fù)Bug超300個(gè)
 - JDK 5和Java SE 6小更新
 - Java SE 6更新 修復(fù)重大安全問(wèn)題
 - Java SE 6中的垃圾回收器G1收費(fèi)是虛驚一場(chǎng)
 

















 
 
 












 
 
 
 