dwr推送spring实现

概述

最近项目需要一个主动推送的功能,果断百度、google发现网上主要有两种实现方式:

一种是使用HTML5的webSockect,这个需要Tomcat7以上才支持,而且需要客户端的IE浏览器都支持HTML5。所以被我们果断的放弃的了。

另一种是使用dwr实现,dwr是一个JS与服务端Java类交互的Ajax框架。可以做到后台调用Java类方法的同时前台JS方法执行,前台JS方法执行的同时后台Java类的对应方法被执行。

最后使用了DWR来实现后台向前台的数据推送。

基本实现思路:

既然涉及到推送,那么我们肯定要明确推送的发起点以及推送的目标点(每个用户都要有明确ID),并且保证当用户在不同页面跳转时,保证数据都能够推送到前台(会话状态)。在我们的系统中,主要发起点是用户A在前台点击某些操作触发的,推送的目标点是用户B。

Dwr推送的基本思想是将一个后台的Java类映射为一个前台的同名JS类,同时通过JS维持一个长连接,与每个页面产生一个scriptsession,该session保证了长连接的同时也保证了每个链接会话的不同状态。当前台调用java对应的JS类中的某个方法时,dwr调用后台的java类中的同名方法,后台java类的某个方法被调用的时候,前台的对应JS对象的方法也会被调用,也就实现了向前台推送

实现方式:

1,引入DWR包

2,在web.xml文件中配置:

<listener>
        <listener-class>
		org.directwebremoting.servlet.DwrListener
	</listener-class>
</listener>
<servlet>
	<servlet-name>dwr-invoker</servlet-name>
    	<servlet-class>
		org.directwebremoting.servlet.DwrServlet
	</servlet-class>
        <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>
        <init-param>
          	<param-name>classes</param-name>
         	<param-value>java.lang.Object</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>3000</param-value>
        </init-param>
        <init-param>
            	<param-name>logLevel</param-name>
            	<param-value>WARN</param-value>
        </init-param>
        <init-param>
            	<param-name>debug</param-name>
            	<param-value>true</param-value>
        </init-param>
</servlet>
<servlet-mapping>
	<servlet-name>dwr-invoker</servlet-name>
    	<url-pattern>/dwr/*</url-pattern>
</servlet-mapping>
以上是在web.xml中的配置,具体配置信息的含义都可以在 http://directwebremoting.org/dwr/documentation/server/configuration/servlet/index.html页面中查看。
3,添加dwr.xml文件
该文件默认存放路径在WEB-INF路径下。
<?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">
 <!-- 指明暴露给前台JS的后台JAVA类,即将后台的JAVA类转换为前台的JS类,并且JAVA类中的public方法,
      都会在JS类中生成同名的JS函数 -->
<dwr>
  <allow>
    <!-- creator是java类的创建者,有spring(dwr与spring集成的时候使用)、new(单独使用)等
         javascript指创建的JS类的名称即生成的JS文件的名称,这里是MessagePush.js -->
  	<create creator="spring" javascript="MessagePush">
  		<!-- name值可以使class,配合creator=new使用.也可以是beanName,配合creator=spring使用
  		value的值根据不同的情况值不同,当name值为new,那么value的值就是目标Java类的全类名;
  		     当name的值为beanName时,value的值就是spring实例化出的bean的id -->
  		<param name="beanName" value="pushUtil"></param>
  	</create>
    <!-- creator是java类的创建者,有spring(dwr与spring集成的时候使用),javascript指创建的
         JS类的名称即生成的JS文件的名称,这里是aclService.js -->
  	<create creator="spring" javascript="aclService">
  		<param name="beanName" value="aclManageService"></param>
  	</create>
  	<!-- 
  	<create creator="new" javascript="MessagePush">
  		<param name="class" value="com.changan.test.DWRTest"></param>
  	</create>
  	 -->
  </allow>
</dwr>
4,在spring中配置:
<bean id="pushUtil" class="com.changan.common.pushUtils.PushUtil"></bean>
5,在JS中引入如下JS文件,这些JS文件不是实际存在的,而是通过DWR的servlet根据dwr.xml文件配置自动生成的。
<script type="text/javascript" src="<%=basePath%>dwr/engine.js"></script>
<script type="text/javascript" src="<%=basePath%>dwr/util.js"></script>
<script type="text/javascript" src="<%=basePath%>dwr/interface/MessagePush.js"></script>
6,在前台页面调用后台java类的方法:
这里的这个方法是在onload方法中调用,以保证页面加载成功后就创建一个ScriptSession对象,来保持长连接通话

function onPageLoad(){
	var userId = '${users.userId}';//这个userId是用来区分ScriptSession的
      	//dwr创建的JS对象MessagePush,对应dwr.xml中的定义,这里调用了后台方法 
	MessagePush.onPageLoad(userId);
}
7,定义被Java后台类调用的前台JS方法

function showMessage(msg){
	alert(msg);
}
8,后台Java类:

  • PushUtil类,暴露、转化为JS对象的Java类
/**
 * 
* @ClassName: PushUtil 
* @Description: 推送的主要实现类,由Spring实例化,同时dwr.xml文件中配置.
* @author lixiaodai
* @date 2013-11-2 上午9:50:26 
*
 */
