1. 程式人生 > >Spring+Stomp+ActiveMq實現websocket長連線

Spring+Stomp+ActiveMq實現websocket長連線

stomp.js+spring+sockjs+activemq實現websocket長連線,使用java配置。

pom.xml(只列出除了spring基本依賴意外的依賴,spring-version為4.3.3.RELEASE):

<dependency>
        <groupId>javax.websocket</groupId>
        <artifactId>javax.websocket-api</artifactId>
        <version>1.1</version>
        <scope>provided</scope> 
        <!-- 注意,scope必須為provided,否則runtime會衝突,如果使用tomcat 8,還需要將TOMCAT_HOME/lib下的javax.websocket-api.jar一併刪除 -->
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-websocket</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-messaging</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jms</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.apache.xbean</groupId>
      <artifactId>xbean-spring</artifactId>
      <version>3.16</version>
    </dependency>
    <dependency>
      <groupId>org.apache.activemq</groupId>
      <artifactId>activemq-core</artifactId>
      <version>5.7.0</version>
    </dependency>
    <dependency>
      <groupId>org.apache.activemq</groupId>
      <artifactId>activemq-pool</artifactId>
      <version>5.12.1</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-core</artifactId>
      <version>2.8.1</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.8.1</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-annotations</artifactId>
      <version>2.8.1</version>
    </dependency>
    <dependency>
        <groupId>io.projectreactor</groupId>
        <artifactId>reactor-net</artifactId>
        <version>2.0.7.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>io.netty</groupId>
        <artifactId>netty-all</artifactId>
        <version>4.0.33.Final</version>
    </dependency>
    <dependency>
        <groupId>io.projectreactor</groupId>
        <artifactId>reactor-core</artifactId>
        <version>2.0.8.RELEASE</version>
    </dependency>

 

 

 

StompConfig.java

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer; import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; import org.springframework.web.socket.config.annotation.StompEndpointRegistry; /** * @EnableWebSocketMessageBroker包含@EnableWebSocket *
@author TD * */ @Configuration @EnableWebSocketMessageBroker @PropertySource("classpath:activemq.properties") public class StompConfig extends AbstractWebSocketMessageBrokerConfigurer{ @Autowired private Environment env; /** * 註冊代理,暴露節點用於連線。 */ @Override public void registerStompEndpoints(StompEndpointRegistry registry) { // TODO Auto-generated method stub registry.addEndpoint("/stompEndPoint.do").withSockJS(); } /** * 修改訊息代理的配置,預設處理以/topic為字首的訊息 * setApplicationDestinationPrefixes:配置請求的根路徑,表示通過MessageMapping處理/td/*請求,不會發送到代理 * enableStompBrokerRelay:配置代理,匹配路徑的請求會進入代理:mq等 */ @Override public void configureMessageBroker(MessageBrokerRegistry registry) { /*基於記憶體實現的stomp代理,單機適用 registry.enableSimpleBroker("/queue","/topic"); */ //以/td為目的地的訊息使用MessageMapping控制器處理,不走代理 registry.setApplicationDestinationPrefixes("/td","/app"); /** * 設定單獨傳送到某個user需要新增的字首,使用者訂閱地址/user/topic/td1地址後會去掉/user,並加上使用者名稱(需要springsecurity支援)等唯一標識組成新的目的地傳送回去, * 對於這個url來說 加上字尾之後走代理。傳送時需要制定使用者名稱:convertAndSendToUser或者sendtouser註解. registry.setUserDestinationPrefix("/user") */ /*基於mq實現stomp代理,適用於叢集。 * 以/topic和/queue開頭的訊息會發送到stomp代理中:mq等。 * 每個mq適用的字首不一樣且有限制。activemq支援stomp的埠為61613
*/ registry.enableStompBrokerRelay("/topic","/queue") .setRelayHost(env.getProperty("mq.brokenHost")) .setRelayPort(Integer.parseInt(env.getProperty("mq.brokenPort"))) .setSystemLogin(env.getProperty("mq.username")) .setSystemPasscode(env.getProperty("mq.password")) .setClientLogin(env.getProperty("mq.username")) .setClientPasscode(env.getProperty("mq.password")); /* * systemLogin:設定代理所需的密碼 * client:設定客戶端連線代理所需的密碼,預設為guest */ } }

 

