在客户端javascript我有
stomp.subscribe("/topic/path", function (message) {
console.info("message received");
});
在服务器端
public class Controller {
private final MessageSendingOperations<String> messagingTemplate;
@Autowired
public Controller(MessageSendingOperations<String> messagingTemplate) {
this.messagingTemplate = messagingTemplate;
}
@SubscribeMapping("/topic/path")
public void subscribe() {
LOGGER.info("before send");
messagingTemplate.convertAndSend(/topic/path, "msg");
}
}
从这个设置中,偶尔(大约30次刷新一次)我会遇到消息丢失,这意味着我既看不到&#34;收到消息&#34;客户端的消息,Chrome调试工具的websocket流量。
&#34;发送之前&#34;始终记录在服务器端。
当我在subscribe()方法中调用它时,看起来MessageSendingOperations尚未就绪。 (如果我把Thread.sleep(50);在调用messagingTemplate.convertAndSend之前,问题就会消失(或者不太可能被复制)。
我想知道是否有人经历过同样的事情,如果有事件可以告诉我MessageSendingOperations是否准备就绪。
答案 0 :(得分:4)
您面临的问题是clientInboundChannel
的性质,默认为ExecutorSubscribableChannel
。
它有3个subscribers
:
0 = {SimpleBrokerMessageHandler@5276} "SimpleBroker[DefaultSubscriptionRegistry[cache[0 destination(s)], registry[0 sessions]]]"
1 = {UserDestinationMessageHandler@5277} "UserDestinationMessageHandler[DefaultUserDestinationResolver[prefix=/user/]]"
2 = {SimpAnnotationMethodMessageHandler@5278} "SimpAnnotationMethodMessageHandler[prefixes=[/app/]]"
在taskExecutor
内调用,因此是异步的。
此处的第一个(SimpleBrokerMessageHandler
(或StompBrokerRelayMessageHandler
),如果您使用broker-relay
),则有责任为subscription
注册topic
。
您的messagingTemplate.convertAndSend(/topic/path, "msg")
操作可能在该WebSocket会话的订阅注册之前执行,因为它们是在单独的线程中执行的。因此,经纪人处理程序并不知道您将消息发送到会话。
可以使用@SubscribeMapping
在方法上配置return
,其中此方法的结果将作为对客户端上subscription
函数的回复发送。
HTH
答案 1 :(得分:1)
这是我的解决方案。它是沿着相同的路线。添加了ExecutorChannelInterceptor
并发布了自定义SubscriptionSubscribedEvent
。关键是要在AbstractBrokerMessageHandler处理完消息后发布事件,这意味着订阅已在代理中注册。
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(new ExecutorChannelInterceptorAdapter() {
@Override
public void afterMessageHandled(Message<?> message, MessageChannel channel, MessageHandler handler, Exception ex) {
SimpMessageHeaderAccessor accessor = SimpMessageHeaderAccessor.wrap(message);
if (accessor.getMessageType() == SimpMessageType.SUBSCRIBE && handler instanceof AbstractBrokerMessageHandler) {
/*
* Publish a new session subscribed event AFTER the client
* has been subscribed to the broker. Before spring was
* publishing the event after receiving the message but not
* necessarily after the subscription occurred. There was a
* race condition because the subscription was being done on
* a separate thread.
*/
applicationEventPublisher.publishEvent(new SessionSubscribedEvent(this, message));
}
}
});
}
答案 2 :(得分:0)
有点晚了,但我想我会添加我的解决方案。在通过消息传递模板发送数据之前,我遇到了相同的订阅问题。由于与DefaultSubscriptionRegistry
竞争,这个问题很少发生且不可预测。
不幸的是,我不能只使用@SubscriptionMapping
的return方法,因为我们使用的是根据用户类型动态更改的自定义对象映射器(本质上是属性过滤)。
我搜索了Spring代码,发现SubscriptionMethodReturnValueHandler
负责发送订阅映射的返回值,并且与我的异步控制器的自动装配SimpMessagingTemplate
有一个不同的messagingTemplate !!
因此解决方案是将MessageChannel clientOutboundChannel
自动装入我的异步控制器并使用它来创建SimpMessagingTemplate
。 (你不能直接连接它,因为你只是让模板进入经纪人。)
在订阅方法中,我使用直接模板,而在其他方法中,我使用了转发到代理的模板。