概述
最近项目需要一个主动推送的功能,果断百度、google发现网上主要有两种实现方式:
一种是使用HTML5的webSockect,这个需要Tomcat7以上才支持,而且需要客户端的IE浏览器都支持HTML5。所以被我们果断的放弃的了。
另一种是使用dwr实现,dwr是一个JS与服务端Java类交互的Ajax框架。可以做到后台调用Java类方法的同时前台JS方法执行,前台JS方法执行的同时后台Java类的对应方法被执行。
基本实现思路:
既然涉及到推送,那么我们肯定要明确推送的发起点以及推送的目标点(每个用户都要有明确ID),并且保证当用户在不同页面跳转时,保证数据都能够推送到前台(会话状态)。在我们的系统中,主要发起点是用户A在前台点击某些操作触发的,推送的目标点是用户B。
Dwr推送的基本思想是将一个后台的Java类映射为一个前台的同名JS类,同时通过JS维持一个长连接,与每个页面产生一个scriptsession,该session保证了长连接的同时也保证了每个链接会话的不同状态。当前台调用java对应的JS类中的某个方法时,dwr调用后台的java类中的同名方法,后台java类的某个方法被调用的时候,前台的对应JS对象的方法也会被调用,也就实现了向前台推送。
实现方式:
1,引入DWR包
2,在web.xml文件中配置:
以上是在web.xml中的配置,具体配置信息的含义都可以在 http://directwebremoting.org/dwr/documentation/server/configuration/servlet/index.html页面中查看。
- <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-INF路径下。
4,在spring中配置:
- <?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>
5,在JS中引入如下JS文件,这些JS文件不是实际存在的,而是通过DWR的servlet根据dwr.xml文件配置自动生成的。
- <bean id="pushUtil" class="com.changan.common.pushUtils.PushUtil"></bean>
6,在前台页面调用后台java类的方法:
- <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>
7,定义被Java后台类调用的前台JS方法
8,后台Java类:
- function showMessage(msg){
- alert(msg);
- }
- 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);
- }
- }
- }