学习需要有强大的内心作为后盾,耐着性子,一步一个脚印的向前摸索。
选择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);
忽略第一没建立数据通道的错误提示。当正确建立数据通道后,平台不会报此错误。
}