public class PushUtil {
	
	/**
	 * 
	* @Title: onPageLoad 
	* @Description: 前台页面创建的onload事件会调用这个方法的JS方法,其实和调用这个方法一样 
	* 				该方法会在每次调用时创建一个脚本会话
	* @param userId 不同session的回话标识 
	* @return void    返回类型 
	* @throws
	 */
	public void onPageLoad(String userId) {
		//ScriptSession,DWR中提供的脚本会话对象,这个会话是储存在本地线程中的
        ScriptSession scriptSession = WebContextFactory.get().getScriptSession();
        //给每个脚本会话赋值一个属性,一般作为脚本会话的区别属性
        scriptSession.setAttribute("userId", userId);
        //初始化信息
        initInfo();
	}

	//初始化方法
	private void initInfo() {
		//得到当前服务端的dwr容器
		Container container = ServerContextFactory.get().getContainer();
		//从dwr容器中得到脚本会话管理类
		ScriptSessionManager manager = container.getBean(ScriptSessionManager.class);
		//从脚本会话管理类中得到目前所有的脚本会话对象
		Collection<ScriptSession> sessions = manager.getAllScriptSessions();
		//得到当前访问用户的HttpSession
		HttpSession httpSession = WebContextFactory.get().getSession();
		//判断当前Http会话中是否已经有scriptSessionId属性
		//如果有,则说明该HttpSession已经绑定了一个ScriptSession对象
		//如果没有,则说明该HttpSession还没有绑定ScriptSession
		if(httpSession.getAttribute("scriptSessionId")!=null){
			//得到当前HttpSession中存放的scriptSessionId属性
			int id = (Integer)httpSession.getAttribute("scriptSessionId");
			//遍历所有的ScirptSession对象,尝试将所有ScirptSession的id不是HttpSession中存放的scriptSessionId
			//的ScriptSession对象废止,注意:这里是废止不是立刻删除
			for(ScriptSession session:sessions){
				if(session.hashCode()!=id){
					session.invalidate();
				}
			}
		}
//		System.out.println("after invalidate sessionId:"+httpSession.getId()+",scriptSessionCount:"+manager.getScriptSessionsByHttpSessionId(httpSession.getId()).size());
		//得到会话监听对象
		ScriptSessionListener listener = PushListener.getInstance();
		//将监听对象添加到ScriptSessionManager管理类上
		manager.addScriptSessionListener(listener);
	}

