Spring @SubscribeMapping이 정말로 어떤 주제에 대해 고객을 구독합니까?
STOMP, Simple Message Broker와 함께 Spring Websocket을 사용하고 있습니다.@Controller
메소드 레벨을 사용합니다.@SubscribeMapping
, 클라이언트가 해당 주제의 메시지를 나중에 수신할 수 있도록 클라이언트에게 주제를 구독해야 합니다.예를 들어, 클라이언트가 "채팅"이라는 항목을 구독한다고 가정해 보겠습니다.
stompClient.subscribe('/app/chat', ...);
클라이언트가 "/topic/chat" 대신 "/app/chat"을 구독하면 이 구독은 다음을 사용하여 매핑된 메서드로 이동합니다.@SubscribeMapping
:
@SubscribeMapping("/chat")
public List getChatInit() {
return Chat.getUsers();
}
Spring ref는 이렇습니다.다음과 같이 말합니다.
기본적으로 @SubscribeMapping 메서드의 반환 값은 연결된 클라이언트에 직접 메시지로 전송되며 브로커를 통과하지 않습니다.이 기능은 응용프로그램 UI를 초기화할 때 응용프로그램 데이터를 가져오는 등 요청-응답 메시지 상호 작용을 구현하는 데 유용합니다.
좋아요, 제가 원하는 건 이런 거였는데, 부분적으로만!!가입 후 초기 데이터를 보내는 중입니다.하지만 구독하는 건 어떨까요?제가 보기에는 여기서 일어난 일은 서비스와 같은 요청-응답일 뿐입니다.구독이 방금 소비되었습니다.이것이 사실인지 명확히 해주시기 바랍니다.
- 중개인이 이 일에 관여하지 않았다면 고객이 어디선가 청약을 했습니까?
- 나중에 "채팅" 구독자에게 메시지를 보내고 싶다면 고객이 받을 수 있습니까?그렇지 않은 것 같습니다.
- 누가 진정으로 구독을 실현합니까?브로커?아니면 다른 사람?
여기서 클라이언트가 어디에도 가입되어 있지 않다면 클라이언트가 미래의 메시지를 수신하지 않고 하나의 메시지만 수신하기 때문에 왜 이것을 "구독"이라고 부르는지 궁금합니다.
편집:
구독이 실현되도록 하기 위해 시도한 것은 다음과 같습니다.
서버측:
구성:
@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
실행됩니다. - 클라이언트가 msg를 '로 보낼 때
/app/hello
', 그것은 인사말 msg를 받아야 합니다.@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 메서드의 반환 값은 연결된 클라이언트에 직접 메시지로 전송되며 브로커를 통과하지 않습니다.
(emphasis 광산)
여기에서 스프링 프레임워크 문서는 수신 메시지가 아닌 응답 메시지에서 발생하는 일을 설명합니다.SUBSCRIBE
메세지.
질문에 답변해 드리겠습니다.
- 예, 고객은 해당 주제에 가입되어 있습니다.
- 예, 해당 항목을 사용하여 해당 항목을 전송하면 해당 항목에 가입된 클라이언트가 메시지를 받게 됩니다.
- 메시지 브로커는 구독 관리를 담당합니다.
서브스크립션 관리에 대한 자세한 내용
더 SimpleMessageBroker
, 메시지 브로커 구현은 응용 프로그램 인스턴스에 존재합니다.가입 등록은 에 의해 관리됩니다.DefaultSubscriptionRegistry
. 메시지를 받을 때,SimpleBrokerMessageHandler
손잡이들SUBSCRIPTION
메시지 및 가입 등록(여기 구현 참조).
RabbitMQ와 같은 "진짜" 메시지 브로커를 사용하면 브로커에게 메시지를 전달하는 Stomp 브로커 릴레이를 구성할 수 있습니다.그런 경우에는.SUBSCRIBE
메시지는 구독 관리를 담당하는 브로커에게 전달됩니다(여기 구현 참조).
업데이트 - STOMP 메시지 흐름에 대한 자세한 내용
STOMP 메시지 흐름에 대한 참조 문서를 보면 다음을 알 수 있습니다.
- "/topic/greeting" 구독은 "clientInbound Channel"을 거쳐 브로커에게 전달됩니다.
- "/app/greeting"으로 전송된 인사말은 "client Inbound Channel"을 거쳐 Greeting Controller로 전달됩니다.컨트롤러는 현재 시간을 추가하고 반환 값은 "/topic/greeting"(/topic/greeting)에 대한 메시지로 "brokerChannel"을 통해 전달됩니다(목적지는 규약에 따라 선택되지만 @SendTo를 통해 재정의될 수 있음)
, 여기 , , ./topic/hello
는 브로커 대상입니다. 거기서 보낸 메시지는 브로커에게 직접 전달됩니다.하는 동안에/app/hello
는 응용 프로그램 대상이며, 에 보낼 메시지를 생성하기로 되어 있습니다./topic/hello
,~하지 않는 한@SendTo
그렇지 않다고 말합니다.
업데이트된 질문은 어떻게든 다릅니다. 더 정확한 사용 사례가 없다면 어떤 패턴이 이 문제를 해결하는 데 최선의 방법인지 말하기가 어렵습니다.다음은 몇 가지입니다.
- 어떤 일이 발생할 때마다 클라이언트가 인식하기를 원하는 경우, 비동기식으로: 특정 주제를 구독합니다.
/topic/hello
- 메시지를 브로드캐스트하려면: 특정 주제에 메시지를 보냅니다.
/topic/hello
- 응용 프로그램의 상태를 초기화하기 위해 즉시 피드백을 받으려는 경우: 응용 프로그램 대상에 SUBSCUBLE(가입)
/app/hello
컨트롤러가 메시지로 바로 응답합니다. - 응용프로그램 대상으로 하나 이상의 메시지를 보내길 원합니다.
/app/hello
: …을 배합하여 쓰다@MessageMapping
,@SendTo
또는 메시지 템플릿을 선택합니다.
좋은 예를 원한다면 실제 사용 사례와 함께 Spring 웹소켓 기능의 로그를 보여주는 이 채팅 어플리케이션을 확인해보세요.
따라서 두 가지를 모두 가질 수 있습니다.
- 항목을 사용하여 헤드라인 등록 처리
- 해당 항목에 대해 @SubscribeMapping을 사용하여 연결 응답 제공
당신이 경험한 것처럼 (나뿐만 아니라) 작동하지 않습니다.
당신의 상황을 해결하는 방법은 (내가 한 것처럼) 다음과 같습니다.
- @SubscribeMapping 제거 - /app 접두사에서만 작동합니다.
- 자연스럽게 (w/o/app 접두사 포함) /topic을 구독합니다.
응용프로그램 수신기 구현
단일 클라이언트에 직접 응답하려면 사용자 대상(websocket-stamp-user-destination 참조)을 사용하거나 하위 경로(예: /topic/my-id-42)에 가입할 수도 있습니다(정확한 사용 사례는 모르겠습니다).저는 전용 구독이 있고 방송을 하고 싶을 때 반복해서 구독합니다.)
Stomp 명령을 받는 즉시 Application Listener의 onApplicationEvent 메서드로 메시지를 보냅니다.SUBSUBLE
구독 이벤트 처리기:
@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*
}
}
안녕하세요 Mert씨, 당신의 질문은 4년이 넘었지만, 최근에 같은 문제로 머리를 긁어서 결국 해결한 것이기 때문에 아직 답변을 시도해보겠습니다.
여기서 핵심은.@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 !!!')
효과가 있을 겁니다
공식 문서는 또한 다음의 사용 사례 시나리오를 권장합니다.@SubscribeMapping
초기 UI 렌더링 시.
이것은 언제 유용합니까?브로커는 /topic 및 /queue에 매핑되고 애플리케이션 컨트롤러는 /app에 매핑된다고 가정합니다.이 설정에서 브로커는 반복 방송을 위한 /topic 및 /queue에 대한 모든 구독을 저장하며, 응용 프로그램이 관여할 필요가 없습니다.클라이언트는 또한 일부 /app 대상을 구독할 수 있으며 컨트롤러는 구독을 저장하거나 다시 사용하지 않고 브로커를 참여시키지 않고 구독에 대한 응답으로 값을 반환할 수 있습니다(실질적으로 일회성 요청-응답 교환).이를 위한 하나의 사용 사례는 시작 시 초기 데이터로 UI를 채우는 것입니다.
저는 같은 문제에 직면했고, 마침내 두 가지를 모두 구독할 때 솔루션으로 전환했습니다./topic
그리고./app
클라이언트에서 수신된 모든 것을 버퍼링합니다./topic
까지 핸들러/app
-bound one은 모든 채팅 기록을 다운로드 할 것이고, 그것이 바로 그것입니다.@SubscribeMapping
돌아온다.그런 다음 모든 최근 채팅 항목을 에 수신된 항목과 병합합니다./topic
- 제 경우엔 중복이 있을 수도 있습니다
또 다른 작업 방식은 다음과 같이 선언하는 것이었습니다.
registry.enableSimpleBroker("/app", "/topic");
registry.setApplicationDestinationPrefixes("/app", "/topic");
확실히 완벽하지는 않습니다.효과가 있었습니다 :)
완전한 연관성이 있는 건 아닐지 몰라도, '앱/테스트'를 구독할 때는 '앱/테스트'로 보내는 메시지를 받을 수가 없었습니다.
그래서 브로커를 추가하는 것이 문제라는 것을 알게 되었습니다(그런데 왜 그런지 모르겠습니다).
그럼 여기 제 코드가 있습니다.
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.setApplicationDestinationPrefixes("/app");
config.enableSimpleBroker("/topic");
}
이후:
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.setApplicationDestinationPrefixes("/app");
// problem line deleted
}
이제 '앱/테스트'에 가입하면 다음과 같이 작동합니다.
template.convertAndSend("/app/test", stringSample);
저 같은 경우에는 더 이상 필요 없습니다.
언급URL : https://stackoverflow.com/questions/29085791/does-spring-subscribemapping-really-subscribe-the-client-to-some-topic
'programing' 카테고리의 다른 글
각 행을 다른 열에 SUM()하는 방법 (0) | 2023.10.26 |
---|---|
블레이드의 조건부 확장 (0) | 2023.10.26 |
MYSQL 문 최적화 (0) | 2023.10.26 |
동일한 TextView에서 문자열의 글꼴 크기가 다름 (0) | 2023.10.26 |
헤더가 없는 파워셸 내보내기-csv? (0) | 2023.10.26 |