学习需要有强大的内心作为后盾,耐着性子,一步一个脚印的向前摸索。
选择DWR的原因:
前端发起录制视频任务传送到JNI录制接口,通过c++ so进行转换,转换后调用 message handle 通知客户端。
提出问题:
转换时间不确定,转换结果不确定,不能及时反馈给客户,客户如果建立个长连接,会阻塞客户的其他业务的处理。
整体设计:
底层服务利用spring3 监听器进行so消息转换结果的监听,如果so有消息返回,则利用dwr reverse ajax 把消息推送到指定用户。
详细设计:
step1 利用spring监听器,如果so消息发布,则publish到spring容器中。
代码如下:
@Service("messageHandler" )
public
class
MessageHandlerImp
implements
IMessageHandler,
ApplicationContextAware {
final
static
Logger
log
= Logger.getLogger(MessageHandlerImp.
class
);
@Autowired
private
IMessageService
messageService
;
private
final
ArrayList<IMessage>
messages
=
new
ArrayList<IMessage>();
@Autowired
private
MpsMessageController
mpsMessageController
;
private
ApplicationContext
context
;
public
void
handler(IMessage msg) {
// mpsMessageController.convertResult();
//messageService.save(msg);
context
.publishEvent(
new
InfoMessage(msg));
}
@Override
public
void
setApplicationContext(ApplicationContext context)
throws
BeansException {
this
.
context
= context;
}
}
step2:DWR ajax resvers是通过DWR ScripteSessionLisener进行scriptSession的创建。推送原理是
,每一个用户请求会建立一个
scriptSession的数据通道,Dwr reverse 技术通过scriptsesession 发送到web端,再利用
scriptSession
与HttpSession相结合区分每一个用户的请求。
这样解决了避免多个用户都收到转换结果,从而达到,who 发起,who 接受的单点数据通信。
/**
*
* 扩展ScriptSessionManager.
*
*
@author
maxc
*
@version
2012
-
2
-
15
*/
public
class
InitScriptManager
extends
DefaultScriptSessionManager {
final
static
Logger
log
= Logger.getLogger(InitScriptManager.
class
);
public
InitScriptManager() {
addScriptSessionListener(
new
ScriptSessionListener() {
public
void
sessionCreated(ScriptSessionEvent event) {
ScriptSession scriptSession = event.getSession();
// 获取新创建的SS
HttpSession httpSession = WebContextFactory.get().getSession();
// 获取构造SS的用户的HttpSession
String userId = (String) httpSession.getAttribute(
"userId"
);
if
(userId ==
null
) {
scriptSession.invalidate();
httpSession.invalidate();
return
;
}
log
.info(
"创建 ==============="
+ scriptSession.getId());
scriptSession.setAttribute(
"userId"
, userId);
// 此处将userId和scriptSession绑定
}
public
void
sessionDestroyed(ScriptSessionEvent event) {
log
.info(
"销毁 ==============="
+ event.getSession().getId());
}
});
/**
*
@author
xiaochao.ma
*
<p>
不用spring3注入是因为 一旦交给spring3容器处理,就会重新生成一个 mannager,反转业务就不能使用唯一的监听器监听scriptSession的创建
<p>
*
* 注入DWRReservers 使得反转业务可以应用scriptManager 这样就可以获得当前应用的scriptSession
*/
DWRReservers.
manager
=
this
;
}
}
step3:配置dwr
<?xml version="1.0" encoding= "UTF-8"?>
<
web-app
version
=
"2.5"
xmlns
=
"http://java.sun.com/xml/ns/javaee"
xmlns:xsi
=
"http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation
=
"http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
>
<!-- 默认的spring配置文件是在WEB-INF下的applicationContext.xml Spring 容器启动监听器 -->
<
listener
>
<
listener-class
>
org.springframework.web.context.ContextLoaderListener
</
listener-class
>
</
listener
>
<
context-param
>
<
param-name
>
springconfig
</
param-name
>
<
param-value
>
/WEB-INF/applicationContext.xml,/WEB-INF/spring3-servlet.xml
</
param-value
>
</
context-param
>
<
filter
>
<
filter-name
>
Set Character Encoding
</
filter-name
>
<
filter-class
>
org.springframework.web.filter.CharacterEncodingFilter
</
filter-class
>
<
init-param
>
<
param-name
>
encoding
</
param-name
>
<
param-value
>
UTF-8
</
param-value
>
</
init-param
>
<
init-param
>
<
param-name
>
forceEncoding
</
param-name
>
<
param-value
>
true
</
param-value
>
<!-- 强制进行转码 -->
</
init-param
>
</
filter
>
<
filter-mapping
>
<
filter-name
>
Set Character Encoding
</
filter-name
>
<
url-pattern
>
/*
</
url-pattern
>
</
filter-mapping
>
<!-- 默认所对应的配置文件是WEB-INF下的{ servlet-name}-servlet.xml,这里便是:spring3-servlet.xml -->
<
servlet
>
<
servlet-name
>
spring3
</
servlet-name
>
<
servlet-class
>
org.springframework.web.servlet.DispatcherServlet
</
servlet-class
>
<
init-param
>
<
param-name
>
contextConfigLocation
</
param-name
>
<
param-value
>
/WEB-INF/applicationContext.xml,/WEB-INF/spring3-servlet.xml
</
param-value
>
</
init-param
>
<
load-on-startup
>
1
</
load-on-startup
>
</
servlet
>
<!--dwr servlet -->
<
servlet-mapping
>
<
servlet-name
>
spring3
</
servlet-name
>
<!-- 这里可以用 / 但不能用 /* ,拦截了所有请求会导致静态资源无法访问,所以要在spring3-servlet.xml中配置mvc:resources -->
<
url-pattern
>
/
</
url-pattern
>
</
servlet-mapping
>
<!-- <servlet>
<servlet-name>initScript</servlet-name>
<servlet-class>com.gnetis.mpagent.controller.InitScriptSession</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet> -->
<
servlet
>
<
servlet-name
>
dwr-invoker
</
servlet-name
>
<
servlet-class
>
org.directwebremoting.servlet.DwrServlet
</
servlet-class
>
<!-- <servlet-class>org.directwebremoting.spring.DwrSpringServlet</servlet-class> -->
<
init-param
>
<
param-name
>
debug
</
param-name
>
<
param-value
>
false
</
param-value
>
</
init-param
>
<
init-param
>
<
param-name
>
crossDomainSessionSecurity
</
param-name
>
<
param-value
>
false
</
param-value
>
</
init-param
>
<
init-param
>
<
param-name
>
pollAndCometEnabled
</
param-name
>
<
param-value
>
true
</
param-value
>
</
init-param
>
<
init-param
>
<
param-name
>
allowScriptTagRemoting
</
param-name
>
<
param-value
>
true
</
param-value
>
</
init-param
>
<
init-param
>
<!--此处非常关键:是建立当系统启动时,建立dwr监听-->
<
param-name
>
org.directwebremoting.extend.ScriptSessionManager
</
param-name
>
<
param-value
>
com.gnetis.mpagent.controller.InitScriptManager
</
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
>
<
welcome-file-list
>
<
welcome-file
>
index.jsp
</
welcome-file
>
</
welcome-file-list
>
</
web-app
>
step3:Spring3监听到消息后,交由DWR ajax reverse技术进行推送。
代码如下:
package com.gnetis.mpagent.service.imp;
import java.util.Collection;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import javax.servlet.ServletContext;
import org.apache.log4j.Logger;
import org.directwebremoting.ScriptBuffer;
import org.directwebremoting.ScriptSession;
import org.directwebremoting.annotations.RemoteMethod;
import org.directwebremoting.guice.ScriptSessionScoped;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.web.context.ServletContextAware;
import com.gnetis.mpagent.controller.InitScriptManager;
import com.gnetis.mpagent.service.MpsConvertTaskResultService;
import com.gnetis.tang.agent.message.IMessage;
import com.gnetis.tang.agent.message.InfoMessage;
public class DWRReservers implements ApplicationListener, ServletContextAware {
@Autowired
private MpsConvertTaskResultService mpsConvertTaskResultService ;
final static Logger log = org.apache.log4j.Logger
. getLogger(DWRReservers.class);
static ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(
1);
public static InitScriptManager manager;
private ServletContext servletContext;
@RemoteMethod
@ScriptSessionScoped
public void reverseMessage(final IMessage message) {
log.info("DWRReservers reverseMessage :: active[" + active
+ "] message[" + message + "]" );
if (message == null) {
active = true ;
} else {
log.info(" reverseMessage :: message====>" + message);
Collection<ScriptSession> ss = manager.getAllScriptSessions();
for (ScriptSession scriptsession : ss) {
Object userId = scriptsession.getAttribute("userId" );
log.info("ScriptSessionId [" + scriptsession.getId()
+ "]userId[" + scriptsession.getAttribute("userId" )
+ "]");
if (userId != null
&& (userId.toString()).equals(message.getSessionId())) {
scriptsession.addScript( new ScriptBuffer()
.appendScript( "receiveMessages").appendScript("(" )
.appendData(message).appendScript( ");"));// 找到符合要求的,推送事件
}
}
active = false ;
}
}
public DWRReservers() {
}
public synchronized void isReceive(final int confId, final String sessionId) {
log.info(" isRecive :: jsp invork function paremeter are [" + confId
+ "]");
mpsConvertTaskResultService.convert(confId, sessionId);
}
/**
* is receive
*/
protected transient boolean active = true;
@Override
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
@Override
public void onApplicationEvent(ApplicationEvent event) {
// TODO Auto-generated method stub
if (event instanceof InfoMessage) {
IMessage message = (IMessage) event.getSource();
log.info("onApplicationEvent :: message [" + message.getMsgID()
+ " " + message.getSessionId() + " ]" );
reverseMessage(message);
}
}
}
step4:jsp发起的请求通道。
<%@ page language= "java" contentType ="text/html; charset=utf-8"
pageEncoding="utf-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@page import="org.directwebremoting.ScriptSession" %>
<%@page import="org.directwebremoting.Container" %>
<%@page import="org.directwebremoting.ServerContextFactory" %>
<%@page import="org.directwebremoting.extend.ScriptSessionManager" %>
<%@page import="org.directwebremoting.event.ScriptSessionListener" %>
<%
String path = request.getContextPath();
String basePath = request.getScheme() + "://"
+ request.getServerName() + ":" + request.getServerPort()
+ path + "/";
%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" >
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
<title> Insert title here</title >
<style type="text/css" >
table {
width: 600px ;
margin: 0px auto ;
font: Georgia 11px ;
color: #333333 ;
text-align: center ;
border-collapse: collapse ;
}
tr {
border: 1px solid black ;
margin: 0px ;
cellspacing: "1" cellpadding :"0"
}
td {
border: 1px solid blue ;
margin: 0px ;
cellspacing: "1" cellpadding :"0"
}
</style>
<script type='text/javascript' src='/mp/dwr/engine.js'></ script>
<script type='text/javascript' src='/mp/dwr/interface/dwrService.js' ></script>
<script type='text/javascript' src='/mp/dwr/util.js'></ script>
<script type="text/javascript" >
dwr.engine._errorHandler=function(message ,ex){
dwr.engine._debug( "Error: " + ex.name + ", " + ex.message, true);
if (message == null || message == "") alert( "A server error has occurred.");
// Ignore NS_ERROR_NOT_AVAILABLE if Mozilla is being narky
else if (message.indexOf("0x80040111") != -1) dwr.engine._debug(message);
else ;//alert(message);
}
function dwrconvert(confId) {
//alert(confId);
dwrService.isReceive(confId, '<%=session.getAttribute("userId" ).toString()%>');
}
function objectEval(text) {
// eval() breaks when we use it to get an object using the { a:42, b:'x' }
// syntax because it thinks that { and } surround a block and not an object
// So we wrap it in an array and extract the first element to get around
// this.
// This code is only needed for interpreting the parameter input fields,
// so you can ignore this for normal use.
// The regex = [start of line][whitespace]{[stuff]}[whitespace][end of line]
text = text.replace(/\n/g, ' ');
text = text.replace(/\r/g, ' ');
if (text.match(/^\s*\{.*\}\s*$/)) {
text = '[' + text + '][0]' ;
}
return eval(text);
}
function receiveMessages(data) {
if (data != null && typeof data == 'object') {
var jsMessage = objectEval(dwr.util.toDescriptiveString(data, 2));
var message = "" ;
var confId = jsMessage["confId" ];
var msgID = jsMessage["msgID" ];
message = "confId : [" + confId + "] , msgID : [" + msgID + "]";
var errorId = jsMessage["errorId" ];
var progressValue = jsMessage["progressValue" ];
var url = jsMessage["url" ];
if (errorId) {
message += ",errorId : [" + errorId + "]" ;
}
if (progressValue) {
message += ",progressValue : [" + progressValue + "]";
}
if (url) {
/* var href=document.createElement("a");
href.attributes("href",url); */
message += ",url : <a href='"+url+"'> " + url + "</a>";
}
document.getElementById( "d0").innerHTML = message;
//dwr.util.setValue('d0', message);
} else {
dwr.util.setValue( 'd0', dwr.util.toDescriptiveString(data, 1));
}
}
</script>
</head>
<body
onload="dwr.engine.setActiveReverseAjax(true);dwr.engine.setNotifyServerOnPageUnload(true);" >
<div id= "d0"></div >
<div>
<table>
<tr>
<td> ID</ td>
<td> 会议ID</td >
<td> 实例ID</td >
<td> 业务类型</td >
<td> 开始时间</td >
<td> 结束时间</td >
<td> 发布时间</td >
<td> site</td >
<td> modelId</td >
<td> convert</td >
</tr>
<c:forEach var="maps" items="${tasks } " >
<c:forEach var="o" items=" ${maps.mpsConvertTasks}" varStatus="sta" >
<tr>
<td> ${ o.id}</td >
<td> ${ o.confId}</td >
<td> ${ o.groupID}</td >
<td> ${ o.serviceType}</td >
<td> ${ o.startTime}</td >
<td> ${ o.endTime}</td >
<td> ${ o.addTime}</td >
<td> ${ o.site}</td >
<td> ${ o.modelId}</td >
<c:if test=" ${sta.count==1 }">
<td rowspan=" ${fn:length(maps.mpsConvertTasks) }">
<a href="#" onclick="dwrconvert('${ o.id}')">converr </a>
</td>
</c:if>
</tr>
</c:forEach>
</c:forEach>
</table>
</div>
</body>
</html>
总结: 如果用spring3的 DwrServelt配置反转mananger 报:
No bean named '__dwrConfiguration' is defined
原因不明,猜测是由于dwr版本问题造成的。DWR反转与spring3相结合的comet方式没有很好得解决方案,dwr在spring容器下不能获得context,本项目采用分离处理。不用 spring实现的dwrservlet。详细见web.xml配置。
DWR.xml 代码:
<?xml version="1.0" encoding= "UTF-8"?>
<!DOCTYPE dwr PUBLIC
"-//GetAhead Limited//DTD Direct Web Remoting 1.0//EN"
"http://www.getahead.ltd.uk/dwr/dwr10.dtd" >
<dwr>
<allow>
<create creator="spring" javascript= "dwrService">
<param name="beanName" value="dwrService" />
</create>
<convert converter="bean"
match="com.gnetis.tang.agent.message.MessageProgress" />
<convert converter="bean"
match="com.gnetis.tang.agent.message.MessageSuccess" />
<convert converter="bean"
match="com.gnetis.tang.agent.message.MessageFailure" />
</allow>
</dwr>
异常2:自己写servlet监听scriptSession的创建汇报
WebContextFactory. get()为NULL
异常,DWR context 必须在servlet容器中,自己写的servlet 没有发起请求时候,init是不能够获取到httpSession的。解决方案就是在DWR 自己的context中配置参数,实现defaultScriptSessionManager。详细见 web.xml。
异常3:dwr消息错误监听,当没有sessionScript建立时候,后台调用invalidSession方法会被dwr._debug接受并错误的弹出alert.
解决方案,重写dwr处理消息的机制,此项目中忽略了错误消息,若有需要可以写到后台日志中。
代码:
dwr.engine._errorHandler= function(message ,ex){
dwr.engine._debug( "Error: " + ex.name + ", " + ex.message, true );
if
(message ==
null
|| message ==
""
) alert(
"A server error has occurred."
);
// Ignore NS_ERROR_NOT_AVAILABLE if Mozilla is being narky
else
if
(message.indexOf(
"0x80040111"
) !=
-
1) dwr.engine._debug(message);
else
;
//alert(message); 忽略第一没建立数据通道的错误提示。当正确建立数据通道后,平台不会报此错误。
}