Spring WebsocketとSTOMP、Simple Message Brokerを使用しています。 @Controller
では、メソッドレベルの@SubscribeMapping
を使用します。これは、クライアントがトピックにメッセージを受信できるように、クライアントをトピックにサブスクライブする必要があります。たとえば、クライアントがトピック"chat"にサブスクライブするとします。
stompClient.subscribe('/app/chat', ...);
クライアントが"/ topic/chat"の代わりに"/ app/chat"にサブスクライブすると、このサブスクリプションは@SubscribeMapping
を使用してマップされるメソッドに移動します。
@SubscribeMapping("/chat")
public List getChatInit() {
return Chat.getUsers();
}
Spring refがここにあります。言う:
デフォルトでは、@ SubscribeMappingメソッドからの戻り値は、接続されたクライアントに直接メッセージとして送信され、ブローカーを通過しません。これは、要求と応答のメッセージ対話を実装するのに役立ちます。たとえば、アプリケーションUIが初期化されているときにアプリケーションデータを取得します。
さて、これは私が望むものでしたが、ただ部分的に !!サブスクライブ後にinit-dataを送信します。しかし、subscribeingについてはどうですか?ここで起こったことは、サービスのようにrequest-replyに過ぎないように思えます。サブスクリプションは消費されます。これが当てはまる場合は、明確にしてください。
ここでクライアントがどこにもサブスクライブされていない場合、なぜこれを「サブスクライブ」と呼ぶのでしょうか。クライアントは将来のメッセージではなく1つのメッセージのみを受信するためです。
編集:
サブスクリプションが実現されたことを確認するために、私は次のことを試みました。
サーバー側:
構成:
@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 }));
...
私がしたいこと:
/topic/greetings
」をサブスクライブすると、メソッドtry1
が実行されます。/app/hello
'に送信すると、@SendTo
'/topic/greetings
'の挨拶メッセージを受信する必要があります。結果:
クライアントが/topic/greetings
をサブスクライブしている場合、メソッドtry1
はそれをキャッチできません。
クライアントがmsgを「/app/hello
」に送信すると、greeting
メソッドが実行され、クライアントはグリーティングメッセージを受信しました。したがって、「/topic/greetings
」に正しくサブスクライブされていることがわかりました。
しかし、1が失敗したことを思い出してください。いくつかの試行の後、クライアントが'/app/topic/greetings'
にサブスクライブしたときに可能になりました。つまり、/app
というプレフィックスが付いています(これは設定で理解できます)。
現在は1.が動作していますが、今回は2.が失敗します。クライアントがmsgを '/app/hello
'に送信すると、はい、greeting
メソッドは実行されましたが、クライアントはグリーティングメッセージを受信しませんでした。 (おそらく、クライアントは「/app
」という接頭辞が付いたトピックにサブスクライブされたため、これは望ましくありませんでした。)
だから、私が得たのは私が望むものの1つまたは2つですが、これら2つは一緒ではありません。
デフォルトでは、@ SubscribeMappingメソッドからのreturn valueは、接続されたクライアントにメッセージとして直接返され、doesブローカーを通過しません。
(エンファシス鉱山)
ここで、Spring Frameworkのドキュメントは、着信SUBSCRIBE
メッセージではなく、応答メッセージで何が起こるかを説明しています。
質問に答えるには:
SimpleMessageBroker
を使用すると、メッセージブローカーの実装はアプリケーションインスタンスに存在します。サブスクリプションの登録は、DefaultSubscriptionRegistry
によって管理されます。メッセージを受信すると、SimpleBrokerMessageHandler
はSUBSCRIPTION
メッセージを処理し、サブスクリプションを登録します( 実装を参照 )。
RabbitMQのような「実際の」メッセージブローカーを使用して、メッセージをブローカーに転送するStompブローカーリレーを構成しました。その場合、SUBSCRIBE
メッセージは、サブスクリプションの管理を担当するブローカーに転送されます( こちらの実装を参照 )。
STOMPメッセージフローのリファレンスドキュメント を見ると、次のことがわかります。
- 「/ topic/greeting」へのサブスクリプションは「clientInboundChannel」を通過し、ブローカーに転送されます
- 「/ app/greeting」に送信されたグリーティングは、「clientInboundChannel」を通過して、GreetingControllerに転送されます。コントローラーは現在の時刻を追加し、戻り値はメッセージとして「brokerChannel」を介して「/ topic/greeting」に渡されます(宛先は規則に基づいて選択されますが、@ SendToでオーバーライドできます)。
ここで、/topic/hello
はブローカーの宛先です。そこで送信されたメッセージは、ブローカーに直接転送されます。 /app/hello
はアプリケーションの宛先であり、/topic/hello
が別の指示をしない限り、@SendTo
に送信されるメッセージを生成することになっています。
更新された質問はどういうわけか異なるものであり、より正確なユースケースがなければ、これを解決するのに最適なパターンを言うことは困難です。以下にいくつかを示します。
/topic/hello
にサブスクライブします/topic/hello
/app/hello
にSUBSCRIBEを送信し、コントローラーがすぐにメッセージで応答する/app/hello
:@MessageMapping
、@SendTo
の組み合わせ、またはメッセージングテンプレートを使用します。良い例が必要な場合は、 実際のユースケースでSpring websocket機能のログを示すこのチャットアプリケーション をチェックしてください。
両方を持っている:
あなたが経験したように機能しません(私も同様)。
(私がやったように)あなたの状況を解決する方法は:
ApplicationListenerを実装する
単一のクライアントに直接返信したい場合は、ユーザーの宛先を使用します( websocket-stomp-user-destination を参照するか、/ topic/my-id-42などのサブパスをサブスクライブすることもできます)このサブトピックにメッセージを送信できます(正確なユースケースについてはわかりませんが、私は専用のサブスクリプションを持っているので、broadcast)
StompCommand.SUBSCRIBEを受け取ったらすぐに、ApplicationListenerのonApplicationEventメソッドでメッセージを送信します
サブスクリプションイベントハンドラー:
@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
の両方にサブスクライブすると、最終的にソリューションに切り替わり、/topic
バインドまで/app
ハンドラーで受信したすべてをバッファリングしましたすべてのチャット履歴をダウンロードします。つまり、@SubscribeMapping
が返します。次に、最近のすべてのチャットエントリを/topic
で受信したものとマージします。私の場合は重複している可能性があります。
別の作業アプローチは、宣言することでした
registry.enableSimpleBroker("/app", "/topic");
registry.setApplicationDestinationPrefixes("/app", "/topic");
明らかに、完璧ではありません。しかし、働いた:)
こんにちはMertさん、質問は4年以上前に聞かれますが、最近同じ問題に頭を悩まして最終的に解決したので、答えようとします。
ここで重要なのは@SubscribeMapping
がone-timeリクエスト-レスポンス交換であるため、コントローラーのtry1()
メソッドはクライアントコードの実行直後に1回だけトリガーされる
stompClient.subscribe('/topic/greetings', callback)
その後、try1()
によってstompClient.send(...)
をトリガーする方法はありません
ここでのもう1つの問題は、コントローラーがアプリケーションメッセージハンドラーの一部であり、宛先がプレフィックス/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宛先にサブスクライブすることもできます。コントローラーは、サブスクリプションを格納または使用せずにブローカーを関与させることなく、そのサブスクリプションに応じて値を返すことができます(事実上、1回限りの要求応答交換)。この使用例の1つは、起動時にUIに初期データを入力することです。
完全に関連しているわけではないかもしれませんが、「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);
私の場合、これ以上は必要ありません。