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表示連接過來的客戶端連接代理所需要的賬號密碼。