JSP頁面:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>stomp測試</title>
</head>
<body>
<button onclick="test()">stomp傳送(服務端返回資訊走訂閱2)</button>
<button onclick="test2()">stomp訂閱1(@subscribeMapping,不過代理)</button>
<button onclick="test3()">stomp訂閱2 :通過代理</button>
</body>
<script type="text/javascript" src="/spring15_socket/js/sockjs-0.3.4.min.js"></script>
<script type="text/javascript" src="/spring15_socket/js/stomp.js"></script>
<script type="text/javascript">
var url = 'http://localhost:8089/spring15_socket/stompEndPoint.do';
//建立sockjs連結
var sock = new SockJS(url);
//建立stomp客戶端
var stomp = Stomp.over(sock);
var msg = JSON.stringify({'name':'td','age':13});
stomp.connect({},function(frame){
	console.log('connecting...'+frame)
	stomp.send('/app/stomp1.do',{},msg);
})

function test(){
	stomp.send('/app/stomp1.do',{},msg);
}

function test2(){
	stomp.subscribe('/app/stomp2.do',function(msg){
		console.log("subscribemapping:"+JSON.parse(msg.body).content);
	})
	
}

function test3(){
	stomp.subscribe('/topic/hello',function(msg){
		console.log("topicHello:"+JSON.parse(msg.body).content);
	})
	
}

</script>
</html>

  

控制器:

package spring15_socket.controller;

import java.sql.SQLException;

import org.springframework.messaging.handler.annotation.MessageExceptionHandler;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.annotation.SubscribeMapping;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import spring15_socket.bean.User;

@Controller
public class TestController {
    
    
    @RequestMapping(value="/sockjs.do")
    public String test0(Model model){
        return "sockjs";
    }    
    
    @RequestMapping(value="/stompjs.do")
    public String test1(Model model){
        return "stomp";
    }    
    
    /**
     * 表示該方法處理客戶端發來的/td/stomp1.do或者/app/stomp1.do。
     * sendTo:重新指定傳送的位置,預設原路返回(url會加上/topic字首)
     * 需走代理
     */
    @MessageMapping("/stomp1.do")
    @SendTo("/topic/hello")
    public User handleStomp(User user) {
        System.out.println("stomp接收到客戶端的請求:"+user);
        user.setName("messagemapping返回user");
        return user;
        
    }
    
    /**
     * 用於處理messagemapping丟擲的異常,類比exceptionhandler
     * @return
     */
    @MessageExceptionHandler({Exception.class,SQLException.class})
    @SendTo("/topic/errorTopic")
    public User errorHandler(Throwable t) {
        System.out.println("異常統一處理");
        User user = new User();
        user.setName("異常統一處理:"+t.getMessage());;
        return user;
    }
    
    
    /**
     * 觸發方式和messagemapping一致。
     * sendTo:重新指定傳送的位置,預設原路返回(url會加上/topic字首)
     * 使用subscribemapping不走代理
     */
    @SubscribeMapping("/stomp2.do")
    @SendTo("/topic/hello")
    public User subsTest() {
        User user2 = new User();
        user2.setName("訂閱name");
        user2.setPhone("subscribePhone");
        return user2;
    }
    
    
}

 

進入activemq的控制檯,點選connection可以看到stomp連線:(這裡連線了兩個客戶端,可以點選超連結檢視連線狀態)

 

檢視啟動日誌可以發現:表明啟動成功

進入頁面點選第一個按鈕,會觸發send:

同時控制檯可以看到:

 

單獨點選第二個按鈕不會有返回訊息,點選第三個按鈕之後會訂閱/topic/hello,此時點選第一個觸發send。在服務端執行完畢後通過sendto註解釋出到/topic/hello,此時瀏覽器控制檯會輸出該頻道釋出的內容

 

注意點:

1:基於activemq作為代理,連線的埠號為61613。

2:setSystemLogin表示設定伺服器連線代理(activemq)的賬號密碼,setClientLogin表示連線過來的客戶端連線代理所需要的賬號密碼。