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页面:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
<%@ 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表示连接过来的客户端连接代理所需要的账号密码。