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

时间:2015-03-16 19:54:42

标签: spring stomp messagebroker spring-websocket spring-messaging

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

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

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

@SubscribeMapping

这是Spring ref。表示:

  

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

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

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

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

修改

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

服务器侧

配置:

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

控制器:

@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" + "!");
    }
}

我想要发生什么:

  1. 当客户订阅“... stompClient.subscribe('/topic/greetings', function(greeting){ console.log('RECEIVED !!!'); }); stompClient.send("/app/hello", {}, JSON.stringify({ 'name': name })); ... ”时,方法/topic/greetings为 执行。
  2. 当客户端将msg发送到“try1”时,它应该会收到问候语msg,即/app/hello'@SendTo'。
  3. 结果:

    1. 如果客户订阅了/topic/greetings,则方法/topic/greetings无法捕获它。

    2. 当客户端将msg发送到“try1”时,执行了/app/hello方法,客户端收到了问候消息。所以我们理解它已正确订阅了“greeting”。

    3. 但请记住1.失败了。经过一番尝试后,客户端可以订阅/topic/greetings,即前缀为'/app/topic/greetings'(这可以通过配置理解)。

    4. 现在1.正在运行,但是这次2.失败:当客户端将msg发送到“/app”时,是的,/app/hello方法被执行了,但是客户端没有收到问候消息。 (因为可能现在客户端订阅了前缀为“greeting”的主题,这是不需要的。)

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

      • 如何使用此结构实现此目的(正确配置映射路径)?

5 个答案:

答案 0 :(得分:15)

  

默认情况下,会发送@SubscribeMapping方法的返回值   作为直接返回已连接客户端的消息,未通过   通过经纪人

(强调我的)

此处,Spring Framework文档描述了响应消息所发生的情况,而不是传入的SUBSCRIBE消息。

所以回答你的问题:

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

有关订阅管理的更多信息

使用SimpleMessageBroker,消息代理实现存在于您的应用程序实例中。订阅注册由DefaultSubscriptionRegistry管理。 收到消息时,SimpleBrokerMessageHandler处理SUBSCRIPTION消息并注册订阅(see implementation here)。

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

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

如果您查看the reference documentation on STOMP message flow,您会看到:

  
      
  • 订阅" / topic / greeting"通过" clientInboundChannel"并转发给经纪人
  •   
  • 致问" / app / greeting"通过" clientInboundChannel"并转发给GreetingController。控制器添加当前时间,返回值通过" brokerChannel"作为" / topic / greeting"的消息(目的地是根据惯例选择的,但可以通过@SendTo覆盖。)
  •   

所以在这里,/topic/hello是经纪人目的地;发送的消息直接转发给代理。虽然/app/hello是应用程序目标,但应该生成要发送到/topic/hello的邮件,除非@SendTo另有说明。

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

  • 您希望客户端在发生异常情况时发现异常情况:订阅特定主题/topic/hello
  • 您要广播消息:向特定主题发送消息/topic/hello
  • 您希望获得有关某些内容的即时反馈,例如初始化您的应用程序的状态:SUBSCRIBE到应用程序目标/app/hello,控制器立即响应消息
  • 您要向一个应用目标/app/hello发送一条或多条消息:使用@MessageMapping@SendTo或消息模板的组合。

如果您想要一个好例子,请查看this chat application demonstrating a log of Spring websocket features with a real world use case

答案 1 :(得分:14)

两个都有:

  • 使用主题处理订阅
  • 使用@SubscribeMapping 提供连接响应的主题

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

解决问题的方法(正如我所做的那样)是:

  1. 删除@SubscribeMapping - 它仅适用于/ app前缀
  2. 就像你自然地(没有/ app前缀)
  3. 一样订阅/ topic
  4. 实施ApplicationListener

    1. 如果您想直接回复单个客户端,请使用用户目的地(请参阅websocket-stomp-user-destination 或者您也可以订阅子路径,例如/ topic / my-id-42然后你可以向这个子主题发送一条消息(我不知道你的确切用例,我的是我有专门的订阅,如果我想做一个我会迭代它们广播

    2. 收到StompCommand.SUBSCRIBE后立即在ApplicationListener的onApplicationEvent方法中发送消息

  5. 订阅事件处理程序:

    {{1}}

答案 2 :(得分:4)

我面临同样的问题,当我在客户端订阅/topic/app时,最后切换到解决方案,缓冲/topic处理程序上收到的所有内容,直到{{1} } -bound将下载所有聊天记录,即/app返回的内容。然后我将所有最近的聊天条目与@SubscribeMapping上收到的聊天条目合并 - 在我的情况下可能会有重复。

另一种工作方法是宣布

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

答案 3 :(得分:1)

虽然您的问题是4年前问的,但您好,但我仍然会尽力回答这个问题,因为我最近对相同的问题scratch了脑筋,终于解决了。

这里的关键部分是@SubscribeMapping是一次一次请求-响应交换,因此,控制器代码中的try1()方法仅在客户端代码之后立即触发一次运行

stompClient.subscribe('/topic/greetings', callback)

之后,无法通过try1()触发stompClient.send(...)

这里的另一个问题是控制器是应用程序消息处理程序的一部分,该程序接收到带有前缀/app的目的地,因此,要到达@SubscribeMapping("/topic/greetings"),您实际上必须编写这样的客户端代码

stompClient.subscribe('/app/topic/greetings', callback)

由于通常将topic与代理进行映射以避免混淆,因此我建议将您的代码修改为

@SubscribeMapping("/greetings")

stompClient.subscribe('/app/greetings', callback)

现在console.log('RECEIVED !!!')应该可以工作。

official doc还建议在初始UI呈现时使用@SubscribeMapping的用例场景。

  

什么时候有用?假定代理映射到/ topic和/ queue,而应用程序控制器映射到/ app。在此设置中,代理将所有打算重复广播的/ topic和/ queue订阅都存储起来,并且不需要应用程序参与其中。客户端还可以订阅某个/ app目的地,并且控制器可以响应该订阅而返回一个值,而无需经纪人,而无需再次存储或使用该订阅(实际上是一次请求-答复交换)。一个用例是在启动时用初始数据填充UI。

答案 4 :(得分:0)

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

所以我发现添加经纪人是个问题(不知道为什么)。

这是我之前的代码:

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

之后:

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.setApplicationDestinationPrefixes("/app");
        // problem line deleted
    }

现在,当我订阅“ app / test”时,此方法就可以正常工作了:

    template.convertAndSend("/app/test", stringSample);

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