Spring @SubscribeMapping是否真的为客户订阅某个主题?

问题描述 投票:32回答:4

我正在使用Spring Websocket和STOMP,Simple Message Broker。在我的@Controller中,我使用方法级别的@SubscribeMapping,它应该将客户端订阅到一个主题,以便客户端之后会收到该主题的消息。假设客户订阅主题“聊天”:

stompClient.subscribe('/app/chat', ...);

当客户订阅“/ app / chat”而不是“/ topic / chat”时,此订阅将转到使用@SubscribeMapping映射的方法:

@SubscribeMapping("/chat")
public List getChatInit() {
    return Chat.getUsers();
}

这是Spring ref。说:

默认情况下,@ SubsscribeMapping方法的返回值作为消息直接发送回连接的客户端,并且不通过代理。这对于实现请求 - 回复消息交互很有用;例如,在初始化应用程序UI时获取应用程序数据。

好的,这就是我想要的,但只是部分!!订阅后发送一些init-data,好吧。但订阅呢?在我看来,这里发生的事情只是一个请求 - 回复,就像一个服务。订阅只是消费。如果是这种情况,请澄清我。

  • 如果经纪人没有参与其中,客户是否订阅了某些地方?
  • 如果以后我想向“聊天”下标者发送一些消息,客户会收到它吗?它似乎不是这样。
  • 谁真正实现了订阅?经纪人?或者其他人?

如果在这里客户端没有订阅任何地方,我想知道为什么我们称之为“订阅”;因为客户端只收到一条消息而不是将来的消息。

编辑:

为了确保订阅已经实现,我尝试的内容如下:

服务器端:

组态:

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic");
        config.setApplicationDestinationPrefixes("/app");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/hello").withSockJS();
    }
}

控制器:

@Controller
public class GreetingController {

    @MessageMapping("/hello")
    @SendTo("/topic/greetings")
    public Greeting greeting(HelloMessage message) throws Exception {
        System.out.println("inside greeting");
        return new Greeting("Hello, " + message.getName() + "!");
    }

    @SubscribeMapping("/topic/greetings")
    public Greeting try1() {
        System.out.println("inside TRY 1");
        return new Greeting("Hello, " + "TRY 1" + "!");
    }
}

客户端:

...
    stompClient.subscribe('/topic/greetings', function(greeting){
                        console.log('RECEIVED !!!');
                    });
    stompClient.send("/app/hello", {}, JSON.stringify({ 'name': name }));
...

我想发生什么:

  1. 当客户订阅'/topic/greetings'时,执行try1方法。
  2. 当客户端向'qazxsw poi'发送消息时,它应该收到问候消息,这将是qazxsw poi'qazxsw poi'。

结果:

  1. 如果客户端订阅了/app/hello,那么方法@SendTo无法捕获它。
  2. 当客户端向'qazxsw poi'发送消息时,执行了qazxsw poi方法,客户端收到了问候消息。所以我们理解它已经正确地订阅了'/topic/greetings'。
  3. 但请记住1.失败了。经过一番尝试后,客户端订阅了/topic/greetings,即以try1为前缀(这可以通过配置理解)。
  4. 现在1.正在工作,但是这次2.失败:当客户端将msg发送到'/app/hello'时,是的,greeting方法被执行,但是客户端没有收到问候消息。 (因为可能现在客户端订阅了以'/topic/greetings'为前缀的主题,这是不需要的。)

所以,我得到的是我想要的1或2,但不是这两个。

  • 如何使用此结构实现此目的(正确配置映射路径)?
spring stomp messagebroker spring-websocket spring-messaging
4个回答
15
投票

默认情况下,@ SubsscribeMapping方法的返回值作为消息直接发送回连接的客户端,并且不通过代理。

(强调我的)

这里的Spring Framework文档描述了响应消息发生了什么,而不是传入的'/app/topic/greetings'消息。

那么回答你的问题:

  • 是的,客户端订阅了该主题
  • 是的,如果您使用该主题发送,则订阅该主题的客户将收到一条消息
  • 消息代理负责管理订阅

更多关于订阅管理

