programing

Spring @SubscribeMapping이 정말로 어떤 주제에 대해 고객을 구독합니까?

oldcodes 2023. 10. 26. 21:41
반응형

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 }));
...

제가 하고 싶은 일:

  1. 클라이언트가 '에 가입할 때/topic/greetings', 방법, 메소드try1실행됩니다.
  2. 클라이언트가 msg를 '로 보낼 때/app/hello', 그것은 인사말 msg를 받아야 합니다.@SendTo'/topic/greetings'.

결과:

  1. 클라이언트가 가입하는 경우/topic/greetings, 방법, 메소드try1잡을 수 없습니다.

  2. 클라이언트가 msg를 '로 보낼 때/app/hello',greeting메소드가 실행되고 클라이언트는 인사 메시지를 받았습니다.그래서 우리는 그것이 '에 가입된 것으로 이해했습니다./topic/greetings' 정확하게

  3. 하지만 1.이 실패했다는 것을 기억하세요.얼마간의 시도 끝에, 클라이언트가 가입했을 때 가능했습니다.'/app/topic/greetings', 예를 들어 에 접두어로/app(이는 구성으로 이해할 수 있습니다.)

  4. 지금은 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을 사용하여 연결 응답 제공

당신이 경험한 것처럼 (나뿐만 아니라) 작동하지 않습니다.

당신의 상황을 해결하는 방법은 (내가 한 것처럼) 다음과 같습니다.

  1. @SubscribeMapping 제거 - /app 접두사에서만 작동합니다.
  2. 자연스럽게 (w/o/app 접두사 포함) /topic을 구독합니다.
  3. 응용프로그램 수신기 구현

    1. 단일 클라이언트에 직접 응답하려면 사용자 대상(websocket-stamp-user-destination 참조)을 사용하거나 하위 경로(예: /topic/my-id-42)에 가입할 수도 있습니다(정확한 사용 사례는 모르겠습니다).저는 전용 구독이 있고 방송을 하고 싶을 때 반복해서 구독합니다.)

    2. 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

반응형