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。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章