使用/app,消息代理实现存在于您的应用程序实例中。订阅注册由/app/hello管理。在接收消息时,greeting处理/app消息并注册订阅(SUBSCRIBE)。

使用像RabbitMQ这样的“真实”消息代理,您已经配置了一个将消息转发给代理的Stomp代理中继。在这种情况下,SimpleMessageBroker消息被转发给负责管理订阅的代理(DefaultSubscriptionRegistry)。

更新 - 有关STOMP消息流的更多信息

如果你看看SimpleBrokerMessageHandler,你会看到:

  • “/ topic / greeting”的订阅通过“clientInboundChannel”传递并转发给代理
  • 发送到“/ app / greeting”的问候语通过“clientInboundChannel”传递并转发给GreetingController。控制器添加当前时间,返回值通过“brokerChannel”作为消息传递给“/ topic / greeting”(目的地根据约定选择,但可以通过@SendTo覆盖)。

所以在这里,SUBSCRIPTION是经纪人的目的地;发送的消息直接转发给代理。虽然see implementation here是一个应用程序目的地,并且应该发送一条消息发送给SUBSCRIBE,除非see implementation here说不然。

现在,您更新的问题在某种程度上是不同的,如果没有更精确的用例,很难说哪种模式最适合解决这个问题。以下是一些:

  • 您希望客户端在发生异常时发现异常:订阅特定主题the reference documentation on STOMP message flow
  • 您想要广播消息:向特定主题/topic/hello发送消息
  • 你想获得某些东西的即时反馈,例如初始化你的应用程序的状态:SUBSCRIBE到一个应用程序目的地/app/hello,控制器立即响应消息
  • 您想要将一条或多条消息发送到任何应用程序目标/topic/hello:使用@SendTo/topic/hello或消息传递模板的组合。

如果你想要一个很好的例子,那么看看/topic/hello


14
投票

所以有两个:

  • 使用主题来处理订阅
  • 在该主题上使用@SubscribeMapping来提供连接响应

不像你经历的那样(和我一样)。

解决你的情况的方法(就像我做的那样)是:

  1. 删除@SubscribeMapping - 它只适用于/ app前缀
  2. 就像你自然一样订阅/ topic(没有/ app前缀)
  3. 实现ApplicationListener 如果你想直接回复单个客户端使用用户目的地(参见/app/hello或者你也可以订阅子路径,例如/ topic / my-id-42,那么你可以向这个子主题发送一条消息(我不是知道你的确切用例,我的是我有专门的订阅,如果我想做广播,我会迭代它们) 收到StompCommand.SUBSCRIBE后,立即在ApplicationListener的onApplicationEvent方法中发送消息

订阅事件处理程序:

/app/hello

4
投票

我遇到了同样的问题,当我在客户端订阅@MessageMapping@SendTo时,最后切换到解决方案,缓冲在this chat application demonstrating a log of Spring websocket features with a real world use case处理程序上收到的所有内容,直到websocket-stomp-user-destination-bound将下载所有聊天记录,这就是@Override public void onApplicationEvent(SessionSubscribeEvent event) { Message<byte[]> message = event.getMessage(); StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message); StompCommand command = accessor.getCommand(); if (command.equals(StompCommand.SUBSCRIBE)) { String sessionId = accessor.getSessionId(); String stompSubscriptionId = accessor.getSubscriptionId(); String destination = accessor.getDestination(); // Handle subscription event here // e.g. send welcome message to *destination* } } 返回的内容。然后我将所有最近的聊天条目与/topic上收到的聊天条目合并 - 在我的情况下可能会有重复。

另一种工作方法是宣布

/app

显然,并不完美。但工作:)


0
投票

也许它并不完全相关,但是当我订阅'app / test'时,无法接收发送到'app / test'的消息。

所以我发现添加经纪人是问题(不知道为什么顺便说一句)。

所以这是我之前的代码:

/topic

之后:

/app

现在,当我订阅'app / test'时,这是有效的:

@SubscribeMapping

就我而言,我不需要更多。

© www.soinside.com 2019 - 2024. All rights reserved.