springmvc(18)使用WebSocket 和 STOMP 实现消息功能
scheme1)我们必须编写一个实现 WebSocketHandler:public interface WebSocketHandler { void afterConnectionEstablished(WebSocketSession session) throws Exception; void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception; void handleTransportError(WebSocketSession session,Throwable exception) throws Exception; void afterConnectionClosed(WebSocketSession session,CloseStatus closeStatus) throws Exception; boolean supportsPartialMessages(); }
scheme2)当然,我们也可以扩展 AbstractWebSocketHandler(更加简单一点);// you can also extends TextWebSocketHandler public class ChatTextHandler extends AbstractWebSocketHandler {// handle text msg.@Overrideprotected void handleTextMessage(WebSocketSession session,TextMessage message) throws Exception {session.sendMessage(new TextMessage("hello world."));} }
对以上代码的分析(Analysis): 当然了,我们还可以重载其他三个方法:handleBinaryMessage() handlePongMessage() handleTextMessage()
scheme3)也可以扩展 TextWebSocketHandler(文本 WebSocket 处理器), 不在扩展AbstractWebSocketHandler, TextWebSocketHandler 继承 AbstractWebSocketHandler ;
// 当新连接建立的时候,被调用;
public void afterConnectionEstablished(WebSocketSession session)
throws Exception {
logger.info("Connection established");
}
// 当连接关闭时被调用;
@Override
public void afterConnectionClosed(
WebSocketSession session, CloseStatus status) throws Exception {
logger.info("Connection closed. Status: " + status);
}
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer{@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {registry.addHandler(getTextHandler(), "/websocket/p2ptext");} // 将 ChatTextHandler 处理器 映射到 /websocket/p2ptext 路径下.@Beanpublic ChatTextHandler getTextHandler() {return new ChatTextHandler();}
}
4.1)client 发送 一个文本到 server,他监听来自 server 的文本消息。下面代码 展示了 利用 js 开启一个原始的 WebSocket 并使用它来发送消息给server;4.2)代码如下:<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%><html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! --> <title>web socket</title><link href="<c:url value="/"/>bootstrap/css/bootstrap.min.css" rel="stylesheet"><!-- jQuery (necessary for Bootstrap's JavaScript plugins) --> <script src="<c:url value="/"/>bootstrap/jquery/jquery.min.js"></script> <!-- Include all compiled plugins (below), or include individual files as needed --> <script src="<c:url value="/"/>bootstrap/js/bootstrap.min.js"></script><script type="text/javascript"> $(document).ready(function() { websocket_client(); });function websocket_client() { var hostaddr = window.location.host + "<c:url value='/websocket/p2ptext' />"; var url = 'ws://' + hostaddr; var sock = new WebSocket(url);// 以下的 open(), onmessage(), onclose() // 对应到 ChatTextHandler 的 // afterConnectionEstablished(), handleTextMessage(), afterConnectionClosed();sock.open = function() { alert("open successfully."); sayMarco(); };sock.onmessage = function(e) { alert("onmessage"); alert(e); };sock.onclose = function() { alert("close"); };function sayMarco() { sock.send("this is the websocket client."); } } </script> </head><body> <div id="websocket"> websocket div. </div> </body> </html>
1.1)problem:许多浏览器不支持 WebSocket 协议;1.2)solutions: SockJS 是 WebSocket 技术的一种模拟。SockJS 会 尽可能对应 WebSocket API,但如果 WebSocket 技术 不可用的话,就会选择另外的 通信方式协议;
XHR streaming
XDR streaming
iFrame event source
iFrame HTML file
XHR polling
XDR polling
iFrame XHR polling
JSONP polling
// 将 ChatTextHandler 映射到 /chat/text 路径下.@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {registry.addHandler(getTextHandler(), "/websocket/p2ptext").withSockJS();
// withSockJS() 方法声明我们想要使用 SockJS 功能,如果WebSocket不可用的话,会使用 SockJS;}
4)客户端配置 SockJS, 想要确保 加载了 SockJS 客户端;
<script src="http://cdn.sockjs.org/sockjs-0.3.min.js"></script>
A1)在springmvc 配置中搭建一个 资源处理器,让它负责解析路径以 "webjars/**" 开头的请求,这也是 WebJars 的标准路径:@Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/webjars/**") .addResourceLocations("classpath:/META-INF/resources/webjars/"); }
A2)在这个资源处理器 准备就绪后,我们可以在 web 页面中使用 如下的 <script> 标签加载 SockJS 库;<script src="sockjs.min.js}"> </script>
var url = 'p2ptext';
var sock = new SockJS(url);
A1)SockJS 所处理的URL 是 "http://" 或 "https://" 模式,而不是 "ws://" or "wss://" ;A2)其他的函数如 onopen, onmessage, and onclose ,SockJS 客户端与 WebSocket 一样;
1.1)假设 HTTP 协议 并不存在,只能使用 TCP 套接字来 编写 web 应用,你可能认为这是一件疯狂的 事情;1.2)不过 幸好,我们有 HTTP协议,它解决了 web 浏览器发起请求以及 web 服务器响应请求的细节;1.3)直接使用 WebSocket(SockJS) 就很类似于 使用 TCP 套接字来编写 web 应用;因为没有高层协议,因此就需要我们定义应用间所发送消息的语义,还需要确保 连接的两端都能遵循这些语义;1.4)同 HTTP 在 TCP 套接字上添加 请求-响应 模型层一样,STOMP 在 WebSocket 之上提供了一个基于 帧的线路格式层,用来定义消息语义;(干货——STOMP 在 WebSocket 之上提供了一个基于 帧的线路格式层,用来定义消息语义)
SEND
destination:/app/marco
content-length:20{\"message\":\"Marco!\"}
A1)SEND:STOMP命令,表明会发送一些内容;A2)destination:头信息,用来表示消息发送到哪里;A3)content-length:头信息,用来表示 负载内容的 大小;A4)空行:A5)帧内容(负载)内容:
package com.spring.spittr.web;import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.handler.annotation.MessageMapping;
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;@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {@Overridepublic void configureMessageBroker(MessageBrokerRegistry config) {config.enableSimpleBroker("/topic", "/queue");config.setApplicationDestinationPrefixes("/app");// 应用程序以 /app 为前缀,而 代理目的地以 /topic 为前缀.// js.url = "/spring13/app/hello" -> @MessageMapping("/hello") 注释的方法.}@Overridepublic void registerStompEndpoints(StompEndpointRegistry registry) {registry.addEndpoint("/hello").withSockJS();// 在网页上我们就可以通过这个链接 /server/hello ==<c:url value='/hello'></span> 来和服务器的WebSocket连接}
}
A1)EnableWebSocketMessageBroker注解表明: 这个配置类不仅配置了 WebSocket,还配置了 基于代理的 STOMP 消息;A2)它重载了 registerStompEndpoints() 方法:将 "/hello" 路径 注册为 STOMP 端点。这个路径与之前发送和接收消息的目的路径有所不同, 这是一个端点,客户端在订阅或发布消息 到目的地址前,要连接该端点,即 用户发送请求 url='/server/hello' 与 STOMP server 进行连接,之后再转发到 订阅url;(server== name of your springmvc project )(干货——端点的作用——客户端在订阅或发布消息 到目的地址前,要连接该端点)A3)它重载了 configureMessageBroker() 方法:配置了一个 简单的消息代理。如果不重载,默认case下,会自动配置一个简单的 内存消息代理,用来处理 "/topic" 为前缀的消息。但经过重载后,消息代理将会处理前缀为 "/topic" and "/queue" 消息。A4)之外:发送应用程序的消息将会带有 "/app" 前缀,下图展现了 这个配置中的 消息流;
对上述处理step的分析(Analysis):
A1)应用程序的目的地 以 "/app" 为前缀,而代理的目的地以 "/topic" 和 "/queue" 作为前缀;A2)以应用程序为目的地的消息将会直接路由到 带有 @MessageMapping 注解的控制器方法中;(干货——@MessageMapping的作用)A3)而发送到 代理上的消息,包括 @MessageMapping注解方法的返回值所形成的消息,将会路由到 代理上,并最终发送到 订阅这些目的地客户端;(干货——client 连接地址和 发送地址是不同的,以本例为例,前者是/server/hello, 后者是/server/app/XX,先连接后发送)
@Overridepublic void configureMessageBroker(MessageBrokerRegistry registry) {// 启用了 STOMP 代理中继功能,并将其代理目的地前缀设置为 /topic and /queue .registry.enableStompBrokerRelay("/queue", "/topic").setRelayPort(62623);registry.setApplicationDestinationPrefixes("/app"); // 应用程序目的地.}
A1)方法第一行启用了 STOMP 代理中继功能: 并将其目的地前缀设置为 "/topic" or "/queue" ;spring就能知道 所有目的地前缀为 "/topic" or "/queue" 的消息都会发送到 STOMP 代理中;A2)方法第二行设置了 应用的前缀为 "app":所有目的地以 "/app" 打头的消息(发送消息url not 连接url)都会路由到 带有 @MessageMapping 注解的方法中,而不会发布到 代理队列或主题中;
A1)enableStompBrokerRelay() and setApplicationDestinationPrefixes() 方法都可以接收变长 参数;A2)默认情况下: STOMP 代理中继会假设 代理监听 localhost 的61613 端口,并且 client 的 username 和password 均为 guest。当然你也可以自行定义;@Override public void configureMessageBroker(MessageBrokerRegistry registry) { registry.enableStompBrokerRelay("/topic", "/queue") .setRelayHost("rabbit.someotherserver") .setRelayPort(62623) .setClientLogin("marcopolo") .setClientPasscode("letmein01"); registry.setApplicationDestinationPrefixes("/app", "/foo"); } // setXXX()方法 是可选的
package com.spring.spittr.web;import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;import com.spring.pojo.Greeting;
import com.spring.pojo.HelloMessage;@Controller
public class GreetingController {@MessageMapping("/hello")@SendTo("/topic/greetings")public Greeting greeting(HelloMessage message) throws Exception {System.out.println("receiving " + message.getName());System.out.println("connecting successfully.");return new Greeting("Hello, " + message.getName() + "!");}
}
对以上代码的分析(Analysis):
A1)@MessageMapping注解:表示 handleShout()方法能够处理 指定目的地上到达的消息;A2)这个目的地(消息发送目的地url)就是 "/server/app/hello",其中 "/app" 是 隐含的 ,"/server" 是 springmvc 项目名称;
@SubscribeMapping({"/marco"})
public Shout handleSubscription() {
Shout outgoing = new Shout();
outgoing.setMessage("Polo!");
return outgoing;
}
A1)@SubscribeMapping注解 的方法来处理 对 "/app/macro" 目的地订阅(与 @MessageMapping类似,"/app" 是隐含的 );A2)请求-回应模式与 HTTP GET 的全球-响应模式差不多: 关键区别在于, HTTP GET 请求是同步的,而订阅的全球-回应模式是异步的,这样客户端能够在回应可用时再去处理,而不必等待;(干货——HTTP GET 请求是同步的,而订阅的请求-回应模式是异步的)
<script type="text/javascript">var stompClient = null;function setConnected(connected) {document.getElementById('connect').disabled = connected;document.getElementById('disconnect').disabled = !connected;document.getElementById('conversationDiv').style.visibility = connected ? 'visible' : 'hidden';document.getElementById('response').innerHTML = '';}function connect() {var socket = new SockJS("<c:url value='/hello'/>");stompClient = Stomp.over(socket);stompClient.connect({}, function(frame) {setConnected(true);console.log('Connected: ' + frame);stompClient.subscribe('/topic/greetings', function(greeting){showGreeting(JSON.parse(greeting.body).content);});});}function disconnect() {if (stompClient != null) {stompClient.disconnect();}setConnected(false);console.log("Disconnected");}function sendName() {var name = document.getElementById('name').value;stompClient.send("/app/hello", {}, JSON.stringify({ 'name': name }));}function showGreeting(message) {var response = document.getElementById('response');var p = document.createElement('p');p.style.wordWrap = 'break-word';p.appendChild(document.createTextNode(message));response.appendChild(p);}</script>
对以上代码的 分析(Analysis): 以上代码连接“/hello” 端点并发送 ”name“;
<script src="<c:url value="/resources/sockjs-1.1.1.js" />"></script><script src="<c:url value="/resources/stomp.js" />"></script>
//this line.function connect() {var socket = new SockJS("<c:url value='/hello'/>");stompClient = Stomp.over(socket);stompClient.connect({}, function(frame) {setConnected(true);console.log('Connected: ' + frame);stompClient.subscribe('/topic/greetings', function(greeting){showGreeting(JSON.parse(greeting.body).content);});stompClient.subscribe('/app/macro',function(greeting){alert(JSON.parse(greeting.body).content);showGreeting(JSON.parse(greeting.body).content);});});}function sendName() {var name = document.getElementById('name').value;stompClient.send("/app/hello", {}, JSON.stringify({ 'name': name }));}
package com.spring.spittr.web;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessageSendingOperations;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.messaging.simp.annotation.SubscribeMapping;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;import com.spring.pojo.Greeting;
import com.spring.pojo.HelloMessage;@Controller
public class GreetingController {// @MessageMapping defines the sending addr for client.// 消息发送地址: /server/app/hello@MessageMapping("/hello")@SendTo("/topic/greetings")public Greeting greeting(HelloMessage message) throws Exception {System.out.println("receiving " + message.getName());System.out.println("connecting successfully.");return new Greeting("Hello, " + message.getName() + "!");}@SubscribeMapping("/macro")public Greeting handleSubscription() {System.out.println("this is the @SubscribeMapping('/marco')");Greeting greeting = new Greeting("i am a msg from SubscribeMapping('/macro').");return greeting;}/*@MessageMapping("/feed")@SendTo("/topic/feed")public Greeting greetingForFeed(HelloMessage message) throws Exception {System.out.println("receiving " + message.getName());System.out.println("connecting successfully.");return new Greeting("i am /topic/feed, hello " + message.getName() + "!");}*/// private SimpMessagingTemplate template;// SimpMessagingTemplate implements SimpMessageSendingOperations. private SimpMessageSendingOperations template;@Autowiredpublic GreetingController(SimpMessageSendingOperations template) {this.template = template;}@RequestMapping(path="/feed", method=RequestMethod.POST)public void greet(@RequestParam String greeting) {String text = "you said just now " + greeting;this.template.convertAndSend("/topic/feed", text);}
}
package com.spring.spittr.web;import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.handler.annotation.MessageMapping;
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;@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {@Overridepublic void configureMessageBroker(MessageBrokerRegistry config) {config.enableSimpleBroker("/topic", "/queue");config.setApplicationDestinationPrefixes("/app");// 应用程序以 /app 为前缀,而 代理目的地以 /topic 为前缀.// js.url = "/spring13/app/hello" -> @MessageMapping("/hello") 注释的方法.}@Overridepublic void registerStompEndpoints(StompEndpointRegistry registry) {registry.addEndpoint("/hello").withSockJS();// 在网页上我们就可以通过这个链接 /server/hello 来和服务器的WebSocket连接}
}
package com.spring.spittr.web;import java.io.IOException;import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.tiles3.TilesConfigurer;
import org.springframework.web.servlet.view.tiles3.TilesViewResolver;@Configuration
@ComponentScan(basePackages = { "com.spring.spittr.web" })
@EnableWebMvc
@Import({WebSocketConfig.class})
public class WebConfig extends WebMvcConfigurerAdapter {@Beanpublic TilesConfigurer tilesConfigurer() {TilesConfigurer tiles = new TilesConfigurer();tiles.setDefinitions(new String[] { "/WEB-INF/layout/tiles.xml" });tiles.setCheckRefresh(true);return tiles;}// config processing for static resources.@Overridepublic void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {configurer.enable();}// InternalResourceViewResolver @Beanpublic ViewResolver viewResolver1() {TilesViewResolver resolver = new TilesViewResolver();return resolver;}@Beanpublic ViewResolver viewResolver2() {InternalResourceViewResolver resolver = new InternalResourceViewResolver();resolver.setPrefix("/WEB-INF/views/");resolver.setSuffix(".jsp");resolver.setExposeContextBeansAsAttributes(true);resolver.setViewClass(org.springframework.web.servlet.view.JstlView.class);return resolver;}@Beanpublic MessageSource messageSource() {ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();messageSource.setBasename("messages"); return messageSource;}@Beanpublic MultipartResolver multipartResolver() throws IOException {CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();multipartResolver.setUploadTempDir(new FileSystemResource("/WEB-INF/tmp/spittr/uploads"));multipartResolver.setMaxUploadSize(2097152);multipartResolver.setMaxInMemorySize(0);return multipartResolver;}
}
method1)作为处理消息 或处理订阅的附带结果;method2)使用消息模板;
@MessageMapping("/hello")@SendTo("/topic/greetings") //highlight line.public Greeting greeting(HelloMessage message) throws Exception {System.out.println("receiving " + message.getName());System.out.println("connecting successfully.");return new Greeting("Hello, " + message.getName() + "!");}
对以上代码的分析(Analysis):返回的对象将会进行转换(通过消息转换器) 并放到 STOMP 帧的负载中,然后发送给消息代理(消息代理分为 STOMP代理中继 和 内存消息代理);
代码同上。
对以上代码的分析(Analysis):消息将会发布到 /topic/hello, 所有订阅这个主题的应用都会收到这条消息;
@SubscribeMapping("/macro") // defined in Controller. attention for addr '/macro' in server.public Greeting handleSubscription() {System.out.println("this is the @SubscribeMapping('/marco')");Greeting greeting = new Greeting("i am a msg from SubscribeMapping('/macro').");return greeting;}
function connect() {var socket = new SockJS("<c:url value='/hello'/>");stompClient = Stomp.over(socket);stompClient.connect({}, function(frame) {setConnected(true);console.log('Connected: ' + frame);stompClient.subscribe('/topic/greetings', function(greeting){showGreeting(JSON.parse(greeting.body).content);});// starting line.stompClient.subscribe('/app/macro',function(greeting){alert(JSON.parse(greeting.body).content);showGreeting(JSON.parse(greeting.body).content);}); // ending line. attention for addr '/app/macro' in client.});}
A0)这个SubscribeMapping annotation标记的方法,是在订阅的时候调用的,也就是说,基本是只执行一次的方法,client 调用定义在server 的 该 Annotation 标注的方法,它就会返回结果,不过经过代理。
A1)这里的 @SubscribeMapping 注解表明当 客户端订阅 "/app/macro" 主题的时候("/app"是应用目的地的前缀,注意,这里没有加springmvc 项目名称前缀), 将会调用 handleSubscription 方法。它所返回的shout 对象 将会进行转换 并发送回client;A2)SubscribeMapping注解的区别在于: 这里的 Shout 消息将会直接发送给 client,不用经过 消息代理;但,如果为方法添加 @SendTo 注解的话,那么 消息将会发送到指定的目的地,这样就会经过代理;(干货——SubscribeMapping注解返回的消息直接发送到 client,不经过代理,而 @SendTo 注解的路径,就会经过代理,然后再发送到 目的地)
<script>
var sock = new SockJS('spittr');
var stomp = Stomp.over(sock);
stomp.connect('guest', 'guest', function(frame) {
console.log('Connected');
stomp.subscribe("/topic/spittlefeed", handleSpittle); // highlight.
});
function handleSpittle(incoming) {
var spittle = JSON.parse(incoming.body);
console.log('Received: ', spittle);
var source = $("#spittle-template").html();
var template = Handlebars.compile(source);
var spittleHtml = template(spittle);
$('.spittleList').prepend(spittleHtml);
}
</script>
@Service
public class SpittleFeedServiceImpl implements SpittleFeedService {
private SimpMessageSendingOperations messaging;
@Autowired
public SpittleFeedServiceImpl(
SimpMessageSendingOperations messaging) { // 注入消息模板.
this.messaging = messaging;
}
public void broadcastSpittle(Spittle spittle) {
messaging.convertAndSend("/topic/spittlefeed", spittle); // 发送消息.
}
}
way1)@MessageMapping and @SubscribeMapping 注解标注的方法 能够使用 Principal 来获取认证用户;way2)@MessageMapping, @SubscribeMapping, and @MessageException 方法返回的值能够以 消息的形式发送给 认证用户;way3)SimpMessagingTemplate 能够发送消息给特定用户;
1.1)代码如下:它会处理传入的消息并将其存储我 Spittle:@MessageMapping("/spittle") @SendToUser("/queue/notifications") public Notification handleSpittle( Principal principal, SpittleForm form) { Spittle spittle = new Spittle( principal.getName(), form.getText(), new Date()); spittleRepo.save(spittle); return new Notification("Saved Spittle"); }
1.2)该方法最后返回一个 新的 Notificatino,表明对象保存成功;1.3)该方法使用了 @MessageMapping("/spittle") 注解,所以当有发往 "/app/spittle" 目的地的消息 到达时,该方法就会触发;如果用户已经认证的话,将会根据 STOMP 帧上的头信息得到 Principal 对象;1.4)@SendToUser注解: 指定了 Notification 要发送的 目的地 "/queue/notifications";1.5)表明上, "/queue/notifications" 并不会与 特定用户相关联,但因为 这里使用的是 @SendToUser注解, 而不是 @SendTo,所以 就会发生更多的事情了;
2.1)看个荔枝:考虑如下的 JavaScript代码,它订阅了一个 用户特定的 目的地:stomp.subscribe("/user/queue/notifications", handleNotifications);
对以上代码的分析(Analysis):这个目的地使用了 "/user" 作为前缀,在内部,以"/user" 为前缀的消息将会通过 UserDestinationMessageHandler 进行处理,而不是 AnnotationMethodMessageHandler 或 SimpleBrokerMessageHandler or StompBrokerRelayMessageHandler,如下图所示:
Attention)UserDestinationMessageHandler 的主要任务: 是 将用户消息重新路由到 某个用户独有的目的地上。 在处理订阅的时候,它会将目标地址中的 "/user" 前缀去掉,并基于用户 的会话添加一个后缀。如,对 "/user/queue/notifications" 的订阅最后可能路由到 名为 "/queue/notifacations-user65a4sdfa" 目的地上;
@Service
public class SpittleFeedServiceImpl implements SpittleFeedService {
private SimpMessagingTemplate messaging;
// 实现用户提及功能的正则表达式
private Pattern pattern = Pattern.compile("\\@(\\S+)"); @Autowired
public SpittleFeedServiceImpl(SimpMessagingTemplate messaging) {
this.messaging = messaging;
}
public void broadcastSpittle(Spittle spittle) {
messaging.convertAndSend("/topic/spittlefeed", spittle);
Matcher matcher = pattern.matcher(spittle.getMessage());
if (matcher.find()) {
String username = matcher.group(1);
// 发送提醒给用户.
messaging.convertAndSendToUser(
username, "/queue/notifications",
new Notification("You just got mentioned!"));
}
}
}
@MessageExceptionHandler
public void handleExceptions(Throwable t) {
logger.error("Error handling message: " + t.getMessage());
}
@MessageExceptionHandler(SpittleException.class) // highlight line.
public void handleExceptions(Throwable t) {
logger.error("Error handling message: " + t.getMessage());
}
// 或者:
@MessageExceptionHandler( {SpittleException.class, DatabaseException.class}) // highlight line.
public void handleExceptions(Throwable t) {
logger.error("Error handling message: " + t.getMessage());
}
@MessageExceptionHandler(SpittleException.class)
@SendToUser("/queue/errors")
public SpittleException handleExceptions(SpittleException e) {
logger.error("Error handling message: " + e.getMessage());
return e;
}
// 如果抛出 SpittleException 的话,将会记录这个异常,并将其返回.
// 而 UserDestinationMessageHandler 会重新路由这个消息到特定用户所对应的 唯一路径;
springmvc(18)使用WebSocket 和 STOMP 实现消息功能相关推荐
- Spring使用WebSocket、SockJS、STOMP实现消息功能
WebSocket 概述 WebSocket协议提供了通过一个套接字实现全双工通信的功能.除了其他的功能之外,它能够实现Web浏览器和服务器之间的异步通信.全双工意味着服务器可以发送消息给浏览器,浏览 ...
- Spring Boot 2 中通过 WebSocket 发送 STOMP 消息
描述 在这篇博客中,我们将了解如何设置应用程序以通过 WebSocket 连接,发送和接收 STOMP 消息.我们将以 Spring Boot 2 为基础,因为它包含对 STOMP 和 WebSock ...
- springboot+rabbitmq+vue实现stomp协议消息推送
springboot+rabbitmq+vue实现stomp协议消息推送 一.rabbitmq添加stomp插件 rabbitmq 默认是没有开启Socket STOMP插件的.如需使用,例如集成sp ...
- 基于websocket的网页实时消息推送与在线聊天(上篇)
文章目录 @[toc] 基于websocket的网页实时消息推送与在线聊天(上篇) "使用dwebsocket在django中实现websocket" websocket原理图 d ...
- 抓取WebSocket推送的消息
介绍 很多直播或对数据及时性要求比较高的网站,使用了WebSocket.这种数据要怎么抓呢? 我们这里以socket.io为例,我们可以查看网站网页源代码看使用的H5的WebSocket还是socke ...
- SpringBoot +WebSocket实现简单聊天室功能实例
SpringBoot +WebSocket实现简单聊天室功能实例) 一.代码来源 二.依赖下载 三.数据库准备(sql) 数据库建表并插入sql 四.resources文件配置 application ...
- WebSocket(3)---实现一对一聊天功能
实现一对一聊天功能 功能介绍:实现A和B单独聊天功能,即A发消息给B只能B接收,同样B向A发消息只能A接收. 本篇博客是在上一遍基础上搭建,上一篇博客地址:[WebSocket]---实现游戏公告功能 ...
- html5利用websocket完成的推送功能(tomcat)
html5利用websocket完成的推送功能(tomcat) 利用websocket和java完成的消息推送功能,服务器用的是tomcat7.0.42,一些东西是自己琢磨的,也不知道恰不恰当,不恰当 ...
- 微信公众号开发 [04] 模板消息功能的开发
1.模板消息的概况 模板消息的定位是用户触发后的通知消息,不允许在用户没做任何操作或未经用户同意接收的前提下,主动下发消息给用户.目前在特殊情况下允许主动下发的消息只有故障类和灾害警示警告类通知,除此 ...
最新文章
- swagger2中UI界面接口点击无法展开问题解决
- linux redhat 下命令行全部乱码解决
- 天才王垠惊人言论炸翻网友:相对论是假说,爱因斯坦是民科!
- 微信开发者工具:Failed to load font ************** net::ERR_CONNECTION_RESET问题解决办法
- php study是什么,phpstudy有什么用
- javacript IO
- UE4中Bebavior Tree中Delay及其后面代码失效的原因
- 从输入 URL 到页面展示,这中间发生了什么?
- Java中一个逐渐被遗忘的强大功能,强到你难以置信!
- 敏感词过滤的php代码,ThinkPHP敏感词汇过滤
- 软考初级程序员常见类型题,错题个人笔记
- Mac下用命令行打开pdf文件
- 【SCOI 2005】王室联邦 树上分块?
- linux的ping命令含义,Linux ping命令详解
- 计算机学院新增电子信息!齐鲁工业大学
- arm方案商,三星S5P6818开发板ARM Cortex-A53架构
- 微信小程序 环形进度条_微信小程序实现圆形进度条实例分享
- edge浏览器如何设置无痕浏览 无痕浏览网页方法
- 美科学家试解“姆潘巴现象”
- 首届长三角青少年人工智能擂台赛全记录(YOLOv5+Win10+Anaconda+Pycharm+ModelArts)
热门文章
- 【WC2016】挑战NPC 【带花树】【建图】
- 【bzoj2555】Substring【后缀平衡树入门】
- 牛客题霸 [ 	判断一棵二叉树是否为搜索二叉树和完全二叉树] C++题解/答案
- Poj 1284 Primitive Roots
- [CodeForces 1603C] Extreme Extension(贪心 + 数论分块优化dp)
- YBTOJ:魔法数字(数位dp)
- 多重背包的二进制优化(ybtoj-宝物筛选)
- CF917D-Stranger Trees【矩阵树定理,高斯消元】
- USACO2.4の其中3道水题【模拟,图论】
- tyvj/joyOI 1305-最大子序和【单调队列】