	//这个方法用来推送,也就是当调用这个方法的时候,前台的JS对应函数就会被触发
    public static void sendMessageAuto(String userid,String message) {
    	//由于我们的推送是有目标的,所以需要目标ID以及要推送信息
        Browser.withAllSessionsFiltered(new PushFilter(userid),new PushRunable(message));
    }
    
}

  • PushListener类
/**
 * 
* @ClassName: PushListener 
* @Description: 会话监听器类,是一个单例类,这个类主要来当监听到ScriptSession创建,
* 				那么就分别在新创建的ScriptSession和HttpSession两个不同级别的会话中
* 				互相绑定对方的唯一标识
* @author lixiaodai
* @date 2013-11-7 上午9:56:57 
*
 */
public class PushListener implements ScriptSessionListener{

	private static PushListener listener;
	
	private PushListener() {

	}
	
	public static synchronized PushListener getInstance(){
		if(listener==null){
			listener = new PushListener();
		}
		return listener;
	}
	
	/**
	 * 会话/长连接创建时调用的方法
	 */
	public void sessionCreated(ScriptSessionEvent ev) {
		//得到当前的HttpSession类
		HttpSession session = WebContextFactory.get().getSession();
		//当前登录用户的用户ID
		String userId = ((Users) session.getAttribute("users")).getUserId() + "";
		//向新创建的ScriptSession中添加属性userId,来标识该ScriptSession对应的用户
		ev.getSession().setAttribute("userId", userId);
		//向HttpSession中设置新生成的ScriptSession对象的ID
		session.setAttribute("scriptSessionId", ev.getSession().hashCode());
	}
	
	/**
	 * 会话(长连接)关闭时调用的方法
	 */
	public void sessionDestroyed(ScriptSessionEvent ev) {
		//尝试废止该ScriptSession对象
		ev.getSession().invalidate();
	}

}

  • PushFilter类
/**
 * 
* @ClassName: PushFilter 
* @Description: 这个类用来过滤不同的SessionScript,保证我们要推送的数据能够准确推送到目标的
* 				ScriptSession中
* @author lixiaodai
* @date 2014-3-21 上午10:31:05 
*
 */
public class PushFilter implements ScriptSessionFilter {

	private static PushFilter filter;

	private String userId;
	
	public PushFilter() {

	}
	
	public PushFilter(String id){
		this.userId = id;
	}
	
	/**
	 * 主要的过滤方法,根据我们的条件来过滤推送到哪个ScriptSession中
	 */
	@Override
	public boolean match(ScriptSession session) {
		//根据脚本中的userId属性来判断是否是要推送的目标脚本会话
        if (session.getAttribute("userId") == null){
            return false;
        }else{
            return (session.getAttribute("userId")).equals(userId);
        }
	}

}

  • PushRunnable
    /**
     * 
    * @ClassName: PushRunable 
    * @Description: 用来实际执行推送的类,这个类是一个线程,同时要设定目标推送的方法以及要推送的信息
    * @author lixiaodai
    * @date 2014-3-21 上午10:47:19 
    *
     */
    public class PushRunable implements Runnable {
    	
    	private String message;
    	
        private ScriptBuffer script = new ScriptBuffer();
        
        public PushRunable(){
        	
        }
        
        public PushRunable(String msg){
        	this.message = msg;
        }
        
      
        /**
         * 新启的线程,执行的业务
         */
        @Override
        public void run() {
        	//要推送到的前台目标的JS方法以及该方法的参数
            script.appendCall("showMessage", message);
            //这里得到的ScriptSession的集合是通过PushFilter过滤过的
            Collection<ScriptSession> sessions = Browser.getTargetSessions();
            for (ScriptSession scriptSession : sessions) {
    //        	System.out.println(scriptSession.getAttribute("userId"));
            	scriptSession.addScript(script);
            }
        }
    }

以上就是简单的推送实现,参照了 http://www.blogjava.net/stevenjohn/archive/2012/07/07/382447.html
并做了一点改进,敬请拍砖,3Q。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值