一、DWR简介
DWR(Direct Web Remoting)是一个用于改善web页面与Java类交互的远程服务器端Ajax开源框架。
从最简单的角度来说,DWR是一个引擎,可以把服务器端Java对象的方法公开给JavaScript 代码
一、DWR原理
1.DWR采用的是长连接机制。
1、长连接技术通过客户端发出请求获取服务器端数据的方式通常称为"拉"技术,很形象说明客户端在拉取服务器端数据,而有时候需要服务器端主动向客户端"推"数据,比如监测聊天上线人数主动向上线发送消息,后台数据库发生变化是主动更新所有客户端展示。
2、Reverse Ajax实现服务器推技术DWR2.x的推技术也叫DWR Reverse Ajax(逆向Ajax)主要是在BS架构中,从服务器端向多个浏览器主动推数据的一种技术。DWR的逆向Ajax主要包括两种模式:主动模式和被动模式。其中主动模式包括polling和comet两种,被动模式只有piggyback这一种:
(1)piggyback方式,是默认的方式。如果后台有什么内容需要推送到前台,是要等到那个页面进行下一次ajax请求的时候,将需要推送的内容附加在该次请求之后,传回到页面。只有等到下次请求页面主动发起了,中间的变化内容才传递回页面。
(2)comet方式当服务端建立和浏览器的连接,将页面内容发送到浏览器之后,对应的连接并不关闭,只是暂时挂起。如果后面有什么新的内容需要推送到客户端的时候直接通过前面挂起的连接再次传送数据。服务器所能提供的连接数目是一定的,在大量的挂起的连接没有关闭的情况下,可能造成新的连接请求不能接入,从而影响到服务质量。
(3)polling方式由浏览器定时向服务端发送ajax请求,询问后台是否有什么内容需要推送,有的话就会由服务端返回推送内容。这种方式和我们直接在页面通过定时器发送ajax请求,然后查询后台是否有变化内容的实现是类似的。
2.ScriptSession的生命周期
客户端每次请求(刷新)都会生成一个新的ScriptSession,当客户端请求时或者客户端退出时将会销毁ScriptSession。
通过ScriptSession我们可以得到客户端(浏览器)的脚本执行权。即我们可以直接调用浏览器的js代码。
.应用实例
平台使用DWR实现报警消息上报,推送指定用户。
第一步:引入dwr的jar包
<dependency>
<groupId>org.directwebremoting</groupId>
<artifactId>dwr</artifactId>
<version>3.0.2-RELEASE</version>
</dependency>
第二步: web.xml中配置dwr
<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>pollAndCometEnabled</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>crossDomainSessionSecurity</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>allowScriptTagRemoting</param-name>
<param-value>true</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>
第三步:在web.xml的同级目录下新建dwr.xml文件
*注意:value="com.dahua.commonresource.dwr.MessagePush"根据项目中实际路径进行配置,就相当于java类中的一个映射,在js中使用MessagePush.java中实现的方法。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE dwr PUBLIC "-//GetAhead Limited//DTD Direct Web Remoting 3.0//EN" "http://getahead.org/dwr/dwr30.dtd">
<dwr>
<allow>
<create creator="new" javascript="MessagePush">
<param name="class" value="com.dahua.commonresource.dwr.MessagePush"/>
</create>
</allow>
</dwr>
第四步:js代码
//前端引入必要的js:
<scripttype='text/javascript'src='dwr/engine.js'></script>
<scripttype='text/javascript'src='dwr/util.js'></script>
<scripttype="text/javascript"src="dwr/interface/MessagePush.js">
//初始化需加载此块代码:
$(document).ready(
function () {
try {
MessagePush.onPageLoad(userName); //调用后台的onPageLoad方法,传入区别本页面的唯一标识符userName
dwr.engine.setActiveReverseAjax(true); // 开启逆向Ajax
dwr.engine.setNotifyServerOnPageUnload(true,true);//这个方法设置DWR在页面关闭时异步通知后台该ScriptSession无效
dwr.engine.setErrorHandler(function(){});
} catch (e) {
console.log(e);
}
}
);
//前台接受消息的方法,由后台调用
*注意:showMessage要和java后台调用的方法一致
window.showMessage = function (autoMessage) {
switch (autoMessage.chnName) {
case 'channel.alarm.notice':
console.log("已接受到实时报警消息");
observer.trigger("commonresource/actualAlarm", [autoMessage.msg[0]]);
break;
case 'channel.message.notice':
console.log("已接受到实时通知消息");
observer.trigger("commonresource/actualTimeNotice", [autoMessage.msg[0]]);
break;
case 'channel.alarm.handleNotice':
console.log("报警消息已处理");
var msg = JSON.parse(autoMessage.msg);
observer.trigger("commonresource/handleMsg", [msg]);
observer.trigger("commonresource/historyHandleMsg", [msg]);
var handStr = '';
switch (msg.handleStat) {
case 2:
handStr = '处理中';
break;
case 3:
handStr = '误报';
break;
case 4:
handStr = '忽略';
break;
default:
handStr = '已处理';
}
var noticeStr = msg.channelName + '-' + msg.alarmTypeStr + '-' + handStr;
window.ocxFun.toOcxMsg('addNotification', noticeStr);
break;
case 'channel.message.handleNotice':
console.log("通知消息已处理");
observer.trigger("commonresource/noticeMsg", [autoMessage.msg]);
observer.trigger("commonresource/historyNoticeMsg", [autoMessage.msg]);
break;
//滞留量达到阈值时上报
case 'channel.alarm.peopleFlow':
observer.trigger("acceptFlowData", [autoMessage.msg]);
break;
//处理后的反馈
case 'channel.alarm.handle.peopleFlow':
observer.trigger("dealFlowData", [autoMessage.msg]);
observer.trigger("refresh/holdUpMsg", [autoMessage.msg]);
break;
default:
console.log('非报警!');
}
};
第四步:java后台代码
DwrScriptSessionManagerUtil.java
MessagePush.java(和前面dwr.xml配置的MessagePush一致)
DwrUtil.java(推送工具类)
DwrScriptSessionManagerUtil.java
public class DwrScriptSessionManagerUtil extends DwrServlet{
private static final long serialVersionUID = -7504612622407420071L;
// 维护一个Map key为session的Id, value为ScriptSession对象(供管理scriptSession使用)
public static final Map<String, ScriptSession> scriptSessionMap = new HashMap<String, ScriptSession>();
public void init() throws ServletException {
Container container = ServerContextFactory.get().getContainer();
ScriptSessionManager manager = container.getBean(ScriptSessionManager.class);
ScriptSessionListener listener = new ScriptSessionListener() {
/**
* 添加客户端session
*
*/
@Override
public void sessionCreated(ScriptSessionEvent ev) {
HttpSession session = WebContextFactory.get().getSession();
String loginName =(String)session.getAttribute(Constants.SESSION_USER_KEY);
ScriptSession scriptSession = ev.getSession();
scriptSession.setAttribute("loginName", loginName);
scriptSessionMap.put(scriptSession.getId(), scriptSession);
}
/**
* 由页面关闭,或时间超时引起的 连接关闭
*/
@Override
public void sessionDestroyed(ScriptSessionEvent ev) {
ScriptSession scriptSession = ev.getSession();
scriptSessionMap.remove(scriptSession.getId()); // 移除scriptSession
}
};
manager.addScriptSessionListener(listener);
}
}
MessagePush.java
public class MessagePush {
/***
* 咨询者 页面加载时处理函数
* @param request
*/
public void onPageLoad(HttpServletRequest request) {
ScriptSession scriptSession = WebContextFactory.get().getScriptSession();
scriptSession.setAttribute("scriptSessionId", scriptSession.getId());
DwrScriptSessionManagerUtil connectManagerCenter = new DwrScriptSessionManagerUtil();
try {
connectManagerCenter.init();
} catch (ServletException e) {
e.printStackTrace();
}
}
/***
* 推送消息
*
*
* @param scriptSessionId
* 根据scriptSessionId可以获得该页面对象
* @param javaScriptFunction
* 推送页面的调用方法
* @param request
* @param pushMsg
* 推送的语句 List
*/
public void pushMsgMethod(final String scriptSessionId, final String javaScriptFunction,
HttpServletRequest request, Object... pushmsg) {
final Object[] pushMsg = pushmsg;
/**
* Browser.withAllSessionsFiltered(filter,runnble)
* 过滤器,通过自定义规则在内存中的过滤ScriptSession
*/
Browser.withAllSessionsFiltered(new ScriptSessionFilter() {
public boolean match(ScriptSession session) {
// 自定义过滤规则
if (session.getAttribute("scriptSessionId") == null)
return false;
else
return (session.getAttribute("scriptSessionId")).equals(scriptSessionId);
}
}, new Runnable() {
private ScriptBuffer script = new ScriptBuffer();
public void run() {
script.appendCall(javaScriptFunction, pushMsg);
Collection<ScriptSession> sessions = Browser.getTargetSessions();
for (ScriptSession scriptSession : sessions) {
scriptSession.addScript(script);
}
}
});
}
}
DwrUtil.java
public class DwrUtil {
/** 推送报警通知 */
public final static String CHANNEL_ALARM_NOTICE = "channel.alarm.notice";
/** 推送消息通知 */
public final static String CHANNEL_MESSAGE_NOTICE = "channel.message.notice";
/** 处理报警通知 */
public final static String CHANNEL_ALARM_HANDLENOTICE = "channel.alarm.handleNotice";
/** 处理消息通知 */
public final static String CHANNEL_MESSAGE_HANDLENOTICE = "channel.message.handleNotice";
/** 推送客流预警 */
public final static String CHANNEL_ALARM_PEOPLE_FLOW = "channel.alarm.peopleFlow";
/** 处理客流预警 */
public final static String CHANNEL_ALARM_HANDLE_PEOPLE_FLOW = "channel.alarm.handle.peopleFlow";
/**
* 发送消息给单个用户
* @param channelName
* @param loginName
* @param message
*/
public static void sendMessageAutoToOne(String channelName,String loginName, Object message){
final String userName = loginName;
final Object autoMessage = message;
final String chnName = channelName;
Browser.withAllSessionsFiltered(new ScriptSessionFilter() {
public boolean match(ScriptSession session){
if (session.getAttribute("loginName") == null)
return false;
else
return (session.getAttribute("loginName")).equals(userName);
}
}, new Runnable(){
private ScriptBuffer script = new ScriptBuffer();
public void run(){
Map<String, Object> map = new HashMap<String, Object>();
map.put("chnName", chnName);
map.put("msg", autoMessage);
// showMessage和js中的showMessage = function (autoMessage)一致
script.appendCall("showMessage", JSONObject.parseObject(JSON.toJSONString(map)));
Collection<ScriptSession> sessions = Browser.getTargetSessions();
for (ScriptSession scriptSession : sessions){
scriptSession.addScript(script);
}
}
});
}
/**
* 发送消息给多个用户
* @param channelName
* @param loginNames
* @param message
*/
public static void sendMessageToAll(String channelName,List<String> loginNames, Object message){
if (!CollectionUtils.isEmpty(loginNames)) {
for (String loginName : loginNames) {
sendMessageAutoToOne(channelName,loginName,message);
}
}
}
}
第五步:调用推送方法
DwrUtil.sendMessageAutoToOne(DwrUtil.CHANNEL_ALARM_NOTICE,alarmReceiverInfo.getLoginName(), getAlarmInfoNoticeMsg(map));